Post

Java Standard - 지네릭스 열거형 애너테이션

...

Java Standard - 지네릭스 열거형 애너테이션

지네릭스

지네릭스란?

  • 정의: 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능
  • 장점
    • 타입의 안정성을 제공

    타입 안정성의 의미

    • 의도하지 않은 타입의 객체가 저장되는 것을 막음
      • 저장된 객체를 꺼냈을 잘못 형변환되어 발생될 오류를 줄여줌
    • 형변환의 번거로움이 줄어듬 → 코드의 간결함

지네릭 클래스의 선언

지네릭스의 용어

Build source

  • 타입문자: 임의의 참조형 타입을 의미, 타입변수나 타입매개변수라고도 함, 위의 예제의 T 를 의미
    • T는 type에서 따온 것으로 주로 사용하는 기호는 T 이외에도 Element의 E, Key의 K, value의 V 상황에 맞는 것을 사용하면 됨
  • 지네릭 타입 호출: 타입 매개변수에 타입을 지정하는 것, 위의 예제의 Box<String> 을 의미
  • 매개변수화된 타입: 타입 매개변수에 지정되는 타입, 대입된 타입이라고도 함, 위의 예제의 String을 의미

지네릭스의 선언

1
2
3
4
5
6
7
8
9
class Box{
	Object item;
	void setItem(Object item){
		this.item = item;
	}
	**Object** getItem(){
		return this.item;
	}
}
1
2
3
4
5
6
7
8
9
class Box<T>{
	T item;
	void setItem(T item){
		this.item = item;
	}
	T getItem(){
		return this.item;
	}
}

지네릭의 선언

  • 위의 예제는 같은 내용의 코드를 지네릭을 적용했을때(오른쪽)와 적용하지 않은(왼쪽) 차이를 보여줌
  • 변경하기 위해서는 다음과 같이 클래스 옆에 <T> 를 붙임
  • 실제 생성할 때는 <T>에 실제 사용될 타입을 넣음
    • Box<String> 으로 선언시 T라고 작성된 부분에 String으로 들어간 것과 같음

지네릭과 이전 코드의 호환 지네릭으로 정의된 클래스를 이전 방식으로 생성하는 것을 허용함 이때는 매개변수화된 타입을 Object라고 간주하여 생성함

  • 다만 unchecked or unsafe operation 경고가 발생 → 매개변수화된 타입을 Object로 넣으면 사라짐

지네릭스의 제한

  • static: static 멤버에 타입 변수T를 사용할 수 없음 타입변수 T는 인스턴스 변수로 간주되기 때문, static멤버는 인스턴스 변수를 참조할 수 없기 때문 단, T가 아닌 특정 타입으로 지정되면 사용 가능
  • Array: 지네릭 타입의 배열을 생성하는 것은 허용되지 않음
    1
    2
    3
    4
    5
    6
    7
    
      class Box<T>{
      	T[] itemArr;// 허용 T타입의 배열을 위한 참조변수
      	T[] toArray(){
      		T[] tmp = new T[itemArr.length]; // 에러, 지네릭 배열 생성불가
      			return tmp;
      	}
      }
    

    new 연산자는 컴파일 시점에 타입 T가 무엇인지 정확하게 알아야 정상동작 하지만 컴파일 시점에 T에 어떤 타입이 들어올지 모르기 때문에 new연산자를 사용 할 수 없음 같은 이유로 instanceof 연산자도 사용할 수 없음

지네릭 클래스의 객체 생성과 사용

  • 생성 조건: 객체 생성시 참조변수와 생성자에 대입된 타입이 일치해야함

    1
    2
    3
    4
    
      Box<Apple> appleBox = new Box<Apple>(); // O
      Box<Apple> appleBox = new Box<Grape>(); // X
        
      Box<Apple> appleBox = new Box<>(); // O, 생략가능 1.7부터, 생략시 뒤에도 Apple로 간주
    

