[JAVA] 지네릭스(Generics), 열거형(enums), 어노테이션(Annotation)

1. 지네릭스(Generics)


☾ 지네릭스

: 컴파일 시 타입을 체크(compile-time type check) - jdk 1.5부터 도입

: 객체의 타입을 미리 명시함으로써 번거로운 형변환 감소

: 타입 안정성을 높이고 형변환의 번거로움을 줄여줌

class Box<T> { }

- Box<T> : 지네릭 클래스
- T : 타입 변수(매개변수)
- Box : 원시 타입

 

 

지네릭스 클래스의 선언

class Box<T> {
	T item;
	
	void setItem(T item) {
		this.item = item;
	}
	
	T getTime() {
		return item;
	}
}

 

 

  • 사용 방법
Box<String> b = new Box<String>(); // 타입 T 대신 String 타입 지정
// b.setItem(new Object); // 에러. String 이외의 타입은 지정 불가
b.setItem("ABC");
String item = b.getItem(); // 형변환 필요 없음

 

 

지네릭스의 제약사항

: static 멤버에는 타입 변수 T를 사용할 수 없음

class Box<T> {
	static T item; // 에러
	static int compare(T t1, T t2) {...} // 에러
}

 

 

: 지네릭 타입의 배열 T[] 생성 불가

class Box<T> {
	T[] itemArr; // 가능. T타입의 배열을 위한 참조변수
	
	T[] toArray() {
		T[] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성 불가
		return tmpArr;
	}
}

 

 

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

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

Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<Grape>(); // 에러
Box<Fruit> appleBox = new Box<Apple>(); // 에러

 

 

: 두 지네릭 클래스가 상속관계이고 대입된 타입이 일치하는 것은 가능

Box<Apple> appleBox = new FruitBox<Apple>(); // 가능. 다형성
Box<Apple> appleBox = new Box(); // 가능. jdk 1.7부터 생략 가능

 

 

: 대입된 타입과 다른 타입의 객체는 추가할 수 없음

Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple());
appleBox.add(new Grape()); // 에러. Box<Apple>에는 Apple객체만 추가 가능

 

 

제한된 지네릭 클래스

: 지네릭 타입에 'extends' 사용하면 특정 타입의 자손들만 대입할 수 있음(메서드에서도 마찬가지)

class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정 가능
	ArrayList<T> list = new ArrayList<T>();
	void add(T item) {
		list.add(item);
	}
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // Apple이 Fruit의 자손인 경우 가능
fruitBox.add(new Grape()); // Grape가 Fruit의 자손인 경우 가능

 

 

: 인터페이스의 경우에도 'implements' 대신 'extends' 사용

interface Eatable{}
class FruitBox<T extends Eatable> {...}
class FruitBox<T extends Fruit & Eatable> {...}

 

 

와일드 카드 '?'

: 지네릭 타입에 와일드 카드를 쓰면 여러 타입 대입 가능

: <? extends T & E> 와 같이 '&' 사용 불가

<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> : 와일드 카드의 제한. T와 그 조상들만 가능
<?> : 제한 없음. 모든 타입이 가능 == <? extends Object>
static Juice makeJuice(FruitBox<? extends Fruit> box) {
	String tmp = "";
	for (Fruit f : box.getList()) {
		tmp += f + " ";
		return new Juice(tmp);
	}
}

 

 

지네릭 메서드

: 반환타입 앞에 지네릭 타입이 선언된 메서드

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> fruitBox = new FruitBox<Apple>();

System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox)); // 대입된 타입 생략 가능

 

 

지네릭 타입의 형변환

: 지네릭 타입과 원시 타입 간의 형변환은 가능 but 경고 발생

Box box = null;
Box<Object> objBox = null;

box = (Box)objBox; // 가능하지만 경고 발생
objBox = (Box<object>)box; // 가능하지만 경고 발생

 

 

: 대입된 타입이 다른 지네릭 타입 간의 형변환은 불가능

Box<Object> objBox = null;
Box<String> strBox = null;

