C++에서 배우는 자바-열거형과 애너테이션

열거형

C++에서 주로 방향 등을 나타낼 때 쓰곤 했던 enum이다. 거의 쓰지 않은 기능이라 잘 몰라서 그런진 몰라도 java의 enum은 C++의 그것과 달리 여러 기능이 추가되면서 더 강력하고 더 복잡해진 느낌이다. 하나씩 알아보도록 하자.

열거형의 정의와 사용

기본적인 사용법 자체는 크게 다르지 않다.

enum Direction={East, South, North, West}
enum Card={Clover, Spade}

위와 같이 정의하고 사용할 때엔 Direction.East와 같이 사용한다. 각각 기본값으로 0부터 시작하여 순서대로 0,1,2,3을 가지게 되고, C++과는 달리 type-safe하기 때문에 서로 타입이 다르면 같은 값을 가져도 비교할 수 없다.
예를 들어 if(Direction.East==Card.Clover) 와 같이 작성하면 C++에선 값이 같기 때문에 True를 도출하였지만, java에서는 타입이 다르기에 컴파일 에러를 도출한다.

열거형의 이해

사실 java에서 열거형은 간소화된 클래스라고 봐도 무방하다.
위의 East, South 등은 각각이 하나의 Direction 객체이고, 따라서 Direction 내부에 추상 메서드를 추가해준다면 각 객체가 모두 추상 메서드를 구현해 주어야한다.
이에 대해 인지하고서 열거형 상수의 값을 변경하고, 추상 메서드를 추가하는 예제를 작성해보자.

enum Direction={
    East(1), South(5), North(-1), West(10);
    private final int value; //int 값을 저장할 인스턴스 변수
    Direction(int value) {
        this.value=value;
    }
}

위와 같이 각 상수의 값을 불연속적인 임의의 값으로 변경할 때엔 인스턴스 변수와 생성자를 꼭 작성해주어야 한다. Direction이 간소화된 클래스라는 것을 인지하고 본다면 이해할 수 있는 코드일 것이다.
이제 추상 메서드를 하나 추가해보자.

enum Direction={
    East(1) { 
        int time(int speed) {return value/speed;}
    }, 
    South(5) { 
        int time(int speed) {return value/speed;}
    }, 
    North(-1) { 
        int time(int speed) {return value/speed;}
    }, 
    West(10) { 
        int time(int speed) {return value/speed;}
    };
    protected final int value; //protected로 해야 각 상수에서 접근 가능
    abstract int time(int speed);
    Direction(int value) {
        this.value=value;
    }
}  

위와 같이 추상 메서드를 추가한다면 각 상수에서 해당 추상 메서드를 구현해주어야 한다. 각 상수는 해당 열거형의 객체이기 때문이다.

애너테이션

애너테이션이란 객체이자 프로그래머가 컴파일러에게 보내는 메세지이다.
자신이 이러이러한 의도를 가지고 코드를 작성했다는 사실을 컴파일러에게 알림으로써 원래는 오류를 잡아내지 못하고 정상적으로 컴파일 될 코드를 에러를 도출하도록 할 수 있다. 크게 중요한 부분은 아닌 것 같고 이런 것이 있다 정도만 알아두고 넘어가면 될 것 같다.
java에서 제공하는 표준 애너테이션에는 일반 애너테이션과 임의로 새로운 애너테이션을 정의할 때 사용하는 메타 애너테이션이 있다. (*)가 붙은 것은 메타 애너테이션이다.

  • @Override : 컴파일러에게 오버라이딩 하는 메서드라는 것을 알린다.
  • @Deprecated : 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
  • @SuppressWarnings : 컴파일러의 특정 경고 메세지가 나타나지 않게 해준다.
  • @SafeVarags : 지네릭스 타입의 가변인자에 붙인다.
  • @Target(*) : 애너테이션이 적용 가능한 대상을 지정하는데 사용한다.
  • @Documented(*) : 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.
  • @Inherited(*) : 애너테이션이 자손 클래스에 상속되도록 한다.
  • @Retention(*) : 애너테이션이 유지되는 범위를 지정하는데 사용한다.
  • @Repeatable(*) : 애너테이션을 반복해서 적용할 수 있도록 한다.

이것들 중 중요한 것 몇 가지만 알아보자.

@Override

메서드 윗 줄에 붙여 사용한다. 간혹 메서드를 오버라이딩 할 때 오타가 나 메서드의 이름을 부모 메서드랑 다르게 적는 경우가 있다. 이럴 경우 컴파일러는 다른 메서드로 판단하여 문제없이 컴파일 된다. 하지만

@Override  
void parantMethod() {}

위와 같이 작성해줄 경우 컴파일러가 같은 이름의 메서드가 조상에 있는지 확인한 후 없다면 에러 메세지를 출력해준다.

@Deprecated

java에는 여러 이유로 다른 기능으로 대체되었지만 구버전 코드들과의 호환을 위해 기능을 삭제하지 않은 여러 코드들이 있다.
Vector, HashTable 등이 있는데 이러한 기능들을 java API에서 확인해보면 모두 @Deprecated 애너테이션이 붙어있다. 이 애너테이션은 이후 사용이 권장되지 않는 메서드들에 붙여주면 컴파일 시 “Note: Recompile with -Xlint:deprecation for details.”라는 메세지를 띄워주어 해당 소스코드가 Deprecated된 대상을 사용하고 있다는 사실을 알 수 있다.

@SuppressWarnings

구현을 하다보면 분명 문제 없이 코드를 작성했지만 구조상의 이유로 경고 메세지가 계속 출력될 때가 있다.
매번 경고 메세지를 보기에는 보기가 안좋으므로 경고를 출력하는 부분의 윗줄에 예를 들면

@SuppressWarnings("unchecked")  
ArrayList list=new ArrayList();
list.add(obj);

위와 같이 억제하고 싶은 경고메세지를 매개변수로 @SuppressWarnings 애너테이션을 작성해주면 해당 경고메세지의 출력을 막을 수 있다.

@Target

애너테이션을 새로 작성할 때 TYPE,FIELD 등 이 애너테이션이 어떤 것들을 대상으로 적용될 수 있는지를 기술하는데 사용한다.

@Retention

애너테이션이 유지되는 기간을 설정하는데 사용한다. SOURCE를 입력하여 소스파일에만 존재하고 클래스 파일엔 존재하지 않게 할 수도, CLASS를 입력하여 클래스 파일에 존재하지만 실행 시간엔 사용이 불가능하도록 할 수도 있다.

@Inherited

해당 애너테이션이 자손 클래스에도 상속이 되도록 한다. 해당 애너테이션을 조상 클래스에 붙이면 그것을 상속받은 자식 클래스에도 해당 애너테이션이 붙어있는 것으로 인식된다.

애너테이션을 새로 정의하는 부분은 지금은 딱히 필요가 없을 것 같아 넘어가도록 하겠다. 다음 포스팅에서는 java에서의 멀티 쓰레딩 환경에 대해 알아볼 것이다.