[Java] Enum, 제네릭, 예외 처리

Nakjoo·2023년 1월 4일
0

[SEB_BE_43]

목록 보기
13/29

1. Enum

열거형(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에서 사용할 수 있는 메서드들이 있다.

리턴 타입메서드(매개변수)설명
Stringname()열거 객체가 가지고 있는 문자열을 리턴하며, 리턴되는 문자열은 열거타입을 정의할 때 사용한 상수 이름과 동일하다.
intordinal()열거 객체의 순번(0부터 시작)을 리턴한다.
intcompareTo(비교값)주어진 매개값과 비교해서 순번 차이를 리턴한다.
열거 타입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 값으로 확인해주고 있다.

2. 제네릭

제네릭이란 클래스나 메서드의 코드를 작성할 때, 타입을 구체적으로 지정하는 것이 아니라, 추후에 지정할 수 있도록 일반화해두는 것을 의미한다.

즉, 작성한 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둔 것을 말한다.

2.1. 제네릭 클래스

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 클래스와 아무런 관계가 없다. 따라서, flowerBasketitem에 할당될 수 없다.

2.2. 제네릭 메서드

제네릭 메서드의 타입변수 선언은 반환타입 앞에서 이루어지며, 해당 메서드 내에서만 선언한 타입 매개변수를 사용할 수 있다.

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 클래스의 메서드에 속한다.

2.3. 와일드카드

와일드 카드란 어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미하며 기호 ?로 와일드카드를 사용할 수 있다.

일반적으로 와일드카드는 extendssuper 키워드를 조합하여 사용한다.

<? extends T>
<? super T>

<? extends T>는 와일드카드에 상한 제한을 두는 거으로서, **TT를 상속받는 하위 클래스 타입만 타입 파라미터로 받을 수 있도록 지정한다.
반면, <? super T>는 와일드카드에
하한 제한을 두는 것으로 TT의 상위 클래스만 타입 파라미터로 받도록 한다.**

참고로, extendssuper 키워드와 조합하지 않은 와일드카드(<?>)는 <? extends Object>와 같다. 즉, 모든 클래스 타입은 Object 클래스를 상속받으므로, 모든 클래스 타입을 타입 파라미터로 받을 수 있음을 의미한다.

3. 예외 처리

예외 처리(Exception Handling)란 예기치 않게 발생하는 에러에 대응할 수 있는 코드를 미리 사전에 작성하여 프로그램의 비정상적인 종료를 방지하고 정상적인 실행 상태를 유지하기 위한 동작이라고 할 수 있다.

자바의 예외 클래스는 일반 예외 클래스(Exception)실행 예외 클래스(Runtime Exception)으로 나눌 수 있다.

3.1. 일반 예외 클래스

런타임 시 발생하는 RuntimeException 클래스와 그 하위 클래스를 제외한 모든 Exception 클래스와 그 하위 클래스들을 가리킨다.

컴파일러가 코드 실행 전에 예외 처리 코드 여부를 검사한다고하여 checked 예외라고도 부른다.

주로 잘못된 클래스명(ClassNotFoundException)이나 데이터 형식(DataFormatException) 등 사용자편의 실수로 발생하는 경우가 많다.

3.2. 실행 예외 클래스

앞서 언급한 런타임 시 발행하는 RuntimeException 클래스와 그 하위 클래스를 지칭한다.

컴파일러가 예최 처리 코드 여부를 검사하지 않는다는 의에서 unchecked 예외라고도 부른다.

주로 개발자의 실수에 의해 발생하는 경우가 많고, 자바 문법 요소와 관련이 있다.

예컨데, 클래스 간 형변화 오류(ClassCastException), 벗어난 배열 범위 지정(ArrayIndexOutOfBoundsException), 값이 null인 참조변수 사용(NullPointerException) 등이 있다.

3.3. try-catch문

자바에서 예외 처리는 try-catch 블럭을 통해 구현이 가능하다.

try 
	// 예외가 발생할 가능성이 있는 코드 삽입
}
catch (ExceptionType1 e1) {
	// ExceptionType1 유형의 예외 발생 시 실행할 코드
}
catch (ExceptionType2 e2) {
	// ExcpetionType2 유형의 예외 발생 시 실행할 코드
}
finally {
	// finally 블럭은 옵셔널
    // 예외 발생 여부와 상관없이 항상 실행
}

예외 정보를 얻는 방법은 getMessage(), toString(), printStackTrace() 메서드 등을 사용하면 된다.

0개의 댓글