[자바의 정석 기초편] 열거형(Enum), 애너테이션(Annotation)

JEREGIM·2023년 3월 9일
0

자바의 정석 기초편

목록 보기
17/23

📌열거형(Enum)

: 서로 관련된 상수들을 같이 묶어 놓은 것.

class Card {
	static final int CLOVER = 0;
    static final int HEART = 1;
    static final int DIAMOND = 2;
    static final int SPADE = 3;
    
    static final int TWO = 0;
    static final int THREE = 1;
    static final int FOUR = 2;
}

열거형 사용🔽

class Card {
	enum Kind {CLOVER, HEART, DIAMOND, SPADE} 
    enum Value {TWO, THREE, FOUR}
    
    final Kind kind;
    final Value value;
}
  • 값은 0부터 자동으로 매겨진다. CLOVER = 0, HEART = 1, DIAMOND = 2, SPADE = 3
if(Card.CLOVER == Card.TWO) // true
  • 결과는 true로 나오지만 의미상으로는 false가 나와야 맞는 조건이다.
    열거형 비교🔽
if(Card.Kind.CLOVER == Card.Value.TWO) // 컴파일 에러
  • 자바의 열거형은 값과 타입을 모두 체크하기 때문에 타입이 다르다고 컴파일 에러가 난다. 이러한 이유 때문에 자바는 타입에 안전한 열거형을 제공한다고 한다.

열거형의 정의와 사용

정의
enum 열거형 이름 {상수명1, 상수명2 ...}

예시) enum Direction {EAST, SOUTH, WEST, NORTH}

열거형 타입의 변수를 선언하고 사용

class Unit {
	Direction dir = Direction.EAST;
    ...
}

열거형 상수의 비교는 ==compareTo() 사용 가능

if (dir == Direction.EAST) { // OK
	...
} else if (dir < Direction.WEST) { // 에러
	...
} else if (dir.compareTo(Direction.SOUTH) > 0 ) { // OK
	...
}
  • else if (dir < Direction.WEST) : 열거형 상수에는 비교연산자 사용 불가

열거형의 조상 - java.lang.Enum

모든 열거형이 상속 받는 메서드들

메서드설명
Class<E> getDeclaringClass()열거형의 Class 객체를 반환
String name()열거형 상수의 이름을 문자열로 반환
int ordinal()열거형 상수가 정의된 순서를 반환(0부터 시작)
T valueOf(Class<T> enumType, String name)지정된 열거형에서 name과 일치하는 열거형 상수를 반환

컴파일러가 자동으로 추가해주는 메서드

메서드설명
static E[] values()열거형 상수가 가지고 있는 모든 값들을 배열로 반환
static E valueOf(String name)열거형 상수 이름(name)을 반환

실습 예제

열거형 상수 정의 및 변수 선언

enum Direction {EAST, SOUTH, WEST, NORTH} // values = 0, 1, 2, 3