objBox = (Box<Object>)strBox; // 에러
strBox = (Box<String>)objBox; // 에러

 

 

: 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능

Box<? extends Object> wBox = new Box<String>();

FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (FruitBox<Apple>)box;

 

 

지네릭 타입의 제거

: 컴파일러는 지네릭 타입을 제거하고 필요한 곳에 형변환을 넣음

 

  1. 지네릭 타입의 경계를 제거
  2. 지네릭 타입 제거 후에 타입이 불일치하면 형변환 추가
  3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

 

 

 

2. 열거형(enums)


 열거형

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

더보기

class Direction {
    static final Direction EAST = new Direction("EAST");
    static final Direction WEST = new Direction("WEST");
    static final Direction SOUTH = new Direction("SOUTH");
    static final Direction NORTH = new Direction("NORTH");

    private String name;

    private Direction(String name) {

        this.name = name;

    }
}

enum Direction {EAST, WEST, SOUTH, NORTH}

 

 

열거형의 정의와 사용

enum 열거형이름 {상수명1, 상수명2, ...}
enum Direction {EAST, WEST, SOUTH, NORTH}

class Unit {
	int x, y;
	Direction dir;
	
	void init() {
		dir = Direction.EAST;
	}
}

 

 

  • 열거형 상수의 비교

: ==와 compareTo() 사용 가능

if (dir == Direction.EAST) {
	x++;
}
else if (dir.compareTo(Direction.WEST) > 0) {
	x--;
}
else if (dir > Direction.WEST) { // 에러. 비교연산자 사용 불가
	...
}

 

 

 

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

: 모든 열거형은 Enum의 자손

Enum으로부터 상속받는 메서드

enum Direction {EAST, WEST, SOUTH, NORTH}

public class EnumEx1 {
	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);
		
		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 ? "+(d2 > d3)); // 에러
		System.out.println("d1.compareTo(d3) ? "+(d1.compareTo(d3)));
		System.out.println("d1.compareTo(d2) ? "+(d1.compareTo(d2)));
		
		switch (d1) {
		case EAST:
			System.out.println("The direction is EAST.");
			break;
		case WEST:
			System.out.println("The direction is WEST.");
			break;
		case SOUTH:
			System.out.println("The direction is SOUTH.");
			break;
		case NORTH:
			System.out.println("The direction is NORTH.");
			break;
		default:
			System.out.println("Invalid direction");
		}
		
		Direction[] dArr = Direction.values();
		
		for (Direction d : dArr) {
			System.out.printf("%s=%d%n", d.name(), d.ordinal());
		}
	}
d1=EAST
d2=WEST
d3=EAST
d1==d2 ? false
d1==d3 ? true
d1.equals(d3) ? true
d1.compareTo(d3) ? 0
d1.compareTo(d2) ? -1
The direction is EAST.
EAST=0
WEST=1
SOUTH=2
NORTH=3

 

 

열거형에 멤버 추가

: 불연속적인 열거형 상수의 경우, 원하는 값을 괄호() 안에 적음

enum Direction {EAST(1), WEST(-1), SOUTH(5), NORTH(10)}

 

 

: 괄호를 사용하려면 인스턴스 변수와 생성자를 새로 추가해야 함

: 열거형의 생성자는 묵시적으로 private이므로 외부에서 객체 생성 불가

enum Direction {
	EAST(1), WEST(-1), SOUTH(5), NORTH(10);
	
	private final int value;
	Direction(int value) {
		this.value = value;
	}
	
	public int getValue() {
		return value;
	}
}

 

 

 

3. 어노테이션(Annotation)


 어노테이션

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

@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알림
public void method() {
	...
}

 

 

표준 어노테이션

*이 붙은 것은 메타 어노테이션

 

 

@Override

: 오버라이딩을 올바르게 했는지 컴파일러가 체크하게 함

: 오버라이딩할 때는 메서드 선언부 앞에 @Override 붙이는 것이 좋음

class Parent {
	void parentMethod() {}
}

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

 

 

@Deprecated

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