예제

  • 대입된 타입의 상속 관계 ( Fruit = 부모클래스, Apple = 자식 클래스 인 상황 )

    1
    2
    3
    4
    5
    6
    7
    
      Box<Apple> appleBox = new Box<Apple>(); // O
      Box<Fruit> fruitBox = new Box<Fruit>(); // O
      Box<Fruit> appleBox = new Box<Apple>(); // X ,타입이 상속 관계여도 불가능
          
      appleBox.add(new Apple()); // O
      appleBox.add(new Grape()); // X, 선언된 타입 외의 값은 추가 할 수 없음
      fruitBox.add(new Apple()); // O, 상속관계에 있는 타입이라면 추가할 수 있음
    
  • 클래스가 상속관계 ( Box = 부모클래스, FruitBox = 자식 클래스 인 상황 )

    1
    2
    
      Box<Apple> appleBox = new FruitBox<Apple>(); //O, 선언된 타입이 같다면 가능
      Box<Apple> appleBox = new FruitBox<Fruit>(); //X
    
  • 지네릭 표현은 컴파일 시에만 사용하고, 컴파일 이후에는 제거됨

제한된 지네릭 클래스

extends키워드 사용시 특정 타입의 자손들만 대입할 수 있도록 제한 할 수 있음

1
2
3
4
5
6
7
class FruitBox<T>{
	T fruit;
}

FruitBox<Toy> fruitBox;
fruitBox=new FruitBox<Toy>();
fruitBox.add(new Toy());
1
2
3
4
5
6
7
class FruitBox<T extends Fruit>{
	T fruit;
}

FruitBox<Toy> fruitBox; //오류
fruitBox=new FruitBox<Toy>();//오류
fruitBox.add(new Toy());//오류
  • 왼쪽: fruitBox지만 선언된 타입에 제한이 없어 fruit이 아니더라도 담을 수 있음
  • 오른쪽: 선언된 타입에 제한을 줌으로써 Fruit을 상속받은 타입으로만 선언 가능

제한에 사용되는 키워드 : extends , super

  • super 는 하한기준 , extends는 상한 기준을 제한함
  • 인터페이스 구현시에도 implement가 아닌 extends 사용
  • 인터페이스를 구현하며 동시에 상속받는 조건을 추가하고싶으면 & 로 연결

와일드 카드

  • 정의: 어떠한 타입도 되는 것을 의미, 기호 ?로 표현
    • 와일드 카드 자체로는 Object와 다른 것이 없어 extendssuper로 제한을 추가해 사용
      • <? extends T>: 상한 제한, T와 그 자손들만 가능
      • <? extends T>: 하한 제한, T와 그 조상들만 가능
      • <?>: 제한 없음, 모든 것 가능
    • 와일드 카드에는 & 를 사용할 수 없음

필요성

  • static메서드에서 T는 사용할 수 없음 → 특정 타입을 지정해, 메서드 오버로딩으로 해결
    • 문제: 지네릭 타입이 다른 것만으로는 오버로딩이 성립되지 않음, “메서드 중복 정의”가 됨

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// class
class Fruit{
  String name;
  int weight;
  Fruit(String name, int weight){ this.name = name; this.weight = weight;}
}
class Apple extends Fruit{
  Apple(String name, int weight){ super(name, weight);}
}
class Grape extends Fruit{
  Grape(String name, int weight){ super(name, weight);}
}

// Comparator 
class AppleComp implements Comparator<Apple>{
  public int compare(Apple a1, Apple a2){
    return a2.weight - a1.weight;
  }
}
class GrapeComp implements Comparator<Grape>{
  public int compare(Grape g1, Grape g2){
    return g2.weight - g1.weight;
  }
}
class FruitComp implements Comparator<Fruit>{
  public int compare(Fruit a1, Fruit a2){
    return a1.weight - a2.weight;
  }
}
//Box
class FruitBox<T extends Fruit> extends Box{} // 상한선 제한
class Box<T>{}

