1. 제네릭(Generic)
1.1 제네릭?
제네릭의 정의
- 제네릭(Generic)은 컴파일 시 타입을 체크 해 주는 기능이다.
- 또는 타입을 파라미터화 해서 컴파일 시 구체적인 타입이 결정 되도록 하는 것이다.
제네릭의 장점
- 객체의 타입 안정성을 높인다.
※ "타입 안정성을 높인다"는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때
원래의 타입과 다른 타입으로 잘못 형변환 되어 발생할 수 있는 오류를 줄여준다는 뜻이다.
- 형 변환의 번거로움을 줄여준다.
1.2 제네릭 클래스의 선언
- 제네릭 클래스를 작성할 때, Object 타입 대신 타입 매개변수(E)를 선언해서 사용한다.
① 예를 들어, ArrayList 클래스는 다음과 같이 정의 되어 있었다.
public class ArrayList extends AbstractList{
private transient Object[] elementData;
public boolean add(Object o) { /* 내용 생략 */ }
public Object add(int index) { /* 내용 생략 */ }
...
}
② 클래스 이름 옆에 '<E>'를 붙인 다음, 'Object'가 모두 'E'로 변경되었다. (JDK 1.5 부터)
public class ArrayList<E> extends AbstractList<E>{
private transient E[] elementData;
public boolean add(E o) { /* 내용 생략 */ }
public E add(int index) { /* 내용 생략 */ }
...
}
- 제네릭 클래스의 객체를 생성할 때는 타입 매개변수(E) 대신에 실제 타입(String)을 지정해야 한다.
- 타입 매개변수 대신 실제 타입이 지정되면, 형 변환 생략 가능
// 타입 매개변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); // 형 변환 생략 가능
1.3 제네릭의 용어
Box<T> : 제네릭 클래스. ‘T의 Box’ 또는 ‘T Box’라고 읽는다.
T : 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box : 원시 타입(raw type)
아래와 같이 타입 매개변수에 타입을 지정하는 것을 "제네릭 타입 호출"이라 한다.
지정된 타입 'String'을 매개변수화된 타입이라 한다. (해당 용어가 좀 길어서, "대입된 타입"이라는 용어를 사용할 것이다.)
1.4 제네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치 해야 한다.
ArrayList<Tv> tvList = new ArrayList<Tv>(); // OK. 일치
ArrayList<Product> productList = new ArrayList<Tv>(); // 에러. 불일치
- 제네릭 클래스 간의 다형성은 성립한다. (여전히 대입된 타입은 일치 해야 한다.)
List<Tv> list1 = new ArrayList<Tv>();
List<Tv> list2 = new LinkedList<Tv>();
- 매개변수의 다형성도 성립한다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // OK
list.add(new Audio()); // OK
boolean add(E e){ ... } // E에는 Product가 대입 됨 그래서 Product와 그 자손 객체가 가능
- JDK 1.7 부터 타입 추론이 가능한 경우, 생성자의 타입을 생략 할 수 있다.
(참조변수의 타입으로 ArrayList가 Product 타입의 객체만 저장한다는 것을 알 수 있기 때문에 생략 가능하다.)
ArrayList<Product> list = new ArrayList<>();
1.5 제네릭 타입의 형 변환
- 제네릭 타입과 원시 타입 간의 형 변환은 가능 하지만 바람직 하지 않다. (경고가 발생)
Box box = null; // 원시 타입
Box<Object> objBox = null; // 제네릭 타입
box = (Box)objBox; // OK. 제네릭 타입 -> 원시 타입 (경고 발생)
objBox = (Box<Object>)box; // OK. 원시 타입 -> 제네릭 타입 (경고 발생)
- 대입된 타입이 다른 제네릭 타입 간의 형 변환은 불가능하다.
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // 에러. Box<String> -> Box<Object>
strBox = (Box<String>)objBox; // 에러. Box<Object> -> Box<String>
- 와일드 카드가 사용된 제네릭 타입으로는 형 변환이 가능하다.
① FruitBox → FruitBox
Box<Object> objBox = (Box<Object>) new Box<<String>(); // 에러. 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>) new Box<String>(); // OK
Box<? extends Object> wBox2 = new Box<String>(); // 위 문장과 동일
// 매개변수로 FruitBox<Fruit>, FruitBox<Apple>, FruitBox<Grape> 등이 가능
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK
// FruitBox<Apple> → FruitBox<? extends Fruit>
FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
② FruitBox → FruitBox
와일드 카드가 사용된 제네릭 타입에서 제네릭 타입으로의 형변환은 "확인되지 않은 형변환"이라는 경고가 발생한다.
FruitBox는 대입될 수 있는 타입은 여러 개인데, 명확한 타입인 FruitBox으로 형 변환하려고
하기 때문에 경고가 발생하는 것이다.
// FruitBox<? extends Fruit> → FruitBox<Apple>
FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (FruitBox<Apple>) box; // OK. 미확인 타입으로 형 변환 경고
1.6 제네릭 타입의 제거
- 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형 변환을 넣는다.
① 제네릭 타입의 경계(bound)를 제거한다.
② 제네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형 변환을 추가한다.
③ 와일드 카드가 포함되어 있는 경우에는 적절한 타입으로의 형 변환이 추가된다.
1.7 제한된 제네릭 클래스
- 제네릭 타입에 ‘extends’를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한 할 수 있다.
<T extends 조상 타입>
class FruitBox<T extends Fruit> { // Fruit과 그 자손만 타입으로 지정 가능
ArrayList<T> list = new ArrayList<T>();
void add(T item) {list.add(item);}
...
}
- 게다가 add()의 매개변수 타입 T도 Fruit와 그 자손 타입이 될 수 있다.
그러므로 아래와 같이 여러 과일을 담을 수 있는 과일 상자가 된다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK. Apple이 Fruit의 자손
fruitBox.add(new Grape()); // OK. Grape가 Fruit의 자손
- 인터페이스를 구현해야 한다는 제약이 필요하면 ‘implements’가 아닌, ‘extends’를 사용한다.
interface Eatable {}
// Eatable 인터페이스를 구현한 클래스만 타입 매개변수 T에 대입 될 수 있다.
class FruitBox<T extends Eatable> { . . . }
// Fruit의 자손이면서 Eatable 인터페이스를 구현한 클래스만 타입 매개변수 T에 대입 될 수 있다.
class FruitBox<T extends Fruit & Eatable> { . . . }
1.8 제네릭의 제약
- static 멤버에는 타입 매개변수(T)를 사용 할 수 없다.
→ 타입 매개변수에 대입하는 것은 인스턴스 마다 다르게 지정 할 수 있다.
그래서 static 멤버는 같은 클래스의 모든 인스턴스들이 공통으로 사용하는 멤버이기 때문이다.
class Box<T> {
T[] itemArr; // OK. T 타입의 배열을 위한 참조변수
. . .
T[] toArray(); {
T[] tmpArr = new T[itemArr.length]; // 에러. 제네릭 타입의 배열 생성 불가
. . .
return tmpArr;
}
. . .
}
T[] tmpArr = (T[]) new Object[itemArr.length]; // Object 배열을 만들고 T 타입 배열로 형 변환 하면 된다.
- 타입 매개변수(T)로 배열을 생성 할 수 없다. (타입 매개변수로 배열을 선언하는 것은 가능)
즉, new 연산자 뒤에 타입 매개변수(T)를 사용 할 수 없다.
- 제네릭 타입의 배열을 생성 해야 한다면 Object 배열을 만들고 T 타입의 배열로 형 변환하면 된다.
1.9 와일드 카드 '?'
- 와일드 카드를 사용하면 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 할 수 있다.
- 와일드카드는 기호 '?'로 표현하며 와일드 카드는 어떠한 타입도 될 수 있다.
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> : 제한없음. 모든 타입 가능. <? extends Object>와 동일
- 와일드 카드에는 "&"를 사용 할 수 없다.
즉, <? extends T & E>와 같이 사용 할 수 없다.
- 메서드의 매개변수에 와일드 카드를 사용 할 수 있다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f: box.getList()) tmp += f + " ";
return new Juice(tmp);
}
1.10 제네릭 메서드
- 제네릭 메서드는 반환 타입 앞에 제네릭 타입이 선언된 메서드이다.
이전 Chapter에서 살펴본 Collections.sort()가 바로 제네릭 메서드이다.
static <T> void sort(List<T> list, Comparator<? super T> c)
- 제네릭 클래스의 타입 매개변수 <T>와 제네릭 메서드의 타입 매개변수 <T>는 별개
static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.
- 제네릭 메서드는 호출할 때 마다, 타입 매개변수에 타입을 대입해야 한다.
(대부분의 경우, 추론이 가능하므로 생략할 수 있음)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // 메서드를 호출 할 때 타입을 대입 해야 하나
System.out.println(Juicer.makeJuice(fruitBox)); // 대입된 타입을 생략 할 수 있다.
※ 제네릭 메서드와 와일드 카드의 차이
- 와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭 객체를 다루기 위한 것이다.
- 제네릭 메서드는 메서드를 호출 할 때 마다 다른 제네릭 타입을 대입 할 수 있도록 한 것이다.
2. 열거형(enums)
2.1 열거형이란?
열거형의 정의
- 열거형(enum)은 관련된 상수들을 같이 묶어 놓은 것이다.
- Java는 타입에 안전한 열거형을 제공한다.
* "타입에 안전한 열거형"이라는 것은 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생하는 것을 의미한다.
즉, 자바의 열거형은 값과 타입을 모두 체크한다.
2.2 열거형의 정의와 사용
1) 열거형을 정의하는 방법
enum 열거형이름 { 상수명 1, 상수명2, . . . }
enum Direction { EAST, WEST, SOUTH, NORTH }
→ Direction이라는 열거형을 선언한다. 상수의 값은 자동으로 0 부터 시작하는 정수 값이 부여된다.
2) 열거형 타입의 변수를 선언하고 사용하는 방법
enum Direction { EAST, WEST, SOUTH, NORTH }
class Unit{
int x, y; // 유닛의 초기화
Direction dir; // 열거형을 인스턴스 변수로 선언
void init() {
dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화
// 열거형 변수의 값은 열거형 상수 값(EAST, WEST ..) 중 하나 이어야 한다.
}
}
3) 열거형 상수의 비교에 ==와 compareTo()를 사용 할 수 있다.
- 열거형을 비교할 때는 compareTo()를 사용 해야 한다.
- compareTo()는 열거형 상수가 정의된 순서의 차이를 반환한다.
if( dir == Direction.EAST ){
x++;
} else if (dir > Direction.WEST){ // 에러. 열거형 상수에 비교 연산자 사용 불가
...
} else if (dir.compareTo(Direction.WEST) > 0){ // compareTo()는 가능
}
2.3 모든 열거형의 조상 - java.lang.Enum
모든 열거형은 추상 클래스 Enum의 자손이며 아래의 메서드를 상속 받는다.
아래와 같이 컴파일러가 자동적으로 추가해주는 메서드가 있다.
static E values() : 해당 열거형의 모든 상수를 배열에 담아 반환한다.
static E valueOf(String name) : 전달된 문자열과 일치하는 해당 열거형의 상수를 반환한다.
2.4 열거형에 멤버 추가하기
- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호() 안에 적는다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
- 괄호()를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.
- 열거형의 생성자는 묵시적으로 private 이므로, 외부에서 열거형의 객체를 생성 할 수 없다.
- 열거형에 멤버를 추가하는 예시
enum Direction {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^"); // 끝에 ';'를 추가해야 한다.
private final int value;
private final String symbol;
Direction(int value, String symbol) { // 접근 제어자 private이 생략됨
this.value = value;
this.symbol = symbol;
}
public int getValue() { return value; }
public String getSymbol() { return symbol; }
}
2.5 열거형의 이해
열거형 Direction이 아래와 같이 선언되어 있을 때, 사실은 열거형 상수 하나 하나가 객체이다.
3. 애노테이션(annotation)
3.1 애노테이션이란?
애노테이션?
- 애노테이션은 소스코드에 붙여서 특별한 의미를 부여하는 기능이다.
- 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다.
애노테이션의 사용 예시
- @Test는 이 메서드를 테스트 해야 한다는 것을 테스트 프로그램에게 알리는 역할을 할 뿐, 메서드가 포함된 프로그램 자체에는
아무런 영향을 미치지 않는다.
@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
. . .
}
3.2 표준 애노테이션
표준 애노테이션?
- 표준 애노테이션은 Java에서 제공하는 애노테이션이다.
위의 표에서 초록 바탕의 애노테이션은 "메타 애노테이션"이다.
* 메타 애노테이션 : 애노테이션을 정의 하는데 사용되는 애노테이션이다.
3.3 메타 애노테이션
메타 애노테이션?
- 메타 애노테이션은 애노테이션을 정의할 때 사용하는 애노테이션이다.
- 애노테이션을 정의할 때, 애노테이션의 적용 대상(target)이나 유지 기간(retention) 등을 지정하는데 사용된다.
@Target
- @Target은 애노테이션이 적용 가능한 대상을 지정하는데 사용된다.
- 여러 개의 값을 지정할 때는 배열에서 처럼 괄호 {}를 사용해야 한다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
* ANNOTATION_TYPE은 애노테이션을 선언할 때 붙일 수 있다는 뜻이며
CONSTRUCTOR는 생성자를 선언할 때 붙일 수 있다는 뜻이며
. . .
TYPE_USE는 타입이 사용되는 모든 곳에 붙일 수 있다는 뜻이다.
@Retention
- @Retention은 애노테이션이 유지(retention)되는 기간을 지정하는데 사용된다.
- 컴파일러에 의해 사용되는 애노테이션의 유지 정책은 SOURCE이다.
(컴파일러를 직접 작성할 것이 아니면, 이 유지 정책은 필요 없다.)
- 실행 시에 사용 가능한 애너테이션의 정책은 RUNTIME이다.
(유지 정책을 RUNTIME으로 하면, 실행 시에 리플렉션을 통해 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리할 수 있다.)
@Documented
- @Documented는 애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
@Inherited
- @Inherited는 애노테이션이 자손 클래스에 상속 되도록 한다.
- @Inherited가 붙은 애노테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애노테이션이 붙은 것과 같이 인식된다.
@Repeatable
- @Repeatable는 반복해서 붙일 수 있는 애노테이션을 정의할 때 사용한다.
- @Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있다.
@Native
- @Native는 native 메서드에 의해 참조되는 상수에 붙이는 애노테이션이다.
- 네이티브 메서드는 JVM이 설치된 OS의 메서드를 말한다.
네이티브 메서드는 보통 C 언어로 작성되어 있는데, 자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다.
- 자바에 정의된 네이티브 메서드와 OS의 메서드를 연결해주는 작업은 JNI(Java Native Interface)가 담당한다.
3.4 애노테이션 타입 정의하기
새로운 애노테이션을 정의하는 방법
@interface 애노테이션이름{
타입 요소이름(); // 애노테이션의 요소를 선언한다.
. . .
}
애노테이션의 요소
- 애노테이션의 요소(element)는 애노테이션 내에 선언된 메서드를 말한다.
- 애노테이션의 요소는 반환 값이 있고, 매개 변수는 없는 추상 메서드의 형태를 가진다.
그리고 애노테이션을 적용할 때 이 요소들의 값을 모두 지정 해야 한다.
(요소의 이름도 같이 적어주므로 순서는 상관 없다.)
애노테이션 요소의 기본 값
- 애노테이션을 적용 시 값을 지정하지 않으면, 사용 될 수 있는 요소의 기본 값을 지정 할 수 있다. (기본 값으로 null은 사용 불가)
- 애노테이션의 요소가 하나이고 이름이 value인 경우, 애노테이션을 적용할 때 요소의 이름을 생략 하고 값만 적어도 된다.
- 요소의 타입이 배열인 경우, 중괄호 {}를 사용해서 여러 개의 값을 지정할 수 있다.
- 기본 값을 지정할 때도 괄호{}를 사용할 수 있다.
java.lang.annotation.Annotation
- Annotation은 모든 애노테이션의 조상이지만 상속은 불가능하다.
- 사실, Annotation은 인터페이스로 정의되어 있다.
마커 애노테이션 (Marker Anntation)
- 마커 애노테이션은 요소가 하나도 정의되지 않은 애노테이션이다.
애노테이션 요소의 규칙
- 애노테이션의 요소를 선언할 때 지켜야 하는 규칙은 다음과 같다.
① 요소의 타입은 기본형, String, enum, 애노테이션, Class만 허용된다.
② 괄호() 안에 매개변수를 선언할 수 없다.
③ 예외를 선언할 수 없다.
④ 요소를 타입 매개변수로 정의할 수 없다.
'Programming > Java \ Spring' 카테고리의 다른 글
🔥자바스터디🔥 자바의 정석 CH14 람다와 스트림 (0) | 2021.07.18 |
---|---|
🔥자바스터디🔥 자바의 정석 CH13 쓰레드 (0) | 2021.07.11 |
🔥자바스터디🔥 자바의 정석 CH11 컬렉션프레임웍 (0) | 2021.06.27 |
🔥자바스터디🔥 자바의 정석 CH10 날짜와 시간 형식화 (0) | 2021.06.27 |
🔥자바스터디🔥 자바의 정석 CH9 java.lang패키지와 유용한 클래스 (1) | 2021.06.20 |