@Deprecated
public int getDate() {
	return normalize().getDayOfMonth();
}

@Deprecated가 붙은 대상이 사용된 코드를 컴파일하면 나타나는 메시지

 

 

@FunctionallInterface

: 함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크

※ 함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약 있음

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

 

 

@SuppressWarnings

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

: 괄호()안에 억제하고자 하는 경고의 종류를 문자열로 지정

: 둘 이상의 경고를 동시에 억제하려면 콤마(,)로 구분

: '-Xlint' 옵션으로 컴파일하면 경고메시지 확인 가능

@SuppressWarnings("unchecked")
ArrayList list = new ArrayList();
list.add(obj);

 

 

@SafeVarargs

: 가변인자의 타입이 non-reifiable인 경우 발생하는 unchecked 경고 억제

: 생성자 또는 static, final이 붙은 메서드에만 붙일 수 있음

: @SafeVarargs에 의한 경고의 억제를 위해 @SuppressWarnings 사용

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
	return new ArrayList<>(a);
}

 

 

메타 

: 메타 어노테이션은 어노테이션을 위한 어노테이션

: 어노테이션을 정의할 때, 적용대상이나 유지기간의 지정에 사용

 

 

@Target

: 어노테이션을 적용할 수 있는 대상의 지정에 사용

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
	String[] value();
}

 

 

@Retention

: 어노테이션이 유지되는 기간을 지정하는데 사용

 

 

: 컴파일러에 의해 사용되는 어노테이션의 유지 정책은 SOURCE

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

 

 

: 실행  시에 사용 가능한 어노테이션의 정책은 RUNTIME

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

 

 

@Documented

: javadoc으로 작성한 문서에 포함시키는데 사용

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

 

 

@Inherited

: 어노테이션을 자손 클래스에 상속할 때 사용

@Inherited
@interface SupperAnno {}

@SupperAnno
class Parent {}

class Child extends Parent {} // Child에 어노테이션이 붙은 것으로 인식

 

 

@Repeatable

: 반복해서 붙일 수 있는 어노테이션을 정의할 때 사용

@interface ToDos {
	ToDo[] value();
}

@Repeatable(ToDos.class) // ToDo어노테이션을 여러 번 반복해서 쓸 수 있게 함
@interface ToDo {
	String value();
}

@ToDo("delete test codes")
@ToDo("override inherited methods")

 

 

@Native

: native메서드에 의해 참조되는 상수에 붙임

@Native public static final long MIN_VALUE = 0x8000000000L;

 

 

어노테이션 타입 정의

: 어노테이션의 메서드는 추상메서드이며, 어노테이션을 적용할 때 모두 지정해야 함

@interface 어노테이션이름 {
	타입 요소이름();
}

 

 

어노테이션 요소의 기본값

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

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

@TestInfo // TestInfo(count=1)과 동일
public class NewClass {}

 

 

: 요소의 이름이 value인 경우 생략 가능

@interface TestInfo {
	String value();
}

@TestInfo("passed") // @TestInfo(value="passed")와 동일
public class NewClass{}

 

 

: 요소의 타입이 배열인 경우 중괄호{} 사용

@interface TestInfo {
	String[] info1() default {"aaa", "bbb"};
	String[] info2() default "ccc";
}

@TestInfo
@TestInfo(info2= {})
class NewClass {}

 

 

모든 어노테이션의 조상 - java.lang.annotation.Annotation

: Annotation은 모든 어노테이션의 조상이지만 상속 불가

: Annotation은 인터페이스로 정의되어 있음

@interface TestInfo extends Annotation { // 에러
	int count();
	String testedBy();
}

 

 

마커 어노테이션 - Marker Annotation

: 요소가 하나도 정의되지 않은 어노테이션

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} // 마커 어노테이션

 

 

어노테이션 요소의 규칙

  1. 요소의 타입은 기본형, String, enum, 어노테이션, Class만 허용됨
  2. 괄호()안에 매개변수 선언 불가
  3. 예외 선언 불가
  4. 요소를 타입 매개변수로 정의 불가