Java 개념 정리III
Java 개념 정리
지네릭스, 열거형, 애너테이션
지네릭스
1. 지네릭스(Generics)란?
- 컴파일 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
- 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌 (하나의 컬렉션에는 대부분 한 종류의 객체만 저장)
- 지네릭스의 장점: 타입 안정성을 제공한다, 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
2. 지네릭 클래스의 선언
- 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
class Box<T>{ //지네릭 타입 T를 선언 T item; void setItem(T item){ this.item = item; } T getItem(){ return item; } }
- 참조변수, 생성자에 T대신 실제 타입을 지정하면 형변환 생략가능
Box<String> b = new Box<String>(); //타입 T 대신, 실제 타입을 지정 b.setItem(new OBject()); //에러. String이외의 타입은 지정불가 b.setItem("ABC"); //OK. String타입임로 가능 String item = (String)b.getItem(); //형변환이 필요없음
3. 지네릭스 용어
Box<T> : 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다 T : 타입 변수 또는 타입 매개변수. (T는 타입 문자) Box : 원시 타입(raw type)
4. 지네릭스의 제약사항
- static 멤버에는 타입 변수 T를 사용할 수 없다.
class Box<T>{ static T item; //에러 static int compare(T t1, T t2){...} //에러 }
- 지네릭 타입의 배열 T[]를 생성하는 것은 허용되지 않는다.
class Box<T>{ T[] itemArr; //OK. T타입의 배열을 위한 참조변수 T[] toArray(){ T[] tmpArr = new T[itemArr.length]; //에러. 지네릭 배열 생성불가 return tmpArr; } }
5. 지네릭 클래스의 객체 생성과 사용
- 지네릭 클래스 Box< T >의 선언
class Box<T>{ ArrayList<T> list = new ArrayList<T>(); void add(T item){ list.add(item); } T get(int i){ return list.get(i); } ArrayList<T> getList(){ return list; } int size(){ return list.size(); } public String toString(){ return list.toString(); } }
- Box< T >의 객체 생성. 참조변수와 생성자에 대입된 타입이 일치해야함
Box<Apple> appleBox = new Box<Apple>(); //OK Box<Apple> appleBox = new Box<Graph>(); //에러. 대입된 타입이 다르다. Box<Fruit> appleBox = new Box<Apple>(); //에러. 대입된 타입이 다르다.
- 두 지네릭 클래스가 상속관계이고, 대입된 타입이 일치하는 것은 OK.
Box<Apple> appleBox = new FruitBox<Apple>(); //OK. 다형성 Box<Apple> appleBox = new Box<>(); //OK. JDK1.7부터 생략가능
- 대입된 타입과 다른 타입의 객체는 추가할 수 없다.
Box<Apple> appleBox = new Box<Apple>(); appleBox.add(new Apple()); //OK. appleBox.add(new Graph()); //에러. Box<Apple>에는 Apple객체만 추가가능
6. 제한된 지네릭 클래스
- 지네릭 타입에 ‘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 Graph()); //OK. Grape가 Fruit의 자손
- 인터페이스의 경우에도 ‘implements’가 아닌, ‘extends’를 사용
interface Eatable{} class FruitBox<T extends Eatable>{...} class FruitBox<T extends Fruit & Eatable>{...}
7. 와일드 카드 ‘?’
- 지네릭 타입에 와일드 카드를 쓰면, 여러 타입을 대입가능
- 단, 와일드 카드에는 <? extends T & E>와 같이 ‘&’를 사용불가
<? extends T> 와일드카드의 상한 제한. T와 그 자손들만 가능 <? super T> 와일드카드의 하한 제한. T와 그 조상들만 가능 <?> 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
8. 지네릭 메서드
- 반환타입 앞에 지네릭 타입이 선언된 메서드
static <T> void sort(List<T> list, Comparator<? super T> c)
- 클래스의 타입 매개변수< T >와 메서드의 타입 매개변수 < T >는 별개
class FruitBox<T>{ static <T> void sort(List<T> list, Comparator<? super T> c){ } }
- 지네릭 메서드를 호출할 때, 타입 변수에 타입을 대입해야 한다. (대부분의 경우, 추정이 가능하므로 생략할 수 있음.)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>(); FruitBox<Apple> appleBox = new FruitBox<Apple>(); System.out.println(Juicer.<Fruit>makeJuice(FuitBox)); System.out.println(Juicer.makeJuice(appleBox)); //대입된 타입 생략가능
9. 지네릭 타입의 형변환
- 지네릭 타입과 원시 타입간의 형변환은 불가능
Box box = null; Box<Object> = null; box = (Box)objBox; //ok. 지네릭 타입 -> 원시 타입. 경고 발생 objBox = (Box<Object>)Book; //ok. 원시타입 -> 지네릭 타입. 경고 발생
- 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능
Box<? extends Object> wBox = new Box<String>(); FruitBox<? extends Fruit> box = null; FruitBox<Apple> appleBox = (FruitBox<Apple>)box; //ok. 미확인 타입으로 형변환
- < ? extends Object >를 줄여서 < ? >로 쓸 수 있다.
Optional<?> EMPTY = new Opitonal<?>(); //에러. 미확인 타입의 객체는 생성불가 Optional<?> EMPTY = new Optional<Object>(); //OK. Optional<?> EMPT = new Optional<>(); //OK. 위의 문장과 동일
10. 지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
- 지네릭 타입의 경계(bound)를 제거
- 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
- 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
열거형(enums)
1. 열거형이란?
- 관련된 상수들을 같이 묶어 놓은 것. Java는 타입에 안전한 열거형을 제공
class Card{ enum Kind{ CLOVER, HEART, DIAMOND, SPADE } //열거형 Kind를 정의 enum Value{ TWO, THREE, FOUR } //열거형 Value를 정의 final Kind kind; //타입이 int가 아닌 kind임에 유의하자. final Value value; }
2. 열거형의 정의와 사용
- 열거형을 정의하는 방법: enum 열거형이름{상수명1, 상수명2, …}
- 열거형 타입의 변수를 선언하고 사용하는 방법
enum Direction{EAST, SOUTH, WEST, NORTH} class Unit{ int x,y; //유닛의 위치 Direction dir; //열거형을 인스턴스 변수로 선언 void init(){ dir = Direction.EAST; //유닛의 방향을 EAST로 초기화 } }
- 열거형 상수의 비교에 ==와 compareTo() 사용가능
if (dir == Direction.EAST){ x++; } else if (dir > Direction.WEST){ //에러. 열거형 상수에 비교연산자 사용불가 } else if (dir.compareTo(Direction.WEST)>0){ compareTo는 가능 }
3. 모든 열거형의 조상 - java.lang.Enum
- 모든 열겨형은 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) Direction d = Direction.valueOf("WEST");
4. 열거형에 멤버 추가하기
- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적는다.
enum Direction{ EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
- 괄호를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.
enum Direction{ EAST(1), SOUTH(5), WEST(-1), NORTH(10); //끝에 ';'를 추가해야 한다. private final int value; //정수를 지정할 필드(인스턴스 변수)를 추가 Direction(int value){this.value = value;} //생성자를 추가 public int getValue(){return value;} }
- 열거형의 생성자는 묵시적으로 private이므로, 외부에서 객체생성 불가
- Direction d = new Direction(1); //에러. 열거형의 생성자는 외부에서 호출불가
5. 열거형의 이해
- 열거형 Direction이 아래와 같이 선언되어 있을 때,
enum Direction { EAST, SOUTH, WEST, NORTH }
- 열거형 Direction은 아래와 같은 클래스로 선언된 것과 유사하다.
class Direction{ static final Direction EAST = new Direction("EAST"); Static final Direction SOUTH = new Direction("SOUTH"); static final Direction WEST = new Direction("WEST"); static final Direction NORTH = new Direction("NORTH"); private String name; private Direction(String name){ this.name = name; } }
애너태이션(Annotation)
1. 애너테이션이란?
- 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공
- 에너테이션 사용 예
@Test //이 메서드가 테스트 대상임을 프로그램에게 알린다. public void method(){ }
2. 표준 애너테이션
- Java에서 제공하는 애너테이션
- @Override - 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.
- @Deprecated - 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
- @SuppressWarnings - 컴파일러의 특정 경고메시지가 나타나지 않게 해준다.
- @SafeVarargs - 제니릭스 타입의 가변인자에 사용한다 (JDK 1.7)
- @FuctionalInterface - 함수형 인터페이스라는 것을 알린다 (JDK 1.8)
- @Native - native메서드에서 참조되는 상수 앞에 붙인다 (JDK 1.6)
3. 메타 애너테이션
- @Target - 애너테이션이 적용가능한 대상을 지정하는데 사용한다.
- @Documented - 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.
- @Inherited - 애너테이션이 자손 클래스에 상속되도록 한다.
- @Retention - 애너테이션이 유지되는 범위를 지정하는데 사용한다.
- @Repeatable - 애너테이션을 반복해서 적요할 수 있게 한다. (JDK 1.8)
4. 애너테이션 타입 정의하기
- 애너테이션을 직접 만들어 슬 수 있다.
@interface 애너테이션 이름{ 타입 요소이름(); //애너테이션의 요소를 선언한다. }
- 애너테이션의 메서드는 추상메서드이며, 애너테이션을 적용할 때 모두 지정해야 한다. (순서 상관없음)
@interface TestInfo{ int count(); String testdBy(); String[] testTools(); TestType testType(); //enum TestType{FIRST, FINAL} DateTime testDate(); //자신이 아닌 다른 앤테이션(@DateTime)를 포함할 수 있다. } @interface Datetime{ String yymmdd(); String hhmmss(); }
5. 애너테이션 요소의 기본값
- 적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)
@interface Testinfo{ int count() default 1; //기본값을 1로 지정 } @TestInfo //@TestInfo(count=1)과 동일 public class NewClass{...}
- 요소의 이름이 value인 경우 생략할 수 있다.
@TestInfo(5) //@TestInfo(value=5)와 통일 public class NewClass{...}
- 요소의 타입이 배열인 경우, 괄호{}를 사용해야 한다.
@interface Testinfo{ String[] info() default{"aaa","bbb"}; //기본값이 여러개인 경우. 괄호{} 사용 String[] info2() default "ccc"; //기본값이 하나인 경우. 괄호 생략 가능 } @TestInfo //@TestInfo(info={"aaa","bbb}, info2="ccc")와 동일 @TestInfo(info2={}) //@TestInfo(info={"aaa","bbb"}, info2={})와 동일 class NewClass{...}
6. 모든 애너테이션의 조상 - java.lang.annotation.Annotation
- Annotation은 모든 애너테이션의 조상이지만 상속은 불가
@interface TestInfo extends Annotation{ //에러. 허용되지 않는 표현 int count(); String testedBy(); }
- 사실 Annotation은 인터페이스로 정의되어 있다.
package java.lang.annotation; public interface Annotation{ //Annotation자신은 인터페이스이다. bolean equals(Object obj); int hashCode(); String toString(); Class<? extends Annotation> annotationType(); //애너테이션이 타입을 반환 }
7. 마커 애너테이션 - Marker Annotataion
- 요소가 하나도 정의되지 않은 애너테이션
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override{} //마커 애너테이션. 정의된 요소가 하나도 없다.
8. 애너테이션 요소의 규칙
- 애너테이션의 요소를 선언할 때 아래의 규칙을 반드시 지켜야 한다.
- 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용됨
- 괄호()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.