class Ex12_5 {
    public static void main(String[] args) {
        Direction d1 = Direction.EAST;
        Direction d2 = Direction.valueOf("WEST");
        Direction d3 = Enum.valueOf(Direction.class, "EAST");

        System.out.println("d1 = " + d1);
        System.out.println("d2 = " + d2);
        System.out.println("d3 = " + d3);
        ...

d1 = EAST
d2 = WEST
d3 = EAST

  • 값을 따로 지정해주지 않으면 자동으로 0에서부터 값이 할당된다.

  • Direction d1 = Direction.EAST; : 보통 이 방법을 제일 많이 쓴다. 나머지 2가지 방법도 기능은 똑같다.

열거형 상수 비교

		...
        System.out.println("d1 == d2 ? " + (d1 == d2));
        System.out.println("d1 == d3 ? " + (d1 == d3));
        System.out.println("d1.equals(d3) ? " + d1.equals(d3));
//		System.out.println("d2 > d3 ? "+ (d1 > d3)); // 컴파일 에러
        System.out.println("d1.compareTo(d3) ? " + (d1.compareTo(d3)));
        System.out.println("d1.compareTo(d2) ? " + (d1.compareTo(d2)));
        ...

d1 == d2 ? false
d1 == d3 ? true
d1.equals(d3) ? true
d1.compareTo(d3) ? 0
d1.compareTo(d2) ? -2

  • 열거형 상수들은 기본형이 아닌 하나하나가 객체이기 때문에 비교연산자 사용 불가

values() 메서드

		...
        Direction[] dArr = Direction.values();

        for (Direction d : dArr)
            System.out.printf("%s = %d%n", d.name(), d.ordinal());
        ...

EAST = 0
SOUTH = 1
WEST = 2
NORTH = 3

  • Direction.values() : 열거형의 모든 상수를 배열로 반환

  • for (Direction d : dArr) : for (Direction d : Direction.values()) 와 동일

  • d.name() : 열거형 상수의 이름 반환

  • d.ordinal() : 열거형 상수의 순서 반환(주의 : 값 아님)

열거형에 멤버 추가

enum Direction {
    EAST(1, "▶"), SOUTH(2, "▼"), WEST(3, "◀"), NORTH(4, "▲");

    private final int value;
    private final String symbol;

    Direction(int value, String symbol) {
        this.value = value;
        this.symbol = symbol;
    }

    public int getValue() {
        return value;
    }

    public String getSymbol() {
        return symbol;
    }
}    
  • EAST(1, "▶"), SOUTH(2, "▼"), WEST(3, "◀"), NORTH(4, "▲"); : 원하는 값을 괄호안에 적는다.

  • private final int value;, private final String symbol; : 괄호안에 들어갈 값을 저장할 인스턴스 변수를 추가해준다.

Direction(int value, String symbol) {
    this.value = value;
    this.symbol = symbol;
}
  • 추가한 iv값을 매개변수로 하는 생성자를 만들어준다.
  • 열거형의 생성자는 항상 묵시적으로 private 접근 제어자이기 때문에, 외부에서 객체 생성 불가
    • Direction d = new Direction(1, "▶"); : 에러. 외부에서 호출 불가
public int getValue() {
    return value;
}

public String getSymbol() {
    return symbol;
}
  • private으로 생성한 iv 값에 대한 getter을 public 제어자로 만들어준다.

열거형 상수 출력

public class EnumExample {
    public static void main(String[] args) {
        for (Direction dir : Direction.values()) {
            System.out.printf("%-5s : value : %d, symbol : %s, ordinal: %d%n",
                    dir.name(), dir.getValue(), dir.getSymbol(), dir.ordinal());
        }
    }
}

  • dir.getValue()dir.ordinal() 차이
    • dir.getValue()는 열거형 상수 괄호안에 들어간 값
    • dir.ordinal()는 열거형 상수들의 순서(0부터 시작)

📌애너테이션(Annotation)

: 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유요한 정보를 제공

표준 애너테이션

: Java에서 제공하는 애너테이션

애너테이션설명
@Override해당 메서드를 오버라이딩 하는 것이다.
@Deprecated앞으로 사용하지 않을 것을 권장한다.
@SuppressWarnings컴파일러 특정 경고메세지 차단
@SafeVarargs지네릭스 타입의 가변인자 사용
@FunctionalInterface함수형 인터페이스이다.
@Nativenative메서드에서 참조되는 상수 앞에 붙인다.

애너테이션 만들 때 사용하는 메타 애너테이션

애너테이션설명
@Taget*애너테이션 적용 가능 대상 지정
@Documented*해당 애너테이션 정보가 javadoc 작성 문자에 포함되게끔
@Inherited*" 자손 클래스 "에상속되도록
@Retention*애너테이션 유지 범위 지정
@Repeatable*애너테이션 반복 적용 허용

@Override

오버라이딩을 제대로 했는지 컴파일러가 체크하게 한다.

오버라이딩할 때 메서드 이름을 잘못 적는 실수를 예방

class Product {
	void operate() {}
}

class Tv extends Product {
	@Override
    void opearte() {} // 컴파일 에러
}
  • @Override 애너테이션을 안붙이면 철자 실수로 인해 내 작성 의도와 다르게 새로운 메서드를 작성한 것처럼 되서 컴파일은 정상적으로 작동한다.
    그러나 @Override 를 붙임으로써 작성한 메서드가 조상 클래스에 없는 클래스라고 컴파일러가 알려준다.

@Deprecated

앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.

class Parent {
	void parentMethod() { }
}

class Child extends Parent {
	@Override
	@Deprecated
	void parentMethod() { }
}

public class Ex12_7 {
	public static void main(String[] args) {
		Child child = new Child();
		child.parentMethod();
	}
}

  • cmd 에서 javac Ex12_7.java를 치면 deprecated API를 썼다는 경고문구가 뜬다.
  • javac -Xlint:deprecation Ex12_7.java로 더 자세한 경고 내용을 볼 수 있다.
    warning: [deprecation] parentMethod() in Child has been deprecated
    child.parentMethod();

@FunctionalInterface

함수형 인터페이스는 단 하나의 추상 메서드만 가질 수 있다. 이 애너테이션을 붙이면 컴파일러가 제대로 작성했는지 체크해준다.

@FunctionalInterface
interface Testable {
	void test();
    void test2(); // 컴파일 에러
}
  • @FunctionalInterface 이 애너테이션을 붙인 인터페이스가 2개 이상의 추상 메서드를 작성하면 에러가 난다.
  • 함수형 인터페이스는 하나의 추상 메서드만 가질 수 있다.

@SuppressWarnings

컴파일러의 경고 메시지가 나타나지 않게 억제

@SuppressWarnings("unchecked")
ArrayList list = new ArrayList();
list.add(1);
  • 괄호 안에 억제하고자 하는 경고의 종류를 문자열로 지정한다.

  • @SuppressWarnings("unchecked") : 제네릭스와 관련된 경고를 억제

  • 경고가 실행에 영향을 주진 않지만 내가 이 경고를 보고 확인했다는 의미에서 이 애너테이션을 붙인다.

둘 이상의 경고를 동시에 억제하는 법

@SuppressWarnings({"deprecation", "unchecked", "varargs"})

메타 애너테이션

애너테이션을 정의할 때 사용하는 애터네이션. java.lang.annotation 패키지에 포함

@Target

애너테이션을 정의할 때, 적용 대상 지정에 사용된다.

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
  • @SuppressWarnings 애너테이션의 적용 대상 타입 설정해준다.
대상 타입설명
ANNOTAION_TYPE애너테이션
CONSTRUCTOR생성자
FIELD필드 (iv, cv)
LOCAL_VARIABLE지역변수
METHOD메서드
PACKAGE패키지
PARAMETER매개변수
TYPE타입(class, interface)
🔽 JDK1.8 이상부터 가능 🔽ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
TYPE_PARAMETER타입 매개변수
TYPE_USE타입이 사용되는 모든 곳

@Retention

애너테이션이 유지(retention)되는 기간을 정하는데 사용

유지 기준의미
SOURCE소스파일에만 존재. 클래스 파일에 X(애너테이션 정보가 클래스 파일에 남아있지 않음.)
CLASS(기본값)클래스 파일에 존재 -> 실행시 사용 불가
RUNTIME클래스 파일에 존재 -> 실행시 사용 가능
  • CLASS는 잘 쓰이지 않고 SOURCERUNTIME이 자주 사용된다.

컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE 이다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
  • @Override 는 컴파일러가 오버라이딩 체크하고 끝이기 때문에 실행 시에 필요가 없다. 따라서 유지 정책이 클래스 파일에 존재하지 않는 SOURCE 이다.

실행 시 사용 가능한 애너테이션의 유지 정책은 RUNTIME 이다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
  • RUNTIME 유지 정책은 실행 시에만 존재하는게 아니라 컴파일 타임부터 실행 시까지 유지되는 것이다.

@Documented, @Inherited

@Documented : javadoc으로 작성한 문서에 포함시킬 때 붙인다.

@Inherited : 애너테이션을 자손 클래스에 상속하고자할 때 붙인다.

@Inherited
@interface MyAnno{}

@MyAnno
class Parent{}

class Child extends Parent{} // Parent클래스에 붙어있던 MyAnno가 동일하게 붙은 것으로 인식
  • Child 클래스에는 @MyAnno이 붙어있지 않지만 @Inherited 으로 인해 조상의 애너테이션을 상속받아 @MyAnno이 붙어있는 것으로 인식된다.

@Repeatable

반복해서 붙일 수 있는 애너테이션을 정의할 때 사용

// @ToDo를 @Repeatable으로 지정
@Repeatable(ToDos.class) 
@interface ToDo {
	String value();
}
// 여러번 반복해서 붙일 수 있다.
@ToDo("update codes")
@ToDo("override inherited methods")
class MyClass {
	...
}
  • @Repeatable이 붙은 애너테이션으 반복해서 붙일 수 있다.
// @ToDo를 하나로 묶을 컨테이너 애너테이션
@interface ToDos {
	ToDo[] value(); // 반드시 value
}
  • @Repeatable로 반복해서 붙일 수 있도록 정의한 애너테이션을 하나로 묶을 컨테이너 애너테이션도 정의해줘야 한다.

애너테이션 타입 정의하기

@interface 애너테이션 이름 {
	타입 요소이름();
    ...
}

애너테이션의 메서드는 추상 메서드이며, 애너테이션을 적용할 때 지정(순서X)

enum TestType { FIRST, FINAL }

@interface DateTime {
	String yymmdd() ;
    String hhmmss() ;
}

@interface Test {
	int count(); // 정수
    String testedBy() ; // 문자열
    String[] testTools() ; // 문자열 배열
    TestType testType(); // Enum
	DateTime testDate(); // 다른 애너테이션 
}
  • 애너테이션의 추상 메서드 타입으로 enum이 올 수 있고, 다른 애너테이션이 올 수 있다.
@Test(
	count = 3,
    testedBy = "Jung",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST 
    testDate = @DateTime(yymmdd="", hhmmss="")
)
public class NewTest {
	... // Test 애너테이션 사용가능
}
  • 요소의 타입과 이름에 맞게 값을 지정해주고 순서는 상관 없다. 다만, 모든 요소에 대해 값을 적용해줘야 한다.

애너테이션의 요소

적용 시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null 제외)

@interface Test {
	int count() default 1;
}

@Test
public class NewTest {
	... // Test 애너테이션 사용가능
}
  • 원래는 @Test(count=1) 이런 식으로 애너테이션의 정의된 요소의 값을 전부 정의해줘야 한다. 하지만 애너테이션을 생성할 때 default 값을 줬다면 @Test 이런 식으로만 써줘도 된다.

요소가 하나이고 이름이 value일 때는 요소의 이름을 생략할 수 있다.

@interface Test {
	String value();
}

@Test("passed")
public class NewTest {
	... // Test 애너테이션 사용가능
}
  • 원래는 @Test(value="passed") 라고 적어줘야 하지만 요소가 하나이고 그 요소의 이름이 value일 때는 요소의 이름을 생략하고 값만 적어줘도 된다.

요소의 타입이 배열인 경우, 중괄호{}를 사용해야 한다.

@interface Test {
	String[] testTools();
}

@Test(testTools={"JUnit", "Auto"})
public class NewTest {
	... // Test 애너테이션 사용가능
}
  • @Test(testTools={"JUnit", "Auto"}) : 배열의 값을 적어줄 때 중괄호를 적어줘야 한다.

