오류 및 예외 처리
- 오류(Error) : 일반적으로 회복이 불가능한 문제
- 시스템 레벨 도는 환경적인 이유로 발생
- 코드의 문제로 발생하는 경우도 있으나, 발생하면 일반적으로 회복이 불가능
- 에러가 발생한 경우 어떤 에러로 프로그램이 종료되었는지 확인하고 대응
- 예외(Exception) : 일반적으로 회복이 가능한 문제
- 회복이 가능하다는 전제는 예외가 발생할 수 있다는 것을 인지하고 대응했을 것
- 코드레벨에서 할 수 있는 문제사항에 대한 대응은 "예외 처리"에 속함
예외의 종류
- 코드 실행 관점에서의 예외 종류
- 컴파일 에러(예외)
- .java 파일을 .class 파일로 컴파일할 때 발생하는 에러
- 대부분 자바 프로그래밍 언어의 규칙을 어겨서 발생
- 없는 클래스 호출, 접근이 불가능한 프로퍼티나 메서드에 접근하는 경우
- 해결방법은 문법에 맞게 다시 작성하는 것
- 런타임 에러(예외)
- 주로 다루게 될 에러(예외)
- 문법적 오류는 아니라, 컴파일은 잘 되었지만, "프로그램"이 실행 도중 맞닥뜨리게 되는 예외
- 컴파일 에러(예외)
- 예외처리 관점에서의 예외 종류
- 확인된 예외(Checked Exception)
- 컴파일 시점에 확인하는 예외
- 반드시 예외 처리를 해줘야 하는 예외
- 확인된 예외에 대한 예외 처리를 하지 않을 경우 컴파일 에러 발생
- 미확인된 예외(Unchecked Exception)
- 런타임 시점에 확인되는 예외
- 예외 처리가 반드시 필요하지 않은 예외
- 확인된 예외(Checked Exception)
Property(프로퍼티) : 객체의 상태 또는 데이터를 나타내는 필드(멤버 변수)에 대한 접근을 제어하는 메서드(함수)를 의미.
객체의 필드에 직접 접근하는 대신 사용되어, read, write, 기타 동작을 캡슐화하고 제어하는 방법을 제공
구성요소
- Getter 메서드 : 객체의 특정 필드 값을 반환하는 메서드, 주로 필드 값을 읽을 때 사용, getPropertyName 같은 이름으로 명명
- Setter 메서드 : 객체의 특정 필드에 값을 설정하는 메서드, 필드 값을 변경할 때 사용되며, setPropertyName 같은 이름으로 명명
장점
- 캡슐화 : 객체의 내부 상태를 보호하고, 외부에서는 메서드를 통해서만 상태를 변경할 수 있게 함(객체의 데이터와 행위를 하나로 묶어 관리할 수 있음)
- 유효성 검사 : Setter 메서드를 통해 객체의 필드에 설정되는 값을 검사하고, 유효하지 않은 값이 설정되는 것을 방지
- 추상화 : 사용자는 객체의 내부 구현을 몰라도 Getter와 Setter 메서드를 통해 객체와 상화작용할 수 있음(객체 사용의 복잡성을 줄이고, 사용자에게 간결한 인터페이스 제공)
- 유연성 : 프로퍼티의 구현을 변경해도 프로퍼티의 인터페이스는 그대로 유지할 수 있어, 코드의 수정 없이 내부 구현을 개선하거나 변경할 수 있음
예시
public class Person {
private String name; // 'name' 필드는 직접 접근 불가
// Getter 메서드
public String getName() {
return name;
}
// Setter 메서드
public void setName(String name) {
this.name = name;
}
}
예외 처리
예외 클래스
package week04.sample01;
// 예외 클래스를 만들어서 예외 정의
public class OurBadException extends Exception {
public OurBadException() {
super("위험한 행동을 하면 예외처리를 꼭 해야함!!");
}
}
package week04.sample01;
public class OurClass {
private final boolean just = true;
// throws : 예외를 던지다/ 발생시키다
public void thisMethodIsDangerous() throws OurBadException {
// custom logic~!
if (just) {
throw new OurBadException();
}
}
}
throws | throw |
메서드 이름 뒤에 붙어 이 메서드가 어떤 예외사항을 던질 수 있는지 알려주는 예약어 | 메서드 안에서, 실제로 예외 객체를 던질 때 사용하는 예약어 |
여러 종류의 예외사항을 적을 수 있음 | 실제로 던지는 예외 객체 하나와 같이 써야 함 |
일반 메서드의 return 키워드처럼 throw 아래의 구문들은 실행되지 않고, throw문과 함께 메서드가 종료 |
예외 처리 사용 예시
package week04.sample01;
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
// try ~ catch ~ finally 구문
// 일단 try하고 예외가 발생하면 그걸 잡아(catch)
// 정상적으로 수행되든, 예외가 발생하든 결국 수행해야 하는 로직을 finally 수행해
try {
//일단 실행
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
System.out.println(e.getMessage());
} finally {
//무조건 여기는 거침
System.out.println("우리는 방금 예외를 handling 했습니다. 정상처리든, 예외사항이 발생하든 여기를 거쳐요!");
}
}
}
예외 클래스 구조 이해
Throwable Class
- 모든 객체의 원형인 Object 클래스에서 시작
- "문제 상황"을 뜻하는 Throwable 클래스가 Object 클래스를 상속
- Throwable 클래스의 자식으로 Error와 Exception 클래스가 있음
- Error 클래스와 Exception 클래스는 각각 IOError 클래스, RutimeException 클래스와 같이 구분하여 처리
Exception 클래스 구조
자바의 수많은 에러 구현체
Java 예외 리스트
// 참고: https://programming.guide/java/list-of-java-exceptions.html
Chained Exception, 실제 예외 처리하는 방법
- 연결된 예외
- 예외는 다른 예외를 유발할 수 있습니다.
- 예외 A가 예외 B를 발생시켰다면, 예외 A는 B의 원인 예외
- 원인 예외를 새로운 예외에 등록한 후 다시 새로운 예외를 발생시키는데, 이를 연결 예외라 함
- 원인 예외를 다루기 위한 메서드
- initCause( )
- 지정한 예외를 원인 예외로 등록하는 메서드
- getCause( )
- 원인 예외를 반환하는 메서드
- initCause( )
package week04.sample01;
// 연결된 예외
public class ExceptionEx {
public static void main(String[] args) {
try {
// 예외 생성
NumberFormatException ex = new NumberFormatException("가짜 예외 이유");
// 원인 예외 설정(지정한 예외를 원인 예외로 등록)
ex.initCause(new NullPointerException("진짜 예외이유"));
// 예외를 직접 던짐
throw ex;
} catch (NumberFormatException ex) {
// 예외 로그 출력
ex.printStackTrace();
// 예외 원인 조회 후 출력
ex.getCause().printStackTrace();
}
// checked exception 을 감싸서 unchecked exception 안에 넣는다
throw new RuntimeException(new Exception("이것이 진짜 예외 이유입니다."));
}
}
// 출력
//Cause by: java.lang.NumberFormatException: 진짜 예외이유
실제 예외 처리
1. 예외 복구
public String getDataFromAnohterServer(String dataPath) {
try {
return anotherServerClient.getData(dataPath).toString();
} catch (GetDataException e) {
return defaultData;
}
}
- 실제로 try-catch로 예외 처리 및 프로그램 정상 상태로 복구
- 가장 기본적인 방식이지만, 현실적으로 복구가 가능한 상황이 아닌 경우가 많거나 최소한의 대응만 가능한 경우가 많기 때문에 자주 사용하지 않음
2. 예외 처리 회피
public void someMethod() throws Exception {...}
public void someIrresponsibleMethod() throws Exception {
this.someMethod();
}
- 관심사를 분리해서 한 레이어에서 처리하기 위해서 에러를 회피해서 그대로 흘려 보내는 경우
3. 예외 전환
public void someMethod() throws IOException {...}
public void someResponsibleMethod() throws MoreSpecificException {
try {
this.someMethod();
} catch (IOException e) {
throw new MoreSpecificException(e.getMessage());
}
}
- 예외처리 회피하기의 방법과 비슷하지만, 조금 더 적절한 예외를 던져주는 경우
- 예외처리에 더 신경 쓰고 싶은 경우나, 오히려 RuntimeException처럼 일괄적으로 처리하기 편한 예외로 바꿔서 던지고 싶은 경우 사용
Generic(제네릭) - 타입을 더 유연하게 다루기
- 제네릭 효용
- 타입 언어에서 "중복되거나 필요없는 코드를 줄여 주는 것"
- 그러면서도 타입 안정성을 해치지 않는 것
- 타입 언어에서의 중복되거나 필요 없는 코드란?
- 자바는 타입을 지정해줘야 하는 언어라 똑같은 로직을 수행하는 함수를 타입을 지정해야 되는 이유로 세 차례나 구현해야 함
제네릭 문법
package week04.gen;
// 1. 제네릭은 클래스 또는 메서드에 사용 가능
// <>안에 들어가야 할 타입을 명시
public class Generic<T> {
// 2. 내부 필드에 String
private T t;
// 3. method의 return type도 String
public T get() {
return this.t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
// 4.
Generic<String> stringGeneric = new Generic<>();
// 5.
stringGeneric.set("Hello World");
String tValueTurnOutWithString = stringGeneric.get();
System.out.println(tValueTurnOutWithString);
}
}
제네릭 용어 정리
- Generic<T>의 클래스처럼, 제네릭을 사용한 클래스를 제네릭 클래스라 함
- 제네릭에서 <> 사이에 들어가는 변수명 T는 타입 변수라고 함
- Generic 클래스를 원시 타입이라 함
제네릭 제한
- 객체의 static 멤버에 사용할 수 없음
- 타입 변수는 인스턴스 변수로 간주되고, 모든 객체에 동일하게 동작해야 하는 static 필드 특성상 사용할 수 없음
- 제네릭 배열을 생성할 수 없음
제네릭 문법
- 다수의 타입 변수를 사용할 수 있음
public class Generic<T, U, E> {
public E multiTypeMethod(T t, U u) { ... }
}
Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);
- 다형성, 상속과 타입의 관계는 그대로 적용
- 제네릭 부모 클래스로 제네릭 타입 변수를 지정하고, 그 안에 자식 클래스를 넘기는 것은 가능
public class ParkingLot<T extends Car> { ... }
ParkingLot<BMW> bmwParkingLot = new ParkingLot();
ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!
- 와일드 카드를 통해 제네릭의 제한을 구체적으로 정할 수 있음
- <? extends T> : T와 그 자손들만 사용 가능
- <? super T> : T와 그 조상들만 가능
- <?> : 제한 없음
- 제한을 하는 이유는 다형성 때문
- 메서드를 스코프로 제네릭을 별도로 선언 가능
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
- 반환 타입 앞에 <> 제네릭을 사용한 경우, 해당 메서드에만 적용되는 제네릭 타입 변수를 선언할 수 있음
- 타입 변수를 클래스 내부의 인스턴스 변수 취급하기 때문에 제네릭 클래스의 타입 변수를 static 메서드에는 사용할 수 없지만, 제네릭 메서드의 제네릭 타입 변수는 해당 메서드에만 적용되 메서드 하나를 기준으로 선언하고 사용 가능
- 같은 이름의 변수를 사용했다고 해도 제네릭 메서드의 타입 변수는 제네릭 클래스의 타입 변수와 다름
public class Generic<T, U, E> {
// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}
Collection
List는 추상적 자료 구조로서, 순서를 가지고, 일렬로 나열한 원소들의 모임(순서가 있고, 중복을 허용).
- 검색에는 유리하고, 수정/삭제는 불리한 자료구조
추상적 자료구조인 리스트는 개념적으로 보통 연산들을 지원해야 함
- 빈 리스트를 만드는 연산
- 리스트가 비어있는지 확인하는 연산
- 리스트의 앞에 원소를 삽입하는 연산
- 리스트의 뒤에 원소를 삽입하는 연산
- 리스트의 제일 첫 원소를 알아보는 연산
Collection 구조
Wrapper 객체
원시 타입
- 추상적인 기능을 사용하려거나, 기본형 값 대신 객체로 저장해야 하거나, 객체로의 "기능"이 필요할 대 원시형 값들을 잠시 객체로 만들어 사용
예외처리 연습 계산기 예외 처리 추가
- Parser 클래스와 Main 클래스에 예외 처리 구문 추가
package week04.homework;
import java.util.regex.Pattern;
public class Parser{
private static final String OPERATION_REG = "[+\\-*/]";
private static final String NUMBER_REG = "^[0-9]*$";
private final Calculator calculator = new Calculator();
public Parser parseFirstNum(String firstInput) throws Exception {
// 구현 1.
if (!Pattern.matches(NUMBER_REG, firstInput)) {
throw new BadInputException("정수값");
}
this.calculator.setFirstNumber(Integer.parseInt(firstInput));
return this;
}
public Parser parseSecondNum(String secondInput) throws Exception {
// 구현 1.
if (!Pattern.matches(NUMBER_REG, secondInput)) {
throw new BadInputException("정수값");
}
this.calculator.setSecondNumber(Integer.parseInt(secondInput));
return this;
}
public Parser parseOperator(String operationInput) throws Exception {
// 구현 1.
if (!Pattern.matches(OPERATION_REG, operationInput)) {
throw new BadInputException("연산자");
}
switch (operationInput) {
case "+" -> this.calculator.setOperation(new AddOperation());
case "-" -> this.calculator.setOperation(new SubstractOperation());
case "*" -> this.calculator.setOperation(new MultiplyOperation());
case "/" -> this.calculator.setOperation(new DivideOperation());
}
return this;
}
public double executeCalculator() {
return calculator.calculate();
}
}
package week04.homework;
public class Main {
public static void main(String[] args) {
boolean calculateEnded = false;
// 구현 2.
while (!calculateEnded) {
try {
calculateEnded = CalculatorApp.start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
계산기 코드 파일
예외 처리문 추가
Exception 클래스
package samples;
public class FoolException extends RuntimeException {
}
예외 처리 - throws 미사용
package samples;
public class Sample {
public void sayNick(String nick) {
try {
if ("바보".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 " + nick + " 입니다.");
} catch (FoolException e) {
System.err.println("FoolException이 발생했습니다.");
}
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.sayNick("바보");
sample.sayNick("야호");
}
}
예외 처리 - throws 사용
package samples;
public class Sample {
public void sayNick(String nick) throws FoolException {
if ("바보".equals(nick)) {
throw new FoolException();
}
System.out.println("당신의 별명은 " + nick + " 입니다.");
}
public static void main(String[] args) {
Sample sample = new Sample();
try {
sample.sayNick("바보");
sample.sayNick("야호");
} catch (FoolException e) {
System.err.println("FoolException이 발생했습니다.");
}
}
}
throw와 throws 차이
- throw : 메서드 내에서 예외를 발생시키는 데 사용 (throw new FoolException)
- throws : 메서드 선언부에서 사용되며, 해당 메서드가 처리하지 않은 예외를 호출자에게 전달함을 나타냄(public void sayNick(String nick) throws FoolException)
예외 처리 위치는 중요함
프로그래밍할 대 예외 처리 위치는 프로그램의 수행여부를 결정함
- sayNick 메서드에서 예외 처리를 할 경우 sayNick("바보"), sayNick("야호") 두 문장이 모두 수행됨("바보") 문장 수행 시 Exception이 발생하지만 그 다음 문장도 수행됨
- main 메서드에서 예외 처리를 한 경우에는 두 번째 문장이 수행되지 않음, 첫 번째 문장에서 예외가 발생하여 catch 문으로 빠져나감
트랜잭션(transaction)
하나의 작업 단위를 뜻함
쇼핑몰의 '상품발송' 이라는 트랜잭션에는 다음과 같은 작업들이 있음
- 포장
- 영수증 발행
- 발송
운영자는 이 3가지 일 중 하나라도 실패하면 3가지 모두 취소하고 '상품발송'전의 상태로 되돌려야 함(모두 취소하지 않을 경우 데이터 정합성이 크게 흔들리게 됨, 모두 취소하는 행위를 rollback이라 함)
- 데이터 정합성 : 데이터들의 값이 서로 일관성 있게 일치하는 것
3가지 작업 중 1개라도 실패하면 모든 작업을 취소하기 위한 예외 처리 슈도 코드
잘한 경우
상품발송() {
try {
포장();
영수증발행();
발송();
}catch(예외) {
모두취소(); // 하나라도 실패하면 모두 취소한다.
}
}
포장() throws 예외 {
...
}
영수증발행() throws 예외 {
...
}
발송() throws 예외 {
...
}
못한 경우
상품발송() {
포장();
영수증발행();
발송();
}
포장(){
try {
...
}catch(예외) {
포장취소();
}
}
영수증발행() {
try {
...
}catch(예외) {
영수증발행취소();
}
}
발송() {
try {
...
}catch(예외) {
발송취소();
}
}
- 이 경우 1가지 작업은 예외 발생으로 실패했는데 다른 메서드는 실행될 수 있음
ArithmeticException 예제
package samples;
public class Exception02Main {
public static void main(String[] args) {
int num1 = 100;
int num2 = 0;
int result = 0;
// 조건문을 사용한 예외 처리
if (num2 != 0) {
result = num1 / num2;
} else {
System.out.println("0으로 나눌 수 없습니다.");
}
System.out.println("result = " + result);
System.out.println();
// try-catch 문을 사용한 예외 처리
try {
result = num1 / num2;
System.out.println("result = " + result);
} catch (ArithmeticException e) {
System.out.println("0으로 나누면 발생하는 Exception");
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
한 번에 여러 개의 예외 처리 예제
package samples;
public class Main {
public static void main(String[] args) {
int num1 = 100, num2 = 2, result = 0;
String str = "Java";
int[] numbers = new int[10];
try {
result = num1 / num2;
System.out.println("result = " + result);
int length = str.length();
System.out.println("length = " + length);
numbers[10] = 11111;
System.out.println("numbers = " + numbers[10]);
} catch (ArithmeticException e) {
System.out.println("산술 연산 예외 : " + e.getMessage());
} catch (NullPointerException e) {
System.out.println("NPE : " + e.getMessage());
} catch (Exception e) {
System.out.println("Exception = " + e.getMessage());
// Exception은 다른 예외보다 상위 클래스에 속해 있으므로 가장 마지막에 작성
}
}
}
연산자를 이용해 multi-catch를 사용할 수 있음
public class Main {
public static void main(String[] args) {
try {
String str = null;
str.length();
int n = 100 / 0;
} catch (ArithmeticException | NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println(e.getClass());
System.out.println(e.getMessage());
}
}
}
try-catch-finally 예제
package samples;
import java.util.InputMismatchException;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
System.out.println("# 1 try {} 직전 ");
try {
System.out.println("# 2 try {} 시작");
int[] numbers = new int[10];
numbers[10] = 100;
System.out.println("# 3 try {} 종료");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("# 4 catch {}");
System.out.println("예외 메시지 : " + e.getMessage());
} finally {
System.out.println("# 5 finally {}");
}
System.out.println("# 6 try 종료 후");
System.out.println();
Scanner sc = new Scanner(System.in);
try {
System.out.println("정수를 입력하세요.");
sc.nextLine();
System.out.println("try 블록 종료");
} catch (InputMismatchException e) {
System.out.println("예외 메시지 : " + e.getMessage());
return;
} finally {
System.out.println("finally 수행");
sc.close();
}
}
}
예외 처리 잘하는 9가지 방법
참조 : https://hbase.tistory.com/157
1. 리소스 정리
- try-catch 구문에서 리소스를 열었다면, finally 블록에서 리소스를 정리하거나 try-with-resources 구문을 이용해야 한다.
2. 더 자세한 예외
- 메서드를 정의할 때, 이 메서드에서 발생할 수 있는 예외를 throw 구문으로 명시하도록 되어 있다(메서드에서 발생할 수 있는 예외는 최대한 자세한 예외를 명시하는 것이 좋다).
- Exception 클래스로 크게 포함하면 메서드를 호출하는 쪽에서 예외처리하는 코드가 복잡해짐
public void exceptableMethod() throw Exception; // Exception 사용으로 큰 범위로 가져가는 것 보다
public void execptableMethod() throw NumberFormatException; // 자세한 예외를 명시하는 것이 좋음
3. javadoc으로 설명하라
- 메서드가 예외를 발생시킬 수 있는 경우, 어떤 경우에 어떤 예외가 발생하는지 javadoc 문서를 통해서 기술해야 함, throws 절에 최대한 구체적인 예외를 사용하더라도 어떤 상황에서 예외가 발생할 수 있는지 글로 자세히 설명해줘야 메서드를 호출하는 쪽에서 적당한 예외 코드를 작성할 수 있음.
/**
* 이 메소드는 어떨때 사용하는 메소드입니다.
*
* @param input 입력 값
* @throws MyException 어떤어떤 경우에 이 예외가 발생합니다.
*/
public void myMethod(String input) throws MyException { ... }
4. 메시지를 자세하게 적는다
- 사용자 정의 예외를 만들 때, 예외가 발생한 상황에 대한 메시지를 적을 수 있다. 이 때, 어떤 상황에서 예외가 발생했는지 한 두 문장 정도로 간결하게 잘 적어둬야 한다.
- 단, 예외의 이름에서 어떤 상황인지 알 수 있을 때에는 너무 많은 정보를 메시지에 적지 않아도 된다. 그렇기 때문에 최대한 상황을 잘 설명할 수 있는 예외 이름을 짓는 것이 중요하다.
//자세한 메시지를 생략해도 되는 예
try {
new Long("xyz");
} catch (NumberFormatException e) {
System.out.println(e);
}
// 실행 결과
//java.lang.NumberFormatException: For input string: "xyz"
5. catch 절 순서
- try-catch 블럭에서 여러 예외가 발생할 수 있는 경우 좀 더 상세한 예외부터 처리해야 함(예외가 상속 관계에 있을 경우 앞쪽 catch 절에서 더 넓은 범위의 예외를 처리하면, 뒤 쪽 catch 절이 쓸모 없어짐)
//올바른 catch 절 순서
try {
method();
} catch (IllegalArgumentException e) {
log.error(e);
} catch (Exception e) {
log.error(e);
}
6. Throwable은 catch 하지마라
- Throwable은 모든 예외 클래스의 슈퍼 클래스, catch 절에서 잡아 처리할 수 있으나 예외뿐만 아니라 에러도 잡아서 처리하게 되어 JVM에서 예상치 못한 동작을 할 수 있음.
- OutOfMemoryError, StackOverflowError 같은 경우 catch 절에서 잡아도 처리할 수 없음.
7. 읽고 무시하지 말것
- 메서드에서 절대 예외가 발생할 수 없는 경우에도 메서드 시그니처를 바꾸지 않으면 나중에 예외가 발생하도록 패치가 될 수도 있음
- 예외가 발생하지 않는다면 시그니처에서 예외를 제거하는 게 옳음(안 될 경우 로그라도 찍어서 상황을 모니터링할 수 있게 해야 함).
- 메소드 시그니처(method signature)란 Java 같은 프로그래밍 언어에서 메소드를 정의할 때 사용하는 표현식
8. 로그 찍고 다시 던지지 마라
- 예외를 처리할 때 습관적으로 로그를 남기고, 다시 상위 메서드로 발생한 예외를 던지는 경우 로그가 너무 자주 찍혀서 가독성을 훼손한다
- 상위로 Throw할 경우 로그를 찍지 말아야 한다. 그 상황에 대한 컨텍스트를 남기고 싶을 경우 예외를 래핑해서 새로운 클래스로 만들고 컨텍스트에 대한 정보를 담아 상위로 던져야 한다.
9. 예외를 래핑할 경우 Cause 예외를 담아서 던져라
- 예외를 래핑해서 사용할 경우 원래 발생했던 예외를 생성자에 넘겨줘야 스택 정보와 메시지, 컨텍스트 정보 등이 상위로 전달된다. 이런 정보가 사라지면 디버깅이 힘들어진다.
예시
try {
method();
} catch (NumberFormatException e) {
throw new MyException("New Message", e);
}
try-with-resources 구문
- 자동으로 리소스를 관리할 수 있게 해주는 구문, 리소스를 사용한 후에 명시적으로 닫아줄 필요가 없음
- java.lang.AutoCloseable 인터페이스나 java.io.Closeable 인터페이스를 구현한 객체를 자동으로 닫아줌
- 파일, 네트워크 연결, 데이터베이스 연결 등을 안전하고 쉽게 다룰 수 있게 해줌
try-with-resources 기본 구문
try (리소스 선언) {
// 리소스를 사용하는 코드
} catch (예외 타입 변수명) {
// 예외 처리 코드
}
- 리소스 선언 : 하나 이상의 리소스를 선언할 수 있으며, 선언된 리소스는 try 블록이 끝나면 자동으로 닫힘
- 예외 처리 코드 : try 블록 내에서 발생할 수 있는 예외를 처리
예제 코드
import java.io.IOException; // 입출력간 예외 처리
import java.nio.file.Files;
import java.nio.file.Paths;
public class TryWithResourcesExample {
public static void main(String[] args) {
// 파일 경로 지정
String filePath = "example.txt";
// try-with-resources 구문을 사용하여 파일을 자동으로 닫음
try (java.io.BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
} // try 블록 종료 시 BufferedReader 객체는 자동으로 닫힘
} catch (IOException e) { // 파일 읽기 작업 중 발생할 수 있는 예외를 처리
e.printStackTrace(); // 오류의 원인을 출력
}
}
}
//Files.newBufferedReader(Paths.get(filePath)) filePath 경로에 있는 파일로부터 텍스트를 읽기 위한 BufferedReader를 생성
//readline 메서드는 파일의 내용을 한 줄씩 읽음
- Files.newBufferedReader 메소드를 사용해 BufferedReader 객체를 생성하고, 파일의 내용을 줄 단위로 읽어 콘솔에 출력합니다. try-with-resources 구문 덕분에 BufferedReader는 자동으로 닫히므로, 개발자는 리소스를 명시적으로 관리하는 데 신경 쓸 필요가 없습니다.
연결된 예외 예제
public class ChainedExceptionEx {
//execute 메서드 - install 메서드 호출, 발생할 수 이쓴 예외 처리
// 예외 발생 시 stackTrace 출력
public static void execute() {
try {
install();
} catch (InstallException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// install 메서드 - 설치 프로세스를 시뮬레이션
// startInstall 과 copyFiles 메서드를 순서대로 호출
// SpaceException, MemoryException 발생 시 이를 InstallException으로 변환 후
// initCause 메서드를 사용해 원인 예외로 설정하고 변경 된 예외를 다시 던짐 (연쇄 예외 처리의 예)
// finally 블록에서는 설치 프로세스에 사용된 임시 파일을 삭제하는 deleteTempFiles 메서드 호출
// 예외의 발생 여부와 관계 없이 정리 작업 보장(finally 블록이라)
private static void install() throws InstallException {
try {
startInstall();
copyFiles();
} catch (SpaceException se) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(se);
throw ie;
} catch (MemoryException me) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(me);
throw ie;
} finally {
deleteTempFiles(); // 설치에 사용된 임시 파일 삭제
}
}
// startInstall 메서드
// 설치를 시작하는 메서드, 설치에 필요한 공간과 메모리가 충분한지 검사
// 충분한 공간이 없으면 SpaceException, 충분한 메모리가 없으면 MemoryException 던짐
// enoughMemory가 false를 반환, MemoryException 발생
private static void startInstall() throws SpaceException, MemoryException {
if (!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족해요.");
}
if (!enoughMemory()) {
throw new MemoryException("메모리가 부족해요.");
//throw new RuntimeException(new MemoryException("메모리가 부족해요."))
}
}
// methods
// copyFiles 메소드는 파일 복사를 시뮬레이션
private static void copyFiles() {
System.out.println("파일을 복사해요");
}
// deleteTempFiles 메소드는 임시 파일을 삭제하는 작업을 시뮬레이션
// 예외 발생 여부와 관계없이 실행
private static void deleteTempFiles() {
System.out.println("임시 파일을 삭제해요");
}
// enoughSpace는 항상 true를 반환. 즉, 충분한 공간이 있다고 가정
private static boolean enoughSpace() {
return true;
}
// enoughMemory는 false를 반환. 즉, 충분한 메모리가 없다고 가정
private static boolean enoughMemory() {
return false;
}
// 사용자 정의 예외 클래스
// InstallException은 설치 과정 중 발생하는 일반적인 예외를 나타냄
static class InstallException extends Exception {
InstallException(String message) {
super(message);
}
}
static class SpaceException extends Exception {
SpaceException(String message) {
super(message);
}
}
static class MemoryException extends Exception {
MemoryException(String message) {
super(message);
}
}
}
'항해 99 > Java' 카테고리의 다른 글
WIL - 2 (0) | 2024.02.18 |
---|---|
페어 프로그래밍 - 코딩 테스트 5 (1) | 2024.02.17 |
페어 프로그래밍 - 코딩 테스트 4 (1) | 2024.02.16 |
페어 프로그래밍 - 코딩 테스트 3 (0) | 2024.02.15 |
페어 프로그래밍 - 코딩 테스트 2 (0) | 2024.02.14 |