//main
public static void main(args[] args){
  FruitBox<Apple> appleBox = new FruitBox<Apple>();
  FruitBox<Grape> grapeBox = new FruitBox<Grape>();

  appleBox.add(new Apple("GreenApple", 100));
  appleBox.add(new Apple("GreenApple", 300));
  appleBox.add(new Apple("GreenApple", 200));

  grapeBox.add(new Grape("GreenGrape", 400));
  grapeBox.add(new Grape("GreenGrape", 100));
  grapeBox.add(new Grape("GreenGrape", 200));

  //정렬 - 각자 정의
  Collections.sort(appleBox.getList(), new AppleComp()); // 하한 제한이 되어있음
  Collections.sort(grapeBox.getList(), new GrapeComp());

  System.out.println(appleBox);
  System.out.println(grapeBox);
  System.out.println();

  //정렬 - Fruit
  Collections.sort(appleBox.getList(), new FruitComp());
  Collections.sort(grapeBox.getList(), new FruitComp());

  System.out.println(appleBox);
  System.out.println(grapeBox);
}

//결과 
[GreenApple(300), GreenApple(200), GreenApple(100)]	//정렬 - 각자 정의
[GreenGrape(400), GreenGrape(200), GreenGrape(100)]

[GreenApple(100), GreenApple(200), GreenApple(300)]	//정렬 - Fruit
[GreenGrape(100), GreenGrape(200), GreenGrape(400)]
  • 상한 제한: FruitBox<T extends Fruit>에 상한 제한이 되어있음, Fruit을 상속 받은 class
  • 하한 제한: Collections.sort() 에 하한 제한이 되어있음 static <T> void sort(List<T> list,Comparator<? super T> c) 실제 메서드 선언부 하한 제한이 없었다면, Collections.sort(appleBox.getList(), new AppleComp()); 에서 AppleComp가 아닌 GrapeComp를 넣어도 되어야함 그래서 각 Comp를 정의할때 타입을 지정함

지네릭 메서드

  • 정의: 메서드의 선언부에 지네릭 타입이 선언된 메서드 ex) Collections.sort()
    • 지네릭 타입의 선언위치는 반환타입의 바로 앞
    • 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 다름
    • 지네릭 매서드는 지네릭 클래스가 아니더라도 정의 될 수 있음
    • 메서드에 선언된 지네릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 됨

지네릭 메서드로 변경

1
2
3
4
static Juice makeJuice(FruitBox**<? extends Fruit>** fruitBox){
  ...
}
makeJuice(new FruitBox<Fruit>()); // 호출시

변경 전

1
2
3
4
5
static **<T extends Fruit>**Juice makeJuice(FruitBox**<T>** fruitBox){
  ...
}
<Fruit>makeJuice(new FruitBox<Fruit>()); // 호출시
makeJuice(new FruitBox<Fruit>()); // 호출시 생략할 수 있음

변경 후

복잡한 지네릭 메서드

public satatic <T extends Comparable<? super T>> void sort(List<T> list)

위와 같이 복잡한 지네릭 클래스를 읽을 때에는 단순화해서 읽는 것이 좋음

  1. 지네릭 입에서 와일드 카드를 걷어내고 읽음 T extends Comparable : Comparable를 구현한 클래스
  2. Comparable를 구현한 클래스가 List로 와야함
  3. 이때의 Comparable 와일드 카드까지 붙여서 읽음 ComparableT나 그 조상의 타입 을 타입으로 함

지네릭 타입의 형변환

  • 지네릭 타입 ↔ 원시 타입 : 가능함
  • 지네릭 타입 ↔ 지네릭 타입
    • 대입된 타입이 있는 경우

      1
      2
      3
      4
      5
      
        Box<Object> objectBox = new Box<>();
        Box<String> stringBox = new Box<>();
            
        objectBox = (Box<String>)stringBox; // X
        stringBox = (Box<Object>)objectBox; // X
      
    • 와일드 카드로 된 경우

      1
      2
      3
      4
      5
      6
      7
      8
      
        Box<? extends Object> box1 = (Box<? extends Object>)new Box<String>();//O
        Box<String> box2 = (Box<String>)new Box<? extends Object>();//O
            
        Box<? extends Object> objectBox = new Box<>();
        Box<? extends String> stringBox = new Box<>();
            
        objectBox = (Box<? extends String>)stringBox; // O
        stringBox = (Box<? extends Object>)objectBox; // O
      

      ⇒ Box 를 Box으로 바로 형변환은 불가능하지만 와일드카드가 포함되어 있다면 가능