  • @Test(testTools="JUnit") : 배열의 값이 1개일 때는 중괄호 생략 가능

  • @Test(testTools={}) : 배열에 값이 없을 때는 반드시 빈 중괄호{}를 적어줘야 한다.

모든 애너테이션의 조상

Annotation 인터페이스는 모든 애너테이션의 조상이지만 상속은 불가하다.

package java.lang.annotation;

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();

    Class<? extends Annotation> annotationType();
}
  • Annotation 인터페이스는 4개의 추상 메서드를 가지고 있고 모든 애너테이션은 구현하지 않아도 이 4개의 추상 메서드를 사용할 수 있다.
  • Class<? extends Annotation> annotationType(); : 애너테이션의 타입을 반환

마커 애너테이션

요소가 하나도 정의되지 않은 애너테이션

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} // 정의된 요소가 하나도 없다.
  • @Override : 대표적인 마커 애너테이션

애너테이션 요소의 규칙

애너테이션의 요소를 선언할 때 지켜야할 규칙들

  • 요소의 타입은 기본형, String, enum, 애너테이션, Class(설계도 객체)만 허용된다.

  • 괄호()안에 매개변수를 선언할 수 없다.

  • 예외를 선언할 수 없다.

  • 요소를 타입 매개변수로 정의할 수 없다.

0개의 댓글