Java Standard - 지네릭스 열거형 애너테이션
...
지네릭스
지네릭스란?
- 정의: 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능
- 장점
- 타입의 안정성을 제공
타입 안정성의 의미
- 의도하지 않은 타입의 객체가 저장되는 것을 막음
- 저장된 객체를 꺼냈을 잘못 형변환되어 발생될 오류를 줄여줌
- 형변환의 번거로움이 줄어듬 → 코드의 간결함
지네릭 클래스의 선언
지네릭스의 용어
- 타입문자: 임의의 참조형 타입을 의미, 타입변수나 타입매개변수라고도 함, 위의 예제의
T
를 의미- T는 type에서 따온 것으로 주로 사용하는 기호는
T
이외에도 Element의E
, Key의K
, value의V
상황에 맞는 것을 사용하면 됨
- T는 type에서 따온 것으로 주로 사용하는 기호는
- 지네릭 타입 호출: 타입 매개변수에 타입을 지정하는 것, 위의 예제의
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
와 다른 것이 없어extends
나super
로 제한을 추가해 사용<? 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)
위와 같이 복잡한 지네릭 클래스를 읽을 때에는 단순화해서 읽는 것이 좋음
- 지네릭 입에서 와일드 카드를 걷어내고 읽음
T extends Comparable
: Comparable를 구현한 클래스 Comparable
를 구현한 클래스가List
로 와야함- 이때의
Comparable
와일드 카드까지 붙여서 읽음Comparable
는T나 그 조상의 타입
을 타입으로 함
지네릭 타입의 형변환
- 지네릭 타입 ↔ 원시 타입 : 가능함
- 지네릭 타입 ↔ 지네릭 타입
대입된 타입이 있는 경우
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
지네릭 타입의 제거
컴파일러의 지네릭 타입 제거
- 지네릭 타입을 이용해 소스파일 체크
- 필요한 곳에 형변환을 넣어줌
- 지네릭 타입을 제거함
⇒ 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없음
지네릭 타입 제거 과정
열거형
열거형이란?
서로 관련된 상수를 편히라게 선언하기 위한 것, 여러 상수들을 정의할 떄 사용하면 유용함
- 장점
- 논리적인 오류를 줄일 수 있음, 값 뿐만 아니라 타입까지 관리하기 떄문
- 열거형 상수 이용시 기존 소스를 다시 컴파일 하지 않아도 됨
- 상수 값 변경시 상수를 사용하는 모든 코드를 다시 컴파일 해야함
열거형의 정의와 사용
정의하는 방법 {}
안에 상수의 이름을 나열
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상수들도 추상메서드를 구현해야함
애너테이션
애너테이션이란?
- 정의: 프로그램 소스안에 다른 프로그램을 위한 정보를 미리 약속된 형식로 포함시킨 것
- 역할: 프로그램에게 알리는 역할을 할 뿐, 프로그램 자체에는 아무런 영향을 미치지 않음
표준 애너테이션
자바에서 기본적으로 제공하는 애너테이션
이 중 일부는 메타 애너테이션임
@Override
- 위치: 메서드 앞
의미: 조상의 메서드를 오버라이딩하는 메서드라는 것을 컴파일러에게 알려줌
컴파일러는 이 애너테이션이 붙어있으면, 같은 메서드의 이름이 조상클래스에 있는지 확인함
@Deprecated
- 의미: 이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니 더이상 사용하지 않을것을 권한다는 의미
- 기존의 것 대신 새로 추가된 기능을 사용하도록 유도
@FunctionalInterface
의미: 함수형 인터페이스 선언시 사용됨
컴파일러는 함수형 인터페이스를 제대로 정의했는지에 대해 확인함
@SuppressWarnings
- 의미: 컴파일러가 보여주는 경고 메시지를 나타나지 않게 정의
- 나타지않게 하고싶은 경고를
()
로 지정, 여러개라면{}
를 사용해배열로 지정
- 나타지않게 하고싶은 경고를
@SafeVarags
- 의미: 메서드에 선언된 가변인자 타입이 non-reifiable 타입인 경우 경고가 발생하는데, 이 경고를 억제함
non-reifiable 타입과 reifiable 타입
non-reifiable
타입: 컴파일 후에 제거되는 타입 ex) 지네릭스reifiable
타입: 컴파일 이후에 제거되지 않는 타입, 컴파일 후에도 타입정보가 유지
메타 애너테이션
새로운 애너테이션을 정의할 때 사용하는 애너테이션
@Target
@Retention
- 정의: 애너테이션이 유지되는 기간을 정의
- 애너테이션의 유지정책 종류
- SOURCE
- 소스 파일에만 존재, 클래스 파일에는 존재하지 않음
- 컴파일러가 사용하는 애너테이션의 정책 ex)
@Override
등 컴파일러가 체크하는데만 사용
- CLASS
- 클래스 파일에 존재, 실행시에 사용불가, 기본값
- 클래스 파일이 JVM에 로딩될 때는 애너테이션의 정보가 무시되어 실행 시에 애녀테이션의 정보를 얻을 수 없음
- RUNTIME
- 클래스 파일에 존재, 실행시에 사용 가능
- 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리할 수 있음
- 지역 변수에 붙은 애너테이션은 컴파일러만 인식 할 수 있으므로 RUNTIME정책의 애너테이션을 지역변수에 붙여도 실행시에는 인식되지 않음
- SOURCE
@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을 조상으로 지정할 수 없음
마커 애너테이션
- 정의: 요소가 하나도 정의되지 않은 애너테이션