지네릭 타입의 제거

컴파일러의 지네릭 타입 제거

  1. 지네릭 타입을 이용해 소스파일 체크
  2. 필요한 곳에 형변환을 넣어줌
  3. 지네릭 타입을 제거함

⇒ 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없음

지네릭 타입 제거 과정

  1. 지네릭 타입의 경계 제거

    Build source

  2. 지네릭 타입 제거 후 타입 불일치시 형변환을 추가

    Build source

열거형

열거형이란?

서로 관련된 상수를 편히라게 선언하기 위한 것, 여러 상수들을 정의할 떄 사용하면 유용함

  • 장점
    • 논리적인 오류를 줄일 수 있음, 값 뿐만 아니라 타입까지 관리하기 떄문
    • 열거형 상수 이용시 기존 소스를 다시 컴파일 하지 않아도 됨
      • 상수 값 변경시 상수를 사용하는 모든 코드를 다시 컴파일 해야함

열거형의 정의와 사용

정의하는 방법 {} 안에 상수의 이름을 나열

1
enum 열거형이름{ 상수명1, 상수명2, 상수명3,,, }
사용하는 방법
열거형이름.상수명 으로 참조, switch문에서만 상수명으로 참조

열거형의 비교

  • ==를 통해 비교가 가능 빠른 성능을 제공함
  • < , > 의 비교는 불가능, compareTo() 를 이용해야함

기본 메서드

  • 컴파일러가 자동으로 추가해주는 메서드
    • values(): 열거형의 모든 상수를 배열에 담아서 반환
  • 모든 열거형의 최고 조상인 Enum에 정의된 메서드
    • getDeciaringClass() : 열거형의 class객체를 반환
    • name(): 열거형 상수의 이름을 문자열로 반환
    • ordinal(): 열거형 상수가 정의된 순서를 반환 (0부터 시작)
    • valueOf(): 지정된 열거형에서 name과 일치하는 열거형 상수를 반환

열거형에 멤버 추가하기

멤버 추가

열거형 상수의 이름옆에 () 를 적고, 그 안에 원하는 값을 넣는다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  enum Direction {
    EAST(1, ">"), SOUTH(5, "V"), WEST(-1, "<"), NORTH(10, "^"); //1. 상수 정의
    
    // 2. 정수를 저장할 필드, 열거형 상수 값을 저장
    private final int value; 
    private final String symbol; 
  
    // 2. 생성자 추가, 이때 생성자는 묵시적으로 private
    Direction(int value, String symbol){
      this.value = value;
      this.symbol = symbol;
    }
  
    // 2.멤버 메서드 추가
    public int getValue(){
      return this.value;
    }
    public int getSymbol(){
      return this.symbol;
    }
  }

열거형 상수를 먼저 정의한 뒤에 다른 멤버들을 추가해야함

추상 메서드 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
enum Direction {
  //각 열거형 상수에서 추상메서드에 대해 구현을 작성함
  EAST(1, ">"){
    return "EAST"+value+symbol;
  }, SOUTH(5, "V"){
    return "SOUTH"+value+symbol;
  }, WEST(-1, "<"){
    return "WEST"+value+symbol;
  }, NORTH(10, "^"){
    return "NORTH"+value+symbol;
  };
  
  //각 열거형상수에서 접근 할 수 있도록 접근 제어자 변경
  protected final int value; 
  protected final String symbol; 

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

  public int getValue(){
    return this.value;
  }
  public int getSymbol(){
    return this.symbol;
  }

  abstract String toCustomString(int distance); // 추상메서드 선언
}

열거형의 이해

