열거형(enum; enumerated type)은 서로 연관된 상수들의 집합을 의미한다.
열거형을 선언하는 법은 간단하다.
enum 열거형이름 { 상수명1, 상수명2, 상수명3, ...}
사계절을 예로 들어 열거형으로 선언해보겠다.
enum Seasons {
SPRING, // 정수값 0 할당
SUMMER, // 정수값 1 할당
FALL, // 정수값 2 할당
WINTER // 정수값 3 할당
}
참고로 상수는 관례적으로 대문자로 작성한다.
또한 각각의 열거 상수들은 객체이기 때문에, 위의 예시에서 Seasons
라는 이름의 열거형은 SPRING, SUMMER, FALL, WINTER
는 총 네 개의 열거 객체를 포함하고 있다고 말할 수 있다.
각각의 상수들에는 따로 값을 지정해주지 않아도 자동적으로 0부터 시작하는 정수값이 할당되어 각각의 상수를 가리키게 된다.
이렇게 선언한 열거형을 어떻게 사용할까?
enum Seasons { SPRING, SUMMER, FALL, WINTER}
public class EnumExample {
public static void main(String[] args) {
System.out.println(Seasons.SPRING); // SPRING
}
}
열거형에 선언된 상수에 접근하는 방법은 열거형이름.상수명
을 통해 가능하다. 클래스에서 static 변수를 참조하는 것과 동일하다고 할 수 있다.
Seasons의 참조변수을 만들어 값을 할당하는 것도 가능하다.
enum에서 사용할 수 있는 메서드들이 있다.
리턴 타입 | 메서드(매개변수) | 설명 |
---|---|---|
String | name() | 열거 객체가 가지고 있는 문자열을 리턴하며, 리턴되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일하다. |
int | ordinal() | 열거 객체의 순번(0부터 시작)을 리턴한다. |
int | compareTo(비교값) | 주어진 매개값과 비교해서 순번 차이를 리턴한다. |
열거 타입 | valueOf(String name) | 주어진 문자열의 열거 객체를 리턴한다. |
열거 배열 | values() | 모든 열거 객체들을 배열로 리턴한다. |
enum Level {
LOW, // 0
MEDIUM, // 1
HIGH // 2
}
public class EnumTest {
public static void main(String[] args) {
Level level = Level.MEDIUM;
Level[] allLevels = Level.values();
for(Level x : allLevels) {
System.out.printf("%s=%d%n", x.name(), x.ordinal());
}
Level findLevel = Level.valueOf("LOW");
System.out.println(findLevel);
System.out.println(Level.LOW == Level.valueOf("LOW"));
switch(level) {
case LOW:
System.out.println("낮은 레벨");
break;
case MEDIUM:
System.out.println("중간 레벨");
break;
case HIGH:
System.out.println("높은 레벨");
break;
}
}
}
//출력값
LOW=0
MEDIUM=1
HIGH=2
LOW
true
중간 레벨
위의 예시는 열거형 메서드를 활용한 예시이다.
먼저 values()
메서드는 컴파일러가 자동적으로 모든 열거형에 추가해주는 메서드로 Level
에 정의된 모든 상수를 배열로 반환했다.
이렇게 받은 배열을 향상된 for문과 열거형의 최상위 클래스로부터 확장된 name()
과 ordinal()
을 사용해 각각 이름과 순서를 출력값으로 반환하고 있다.
마지막으로 valueOf
메서드를 활용해 지정된 열거형에서 이름과 일치하는 열거형 상수를 반환하고, 반환된 상수가 의도했던 상수와 일치하는 지 여부를 boolean
값으로 확인해주고 있다.
제네릭이란 클래스나 메서드의 코드를 작성할 때, 타입을 구체적으로 지정하는 것이 아니라, 추후에 지정할 수 있도록 일반화해두는 것을 의미한다.
즉, 작성한 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둔 것을 말한다.
class Basket<T> {
private T item;
public Basket(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
위의 코드에서, T
를 타입 매개변수라고 하며, <T>
와 같이 꺽쇠 안에 넣어 클래스 이름 옆에 작성해줌으로써 클래스 내부에서 사용할 타입 매변수를 선언할 수 있다.
T
이외에도, K
, V
, E
, N
, R
도 자주 사용되는데 각각 Type, Key, Value, Element, Number, Result의 첫 글자를 따온 것이다.
제네릭 클래스를 정의할 때 주의할 점이 있는데 클래스 변수에 타입 배개변수를 사용할 수 없다는 점이다.
class Basket<T> {
private T item1; // O
static T item2; // X
}
그 이유는 클래스 변수는 모든 인스턴스가 공유하는 변수인데, 만약, 클래스 변수에 타입 매개변수를 사용할 수 있다면 클래스 변수의 타입이 인스턴스 별로 달라지게 된다.
즉, 클래스 변수에 타입 매개변수를 사용할 수 있다면, Basket<String>
으로 만든 인스턴스와, Basket<Integer>
로 만든 인스턴스가 공유하는 클래스 변수의 타입이 서로 달라지게 되어, 클래스 변수를 통해 같은 변수를 공유하는 것이 아니게 된다. 따라서 static
이 붙은 변수 또는 메서드에는 타입 매개변수를 사용할 수 없다.
제네릭 클래스를 사용, 즉, 인스턴스화 할 때에는 의도하고자 하는 타입을 아래와 같이 지정해주어야 한다.
단, 타입 매개변수에 치환될 타입으로 기본타입을 지정할 수 없다.
Basket<String> basket1 = new Basket<String>("Hello");
Basket<Integer> basket2 = new Basket<Integer>(10);
Basket<Double> basket3 = new Basket<Double>(3.14);
제네릭 클래스에 다형성도 적용할 수 있다.
class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }
class Basket<T> {
private T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
class Main {
public static void main(String[] args) {
Basket<Flower> flowerBasket = new Basket<>();
flowerBasket.setItem(new Rose()); // 다형성 적용
flowerBasket.setItem(new RosePasta()); // 에러
}
}
new Rose()
를 통해 생성된 인스턴스는 Rose
타입이며, Rose
클래스는 Flower
클래스를 상속받고 있으므로, Basket<Flower>
의 item
에 할당될 수 있다. Basket<Flower>
은 결국 item
의 타입을 Flower
로 지정하는 것이고, Flower
클래스는 Rose
클래스의 상위 클래스이기 때문이다.
반면, new RosePasta()
를 통해 생성된 인스턴스는 RosePasta
타입이며, RosePasta
클래스는 Flower
클래스와 아무런 관계가 없다. 따라서, flowerBasket
의 item
에 할당될 수 없다.
제네릭 메서드의 타입변수 선언은 반환타입 앞에서 이루어지며, 해당 메서드 내에서만 선언한 타입 매개변수를 사용할 수 있다.
class Basket<T> { // 1 : 여기에서 선언한 타입 매개변수 T와
...
public <T> void add(T element) { // 2 : 여기에서 선언한 타입 매개변수 T는 서로 다른 것입니다.
...
}
}
제네릭 메서드의 타입 매개변수는 제네릭 클래스의 타입 매개변수와 별개의 것이다. 즉, 아래와 같이 동일하게 T
라는 타입 매개변수명을 사용한다 하더라도, 같은 알파벳 문자를 이름으로 사용하는 것일 뿐, 서로 다른 타입 매개변수로 간주된다.
이는 타입이 지정되는 시점이 서로 다르기 때문이다. 즉, 클래스명 옆에서 선언한 타입 매개변수는 클래스가 인스턴스화될 때 타입이 지정된다.
그러나, 제네릭 메서드의 타입 지정은 메서드가 호출될 때 이루어진다. 제네릭 메서드를 호출할 때에는 아래와 같이 호출하며, 이 때 제네릭 메서드에서 선언한 타입 매개변수의 구체적인 타입이 지정된다.
Basket<String> basket = new Bakset<>(); // 위 예제의 1의 T가 String으로 지정된다.
basket.<Integer>add(10); // 위 예제의 2의 T가 Integer로 지정된다.
basket.add(10); // 타입 지정을 생략할 수도 있다.
또한, 클래스 타입 매개변수와 달리 메서드 타입 매개변수는 static
메서드에서도 선언해 사용 가능하다.
class Basket {
...
static <T> int setPrice(T element) {
...
}
}
제네릭 메서드는 메서드가 호출되는 시점에서 제네릭 타입이 결정되므로, length()
와 같은 String
클래스의 메서드는 제네릭 메서드를 정의하는 시점에 사용할 수 없다.
하지만 모든 자바 클래스의 최상위 클래스인 Object
클래스의 매서드는 사용이 가능하다. equals()
, toString()
등이 Object
클래스의 메서드에 속한다.
와일드 카드란 어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미하며 기호 ?
로 와일드카드를 사용할 수 있다.
일반적으로 와일드카드는 extends
와 super
키워드를 조합하여 사용한다.
<? extends T>
<? super T>
<? extends T>
는 와일드카드에 상한 제한을 두는 거으로서, **T
와 T
를 상속받는 하위 클래스 타입만 타입 파라미터로 받을 수 있도록 지정한다.
반면, <? super T>
는 와일드카드에 하한 제한을 두는 것으로 T
와 T
의 상위 클래스만 타입 파라미터로 받도록 한다.**
참고로, extends
및 super
키워드와 조합하지 않은 와일드카드(<?>
)는 <? extends Object>
와 같다. 즉, 모든 클래스 타입은 Object 클래스를 상속받으므로, 모든 클래스 타입을 타입 파라미터로 받을 수 있음을 의미한다.
예외 처리(Exception Handling)란 예기치 않게 발생하는 에러에 대응할 수 있는 코드를 미리 사전에 작성하여 프로그램의 비정상적인 종료를 방지하고 정상적인 실행 상태를 유지하기 위한 동작이라고 할 수 있다.
자바의 예외 클래스는 일반 예외 클래스(Exception) 과 실행 예외 클래스(Runtime Exception)으로 나눌 수 있다.
런타임 시 발생하는 RuntimeException
클래스와 그 하위 클래스를 제외한 모든 Exception
클래스와 그 하위 클래스들을 가리킨다.
컴파일러가 코드 실행 전에 예외 처리 코드 여부를 검사한다고하여 checked 예외라고도 부른다.
주로 잘못된 클래스명(ClassNotFoundException)이나 데이터 형식(DataFormatException) 등 사용자편의 실수로 발생하는 경우가 많다.
앞서 언급한 런타임 시 발행하는 RuntimeException
클래스와 그 하위 클래스를 지칭한다.
컴파일러가 예최 처리 코드 여부를 검사하지 않는다는 의에서 unchecked 예외라고도 부른다.
주로 개발자의 실수에 의해 발생하는 경우가 많고, 자바 문법 요소와 관련이 있다.
예컨데, 클래스 간 형변화 오류(ClassCastException), 벗어난 배열 범위 지정(ArrayIndexOutOfBoundsException), 값이 null인 참조변수 사용(NullPointerException) 등이 있다.
자바에서 예외 처리는 try-catch
블럭을 통해 구현이 가능하다.
try
// 예외가 발생할 가능성이 있는 코드 삽입
}
catch (ExceptionType1 e1) {
// ExceptionType1 유형의 예외 발생 시 실행할 코드
}
catch (ExceptionType2 e2) {
// ExcpetionType2 유형의 예외 발생 시 실행할 코드
}
finally {
// finally 블럭은 옵셔널
// 예외 발생 여부와 상관없이 항상 실행
}
예외 정보를 얻는 방법은 getMessage()
, toString()
, printStackTrace()
메서드 등을 사용하면 된다.