예외는 try - catch - finally
구문을 통해 핸들링할 수 있다.
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
try {
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
System.out.println(e.getMessage());
} finally {
System.out.println("우리는 방금 예외를 handling 했습니다!");
}
}
}
예외 계층은 다음과 같다. 예전에 다른 강의에서 배웠을 때, 가장 상위 예외는 Throwable이지만 예외를 잡을 때 상위 예외를 잡으면 그 하위 예외까지 잡게 된다고 했다. 따라서 애플리케이션 로직에서 Exception뿐만 아니라 Error 예외까지 함께 잡게 되기 때문에 Exception 부터 필요한 예외로 생각하고 잡아야 한다.
제네릭(Generic)은 '일반적인', '범용적인'이라는 의미를 가지고 있다. 특정 타입에 속하지 않고 일반적으로 사용할 수 있다는 뜻이다.제네릭을 이용하면 코드 재사용성과 타입 안정성을 모두 충족시킬 수 있다. 다음과 같이 <>(다이아몬드)를 사용한 클래스를 제네릭 클래스라고 한다.
<T>
와 같이 선언한다. public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
이렇게 만든 제네릭 클래스는 다음과 같이 원하는 타입을 넣어 객체를 생성해 사용한다.
GenericBox<Integer> integerBox = new GenericBox<Integer>();
GenericBox<String> stringBox = new GenericBox<String>();
GenericBox<Double> doubleBox = new GenericBox<Double>();
참고로 생성하는 제네릭 타입은 생략이 가능하다. 과거 코드와의 하위 호환 문제로 <>를 지정하지 않는 raw type을 지원하기도 하지만, 제네릭 타입을 사용할 때는 반드시 <>를 사용해서 사용시점에 타입을 지정하는 것이 좋다.
GenericBox<Integer> integerBox = new GenericBox<>();
✔️ 제네릭 관련 용어 정리
GenericBox<T>
에서 T
를 의미함.GenericBox<Integer>
에서 Integer
를 의미함.LV.3 계산기 구현을 시작했는데, enum 타입으로 연산자 타입에 대한 정보를 관리해야한다는 요구사항을 보고 고민에 빠졌다.
처음에는 이런 식으로 해야하는 건가? 했는데, 이미 Lv.2 계산기를 할 때 switch문에서 잘못된 연산자를 입력 받지 못하게 했기 때문에 enum을 왜 써야하는지 의문이었다. 사칙연산을 그대로 enum에 집어넣을 수 있다면 몰라도...?
public enum OperatorType {
ADD("+"),
SUBTRACT("-"),
MULTIPLY("*"),
DIVIDE("/")
}
알고보니 정말 집어넣을 수 있었다. enum 상수마다 동작을 다르게 정의해야 할 때 익명 클래스를 사용해 각 상수별로 고유한 메서드 구현을 할 수 있다.
public enum Operation {
ADD {
@Override
public double operate(double x, double y) {
return x + y;
}
},
SUBTRACT {
@Override
public double operate(double x, double y) {
return x - y;
}
},
MULTIPLY {
@Override
public double operate(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
public double operate(double x, double y) {
if (y == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다.");
}
return x / y;
}
};
// 추상 메서드 선언
public abstract double operate(double x, double y);
}
혹은 다음과 같이 람다식을 사용해 더 간결하게 작성할 수도 있다. 간단한 로직이므로 람다식으로 표현하는 것이 더 적합해보인다. 람다식만 적었다고 되는 것이 아니라, 람다식을 받는 생성자와 필드를 정의해줘야 한다.
public enum OperatorType {
ADD((a, b) -> a + b),
SUBTRACT((a, b) -> a - b),
MULTIPLY((a, b) -> a * b),
DIVIDE((a, b) -> {
if (b == 0) throw new ArithmeticException("0으로 나눌 수 없습니다.");
return a / b;
});
private final BiFunction<Double, Double, Double> operation;
OperatorType(BiFunction<Double, Double, Double> operation) {
this.operation = operation;
}
public double apply(double a, double b) {
return operation.apply(a, b);
}
}
여기서 BiFunction
은 두 개의 입력값(T, U)을 받아서 하나의 출력값(R)을 반환하는 함수형 인터페이스이다. OperatorType
열거형은 각 연산(람다식)을 생성자에 전달받아 operation
필드에 저장한다. apply()
메서드는 저장된 operation 필드(람다식)를 호출하여 실제 연산을 수행하는 역할을 한다.
operation.apply(a, b)
에서 볼 수 있듯이 BiFunction
인터페이스에 apply()
메서드가 있다. 찾아보니 다음과 같이 먼저 BiFunction
객체에 특정 로직을 수행하는 람다 표현식을 할당한 후, apply()
메서드로 두 개의 매개변수를 전달받아 특정 작업을 수행 후 값을 반환하게 된다.
BiFunction<Integer, Integer, String> biFunctionAdd =
(num1, num2) -> Integer.toString(num1 + num2);
System.out.println("100 + 50 = " + biFunctionAdd.apply(100, 50));
오늘은 Lv.3 계산기를 시작만 해서 내일 잘 완성할 수 있을지 모르겠다. 하지만 Lv.1, Lv.2 계산기는 너무 자연스럽게 만들어진 느낌이었다면, Lv.3는 좀 더 생각을 많이 하고 검색도 많이 해본 후에 시작해서 말 그대로 도전 과제의 느낌이었다. 개인적으로는 이렇게 적당히 도전적인 과제가 더 재밌고 공부할 것도 많아 시간이 더 잘 가는 것 같다. 내일은 과제를 모두 끝내고, 미뤘던 자바 강의 정리에 집중해야겠다.