열거형의 내부적인 구조를 보자
사실은 열거형의 내부구조 하나하나가 Direction 객체
1
2
3
4
5
6
enum Direction{
  EAST, 
  SOUTH, 
  NORTH, 
  WEST;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Direction{
  static final Direction EAST 
    = new Direction("EAST");
  static final Direction SOUTH 
    = new Direction("SOUTH");
  static final Direction NORTH 
    = new Direction("NORTH");
  static final Direction WEST 
    = new Direction("WEST");

  private String name;
  private Direction(String name){
    this.name = name;
  }
}

→ 왼쪽의 enum을 class로 변환한다면 오른쪽과 같음

  • == 연산자로 비교가 가능했던 이유: 각각 static상수의 값은 객체의 주소이고, 이 값은 바뀌지 않기때문
  • 모든 열거형은 추상클래스 Enum의 자손
    • 객체가 생성될 때마다 ordinal변수에 번호를 붙여서 저장
    • Enum은 Comparable인터페이스를 구현함 : compareTo()를 이용해 비교할 수 있는 이유
    • Enum은 제네릭으로 <T extends <Enum<T>> 과 같이 정의되어있음 ordinal() 때문
  • 추상 메서드 추가시, abstract 키워드 추가와 각 static상수들도 추상메서드를 구현해야함

애너테이션

애너테이션이란?

  • 정의: 프로그램 소스안에 다른 프로그램을 위한 정보를 미리 약속된 형식로 포함시킨 것
  • 역할: 프로그램에게 알리는 역할을 할 뿐, 프로그램 자체에는 아무런 영향을 미치지 않음

표준 애너테이션

자바에서 기본적으로 제공하는 애너테이션

종류 Build source

이 중 일부는 메타 애너테이션임

@Override

  • 위치: 메서드 앞
  • 의미: 조상의 메서드를 오버라이딩하는 메서드라는 것을 컴파일러에게 알려줌

    컴파일러는 이 애너테이션이 붙어있으면, 같은 메서드의 이름이 조상클래스에 있는지 확인함

@Deprecated

  • 의미: 이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니 더이상 사용하지 않을것을 권한다는 의미
    • 기존의 것 대신 새로 추가된 기능을 사용하도록 유도

@FunctionalInterface

  • 의미: 함수형 인터페이스 선언시 사용됨

    컴파일러는 함수형 인터페이스를 제대로 정의했는지에 대해 확인함

@SuppressWarnings

  • 의미: 컴파일러가 보여주는 경고 메시지를 나타나지 않게 정의
    • 나타지않게 하고싶은 경고를 ()로 지정, 여러개라면 {}를 사용해배열로 지정

@SafeVarags

  • 의미: 메서드에 선언된 가변인자 타입이 non-reifiable 타입인 경우 경고가 발생하는데, 이 경고를 억제함

non-reifiable 타입과 reifiable 타입

  • non-reifiable 타입: 컴파일 후에 제거되는 타입 ex) 지네릭스
  • reifiable 타입: 컴파일 이후에 제거되지 않는 타입, 컴파일 후에도 타입정보가 유지

메타 애너테이션

새로운 애너테이션을 정의할 때 사용하는 애너테이션

@Target

  • 의미: 애너테이션이 적용가능한 대상을 지정하는데 사용됨

  • 지정할 수 있는 적용대상 종류

    Build source

@Retention

  • 정의: 애너테이션이 유지되는 기간을 정의
  • 애너테이션의 유지정책 종류
    • SOURCE
      • 소스 파일에만 존재, 클래스 파일에는 존재하지 않음
      • 컴파일러가 사용하는 애너테이션의 정책 ex) @Override 등 컴파일러가 체크하는데만 사용
    • CLASS
      • 클래스 파일에 존재, 실행시에 사용불가, 기본값
      • 클래스 파일이 JVM에 로딩될 때는 애너테이션의 정보가 무시되어 실행 시에 애녀테이션의 정보를 얻을 수 없음
    • RUNTIME
      • 클래스 파일에 존재, 실행시에 사용 가능
      • 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리할 수 있음
      • 지역 변수에 붙은 애너테이션은 컴파일러만 인식 할 수 있으므로 RUNTIME정책의 애너테이션을 지역변수에 붙여도 실행시에는 인식되지 않음

