자바 스터디

[자바의 정석] CH 12 (Generics, Enum, Annotation)

changha. 2023. 7. 20. 15:00

지네릭스

- 컴파일시 타입을 체크해 주는 기능

public class GenericTset{
	public static void main(String[] args){
    // ArrayList list = new ArrayList();
    ArrayList<Integer> list = new ArrayList();
    list.add(10);
    list.add(20);
    // list.add("30");
    list.add(30); //컴파일 처크 
    
    // 컴파일 OK, But 실행시 에러발생 
    // Integer i = (Integer)list.get(2); 
    
    System.out.println(list); 
    }
}

 

장점

1. 타입 안정성을 제공한다.

2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.

 

RuntimeException을 compile단계에서 잡을 수 있도록 하는게 좋음 

에러를 먼저 인식할 수 있으니까 

ClassCastException은 지네릭스로 인해 컴파일 단계에서 체크가능 

또한 String str = null; 보다는 String str = ""; 을 써야되는 이유도 NullPointerException과 연관돼있음 (str.length()) 

 

타입 변수

- 클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용.

 

타입 변수는 무엇이든지 상관없음, 일반적으로 T나 E(Element의 첫글자) 가 자주 쓰임 

예전에는 반환타입이 Object라서 형변환이 필요했지만 

지네릭스 도입으로 인해 <E>에 Tv가 대입되므로 형변환이 필요 없다. 

 

 

지네릭스 용어 

º Box<T> 지네릭 클래스. 'T의 Box'또는 'T Box'라고 읽는다.

º T 타입 변수 또는 타입 매개변수.(T는 타입 문자)

º Box 원시 타입(raw type)

 

class Box<T> {} // 지네릭 클래스 선언

Box<String> b = new Box<String>();

 

- 참조 변수와 생성자의 대입된 타입은 일치해야 한다. 

ArrayList<Tv> list = new ArrayList<Tv>(); // OK
ArrayLIst<Product> list = new ArrayList<Tv>(); // 에러.

 

- 지네릭 클래스간의 다형성은 성립 (여전히 대입된 타입은 일치해야)

List<Tv> list = new ArrayList<Tv>(); // OK. 다형성. ArrayList가 List를 구현 
List<Tv> list = new LinkedList<Tv>(); // OK. 다형성. LinkedList가 List를 구현

- 매개변수의 다형성도 성립

ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // OK
list.add(new Audio()); // OK

// 꺼낼 때 
Product p = list.get(0);
Tv t = (Tv)list.get(1); //이때는 형변환 해야 함

 

제한된 지네릭 클래스 

- extends로 대입할 수 있는 타입을 제한 

class FruitBox<T extends Fruit> {
	ArrayList<T> list = new ArrayLIst<T>();
    ...
}

FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK 
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 자손이 아님

 

- 인터페이스인 경우에도 extends를 사용 

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

 

지네릭스의 제약

- 타입 변수에 대입은 인스턴스 별로 다르게 가능 

Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Grape> grapeBox = new Box<Grape>(); // OK

 

- static 멤버에 타입 변수 사용 불가 

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

 

- 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능 

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

객체 생성이나 배열 생성 불가능 (왜냐면 타입이 확정되어있어야 하는데 지네릭은 그렇지 않으므로)

 

와일드 카드 <?>

- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능 

ArrayList<? extends Product> list = new ArrayList<Tv>(); // OK
ArrayList<? extends product> list = new ArrayList<Audio>(); // OK
ArrayList<Product> list = new ArrayList<Tv>(); // 에러. 대입된 타입 불일치

 

º <? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능

º <? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능

º <?> 제한 없음. 모든 타입이 가능 <? extends Object>와 동일 

 

- 매서드의 매개변수에 와일드 카드를 사용

static Juice makeJuice(FruitBox<? extends Fruit> box {
	...
}

System.out.println(Juice.makeJuice(new FruitBox<Fruit>())); // OK.
System.out.println(Juice.makeJuice(new FruitBox<Apple>())); // OK.

 

지네릭 메서드 

- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)

static <T> void sort(List<T> list, Comparator<? super T> c)

- 클래스의 타입 매개변수<T>와 메서드의 타입 매개변수 <T>는 별개 

 

- 메서드를 호출할 때마다 타입을 대입해야(대부분 생략 가능)

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
...

System.out.println(Juice.<Fruit>makeJuice(fruitBox)); // OK
System.out.println(Juice.makeJuice(appleBox)); // OK. 생략 가능

static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {...}

 - 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가 

System.out.println(<Fruit>makeJuice(fruitBox)); // 에러. 클래스 이름 생략불가 
System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK.
System.out.println(Juice.<Fruit>makeJuice(fruitBox)); // OK.

 

지네릭 메서드는 메서드를 호출할 때마다 다르 지네릭 타입을 대입할 수 있게 한 것

와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것 

 

지네릭 타입의 형변환

- 지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.(경고 발생)

Box<Object> objBox = null;
Box box = (Box)objBox; // OK. 지네릭 타입 -> 원시 타입. 
objBox = (Box<Object>) box; // OK. 원시 타입 -> 지네릭 타입.

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

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

Box<Object> objBox = (Box<Object>) new Box<String>(); // 에러. 형변환 불가능 
Box<? extends Object> wBox = (Box<? extends Object>) new Box<String>(); // OK.
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일

 

지네릭 타입의 제거 

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

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

class Box<T extends Fruit> {
	void add(T t) {
    	...
    }
}

-> 

class Box {
	void add(Fruit t) {
    	...
    }
}

2. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가 

T get(int i) {
	return list.get(i);
}

->

Fruit get(int i){
	return (Fruit)list.get(i);
}

3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가 

 

열거형

- 관련된 상수들을 같이 묶어 놓은 것. Java는 타입에 안전한 열거형을 제공 

값, 타입 모두 체크 한다 

 

열거형의 정의와 사용 

- 열거형을 정의하는 방법

enum Direction { EAST, SOUTH, WEST, NORTH }

enum 열거형이름 { 상수명1, 상수명2, ...}

 

- 열거형 타입의 변수를 선언하고 사용하는 방법

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

 

- 열거형 상수의 비교에 ==와 compareTo() 사용가능( 비교연산자 사용x)

if(dir==Direction.EAST) {
	x++;
} else if (dir > Direction.WEST) { // 에러. 열거형 상수에 비교연산자 사용불가 
	...
} else if (dir.compareTo(Direction.WEST) > 0) { //compareTo()는 사용가능 
	
}

 

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

- 모든 열거형은 Enum의 자손이며, 아래의 메서드를 상속받는다.

º Class<E> getDeclaringClass() : 열거형의 Class객체를 반환

º String name() : 열거형 상수의 이름을 문자열로 반환

º int ordinal() : 열거형 상수가 정의된 순서를 반환(0부터 시작)

º T valueOf(Class<T> enumType, String name) : 지정된 열거형에서 name과 일치하는 열거형 상수를 반환 

 

- values(), valueOf()는 컴파일러가 자동으로 추가 

static E[] values()
static E valueOf(String name) // -> Direction d = Direction.valueOf("WEST");

Direction[] dArr = Direction.values(); // 열거형에 있는 상수들을 배열로 반환 

for(Direction d : dArr)
	System.out.printf("%s=%d%n", d.name(), d.ordinal());

 

열거형에 멤버 추가하기

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

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); // 에러. 열거형의 생성자는 외부에서 호출불가

 

애너테이션

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

소스코드(*.java) 와 설정파일(XML)을 분리해서 작성했었음, 그 설정에 대한 정보를 소스코드 안에 @이름 과 같은 형식으로

제공함(Junit이라는 특정프로그램에 대해서 작동함) 대규모로 개발할 때 XML을 다같이 수정하기 번거로우니까 애너테이션이 이러한 부분에서 편리함 

소스코드 + 문서를 한 곳에 합침

 

애너테이션의 사용예

 

표준 애너테이션 

- Java에서 제공하는 애너테이션 

 

º @Override

    - 오버라이딩을 올바르게 했는지 컴파일러가 체크하게 된다.

    - 오버라이딩할 때 메서드이름을 잘못적는 실수를 하는 경우가 많다.

class Child extends Parent {
	void parentmethod() {} // parentMethod인데 오타일 때 
}

-> 

class Child extends Parent{
	@Override
    void parentmethod() {} // 에러 출력해줌 
}

 

º @Deprecated

- 앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.

- @Deprecated의 사용 예

 

스프링부트 공부하면서 아래와 같은 deprecated가 생각이 났다 


스프링 버전이 올라가면서 WebSecurityConfigureAdapter가 deprecated됨 

위와 같은 함수형 열거방식도 deprecated되었다

 


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

 

 

º @FunctionalInterface

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

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

@FunctionalInterface
public interface Runnable {
	public abstract void run(); // 추상 메서드 
}

 

º @SuppressWarnings

- 컴파일러의 경고메시지가 나타나지 않게 억제한다.

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

@SuppressWarnings("unchecked") // 지네릭스와 관련된 경고를 억제 
ArrayList list = new ArrayList(); // 지네릭 타입을 지정하지 않았음 
list.add(obj); // 여기서 경고가 원래는 발생

 

메타 애너테이션 

- 메타 애너테이션은 '애너테이션을 위한 애너테이션' 

- 메타 애너테이션은 java.lang.annotation패키지에 포함 

 

º @Target

- 애너테이션을 정의할 때, 적용대상 지정에 사용 

@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation { }

@MyAnnotation
class MyClass {
	@MyAnnotation
    int i;
    
    @MyAnnotation
    MyClass mc;
}

 

º @Retention

- 애너테이션이 유지되는 기간을 지정하는데 사용 

- 컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE이다. 

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

 

- 실행시에 사용 가능한 애너테이션의 정책은 RUNTIME이다.

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

 

애너테이션 타입 정의하기 

- 애너테이션을 직접 만들어 쓸 수 있다.

@interface 애너테이션이름 {
	타입 요소이름(); // 애너테이션의 요소를 선언한다. 
}

 

- 애너테이션의 메서드는 추상 메서드이며, 애너테이션을 적용할 때 지정(순서x)

@interface TestInfo {
	int count();
    String testedBy();
    String[] testTools();
    TestType testType();
    DateTime testDate();
}

 

 

애너테이션의 요소 

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

@interface TestInfo{
	int count() default 1; // 기본값을 1로 지정 
}

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

 

- 요소가 하나이고 이름이 value일 때는 요소의 이름 생략가능

@interface TestInfo{
	String value();
}

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

 

- 요소의 타입이 배열인 경우, 괄호 {}를 사용해야 한다.

@interface TestInfo{
	String[] testTools();
}

@TestInfo(testTools={"JUnit", "AutoTester"})
@TestInfo(testTools={}) // 값이 없을 때는 괄호{}가 반드시 필요

 

마커 애너테이션 - Marker Annotation

-요소가 하나도 정의되지 않은 애너테이션 

ex) @Deprecated, @Test

 


정답 : b, d  (마우스로 드래그)

 

이유 :

b - value만 요소 이름 생략 가능 

d - 배열일 경우 괄호{}가 필요함

 

정답 : c,d,g (마우스로 드래그)

 

이유:

c - Fruit과 자손들만 대입이 가능하므로 Object는 안됨 

d - 타입 불일치 

g - new 연산자는 타입이 명확해야하므로 와일드 카드 불가능