@Documented

  • 정의: 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 함

@Inherited

  • 정의: 애너테이션의 자손 클래스에 상속되게함
    • @Inherited 애너테이션을 이용해 정의한 애너테이션을 class에 붙이면, 자손 class에도 정의된 애너테이션이 붙은것으로 인식함

@Repeatable

  • 정의: 원래 애너테이션은 대상에게 한개만 붙일 수 있는데, 여러 개의 애너테이션이 하나의 대상에 적용될 수 있도록함
    • @Repeatable 애너테이션을 이용해 정의한 애너테이션은 1개의 대상에 대해 여러개 추가 가능

      1
      2
      3
      4
      5
      6
      
        @Todo("todo list 1")
        @Todo("todo list 2")
        @Todo("todo list 3")
        class TestClass{
          ,,,
        }
      
    • 애너테이션 여러 개가 하나의 대상에 적용될 수 있기 때문에, 이 애너테이션을 묶음으로 다룰 수 있는 애너테이션도 추가로 같이 정의 해야함

      1
      2
      3
      
        @interface Todos{ //@Todo 여러개를 담을 애너테이션
          Todo[] value(); // 이름이 반드시 value 여야함
        }
      

@Native

  • 정의: 네이티브 메서드에 의해 참조되는 상수필드에 붙이는 애너테이션

네이티브 메서드

  • JVM이 설치된 OS의 메서드를 의미
  • 네이티브 메서드는 보통 c언어로 작성 되어있음
  • 자바에서는 메서드의 선언부만 정의하고, 구현은 하지 않음
  • 자바로 정의되어있어서 호출방법은 같지만, 실제 호출되는 것은 OS의 메서드
  • JNI(Java Native Interface): 네이티브 메서드와 OS의 메서드를 연결해주는 작업을 함

애너테이션 타입 정의하기

정의하기

1
2
3
@interface 애너테이션이름{
	타입 요소이름();//애너테이션의 요소 이름을 선언
}

애너테이션의 요소

  • 정의: 애너테이션 내에 선언된 메서드
  • 특징
    • 반환값이 있음
    • 추상메서드의 형태, 상속으로 구현하지 않아도 됨
    • 기본값을 가질 수 있으며, 애너테이션 적용시 값 지정을 하지 않으면 기본값이 사용됨
    • 타입이 배열인경우 {}를 이용해 여러 개의 값을 지정할 수 있음
  • 규칙
    • 타입 : 기본형, String, enum, 애너테이션, Class 중에서만 가능
    • 매개변수는 가질 수 없음
    • 예외를 선언할 수 없음
    • 요소를 타입 매개변수로 정의할 수 없음
  • 애너테이션의 값을 얻기: 클래스 객체를 통해 가져와야함

    1
    2
    3
    4
    
      Class<AnnotationEx> cls = AnnotationEx.class;
      TestInfo anno = (TestInfo)cls.getAnnotation(TestInfo.class);
      //getAnnotation()에 얻고싶은 annotation을 지정하거나 
      //getAnnotations()로 전체 애너테이션을 조회
    

클래스 객체 클래스에 대한 모든 정보를 가지고 있는데, 여기에 애너테이션의 정보도 포함되어있음 클래스 객체의 리터럴은 AnnotationEx.class 와 같은 형식으로 표현됨

java.lang.annotation.Annotation

  • 정의: 모든 애너테이션의 조상
  • 특징
    • 인터페이스로 정의되어 있음
    • hashCode(), equals(), toString(), annotationType() 이 정의되어 있어 애너테이션 객체에 대해 사용가능함
    • 명시적으로 Annotation을 조상으로 지정할 수 없음

마커 애너테이션

  • 정의: 요소가 하나도 정의되지 않은 애너테이션
This post is licensed under CC BY 4.0 by the author.