language/java

12. 지네릭스, 애너테이션

wooweee 2023. 3. 23. 00:32
728x90

1. 지네릭스

  • 정의
    1. 동적일 필요없는 타입을 정적으로 변환 → 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능  런타임 에러를 줄여준다.
    2. 클래스에서 지정한 지네릭스 타입으로 인해 다양한 타입의 객체들을 다루는 메서드에 type을 제한할 수 있다.
      • type 제한: 지네릭스 타입 객체 및 지네릭스 타입의 자손 타입 객체 가능
      • method 중 지네릭스가 지정된 method는 반환시 지네릭스 타입을 반환한다

 

  • 장점
    1. 타입 안정성을 제공한다
      • 형변환 error(classCastException)을 방지한다.
      • 의도치 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 월래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여준다.
    2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
ArrayList<Tv> tvList = new ArrayList<Tv>(); // 컬렉션 클래스에 들어갈 수 있는 type 지정

tvList.add(new Tv()); 
tvList.add(new TvBaby()); 
// 지네릭스가 지정한 클래스 및 자손 클래스 를 메서드에 넣을 수 있다.

tvList.get(new TvBaby());
// get으로 나올 때 Tv type으로 나온다. ArrayList에서 해당 method의 type을 지네릭스로 지정했기 때문


// ArrayList class source code
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
    public E get(int index) {
        Objects.checkIndex(index, size);
        return elementData(index);
    }
...

}

 

 

2. 타입 변수

public class ArrayList<E> extends AbstractList<E>
    public transient [E] elementData;
    public boolean add(E o) {/* 내용 생략 */}
    public E get(int index) {/* 내용 생략 */}
    ...
}
// 지네릭 타입에 실제 class type 대입한 형태
public class ArrayList<Tv> extends AbstractList<Tv>
    public transient [Tv] elementData;
    public boolean add(Tv o) {/* 내용 생략 */}
    public Tv get(int index) {/* 내용 생략 */}
    ...
}

 

  • <E>
    • class에서 지네릭스를 사용할 수 있도록 의미 없는 문자를 넣은 것
      단, 해당 위치에는 참조형 타입만 들어간다.
    • method로 예를 들면 매개변수로 생각하면 된다. 이후 내가 직접 객체를 생성할 때 class Type을 넣어주면 된다.
  • 지네릭스가 들어갈 수 있는 곳
    1. class 선언부 
      • class 명 옆
      • 상속 클래스 옆
    2. 메서드 선언부
      • method명 앞
      • 메서드 선언부가 작성되면 해당 메서드는 무조건 메서드 선언부의 generic의 영향을 받는다.
    3. 필드 타입
    4. 메서드
      • 매개변수 타입
      • 반환타입 

  • 지네릭스가 사용 불가한 곳
    1. static : 지네릭스가 iv로 간주되기 때문
    2. new 연산자 이후 : 컴파일 시점에 타입 T가 무엇인지 정확히 알아야하기 때문

3. 지네릭스의 용어

class Box<T> {}

// Box    : 원시 타입
// T      : 타입 변수 또는 타입 매개변수
// Box<T> : 지네릭 클래스
  • 타입 변수(타입 매개변수)
    • 항상 타입 변수와 일치한 class가 와야 한다
    • 다형성 불가
  • 원시 타입
    • 다형성 가능
// 타입 변수 다형성 불가
ArrayList<Tv> list = new ArrayList<Tv>();
ArrayList<Product> list = new ArrayList<Tv>(); // 에러

// 원시 타입 다형성 가능
List<Tv> list = new ArrayList<Tv>();
List<Tv> list = new LinkedList<Tv>();

// 생성된 객체를 이용한 method는 상속관계 허용 -> 실제로는 class에서 어떻게 설정했는지에 따라 다르다
// 하지만 형변환 필요
// 아래 예시에서는 지네릭스 타입 변수가 Product이므로 get으로 꺼낼시 모든 Product type으로 반환된다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // 가능
list.add(new Audio()); // 가능

 

4. 지네릭스 타입과 다형성

  • 규칙 3가지
    1. 객체 생성 시 generic type은 일치해야 한다.
    2. 객체 생성 시 원시타입은 불일치해도 된다.
    3. 생성된 객체를 이용한 mehtod는 상속관계 허용 대신 형변환 필요하다.
ArrayList<Tv> list = new ArrayList<Tv>();

 

class 자체에 지네릭스 타입을 지정하는 이유

  1. 해당 클래스 메서드나 변수로 들어오는 값을 지네릭스 타입과 지네릭스 타입의 자손으로 제한 가능
  2. 다른 클래스의 메서드에서 매개변수로 해당 클래스의 타입을 지정 시 들어오는 객체를 제한 가능
class Try {
    public static void main(String[] args) {
        ArrayList<Product> productList = new ArrayList<Product>();
        ArrayList<Tv> tvList = new ArrayList<>();

        productList.add(new Tv());
        productList.add(new Audio());

        tvList.add(new Tv());
        tvList.add(new Audio()); //  1. 제한  error.

        printAll(productList);
        printAll(tvList);  // 2. 제한 error.
        
    }
    // 다른 class의 메서드 매개변수
    public static void printAll(ArrayList<Product> list) {
        for (Product p : list) {
            System.out.println(p);
        }
    }
}

class Product{}
class Tv extends Product {}
class Audio extends Product{}

 

5. 지네릭스 사용하는 class

  • Iterator <E>
  • HashMap <K, V>

 

6. 제한된 지네릭 클래스

  • 필요한 이유
    • class Tv<T> {}를 사용할 때 <T> 타입 변수에 아무 클래스나 다 넣을 수 있다.
    • T에 들어갈 수 있는 타입을 제한하기 위해 제한된 지네릭 클래스를 만들었다.
  • T extends Type: Type을 구현한 것들로만 지네릭스 지정 가능
    1. class type 제한 방법
      • 단일만 가능
      • 위치는 무조건 interface 구현보다 처음에 작성
    2. interface 구현 요구 
      • 다중 가능
      • 위치는 class type 이후
      • implements 사용 안 함
    3. class type interface 복합 사용 시
      • &로 연결
// 제공된 조상 class 와 인터페이스들
class Box<T>{}
interface Eatable{}
interface Eatable1{}

// class type으로 제한하기
class FruitBox <T extends Fruit> extends Box<T>

// 인터페이스 구현 제약
class FruitBox <T extends Eatable> extends Box<T>
class FruitBox <T extends Eatable & Eatable1> extends Box<T>


// class type과 인터페이스 구현 둘다 제한
class FruitBox <T extends Fruit & Eatable> extends Box<T>

 

  • extends Box에 타입변수 <T>를 붙이는 이유
    • 규칙이다. 안 써도 되지만 쓰도록 권장
    • 만약 Box라는 조상 클래스 타입변수에 제한된 generic 이 존재 시 자손 class도 해당 제한된 generic을 extends 해줘야 한다.
class Box<T extends Toy> {
    ArrayList<T> list = new ArrayList<>();

    void add(T item) {
        list.add(item);
    }
    T get(int i) {
        return list.get(i);
    }
    int size() {
        return list.size();
    }
    public String toString() {
        return list.toString();
    }

}

class FruitBox<T extends Toy & Eatable> extends Box<T> {
}

 

7. 와일드카드

  • class 다형성으로 인해서 동일한 정적 메서드 (static method)를 만들 경우 문제가 발생한다.
    1. T에 맞춰서 동일한 static method를 생성해야한다. == 중복 메서드 발생
    2. 해당 중복메서드는 class 파일 입장에서 지네릭스만 다르다고 오버로딩을 허용해주지 않아 컴파일 에러 발생 == 메서드 중복 정의
  • 해당 문제점을 해결하기 위해서 와일드 카드로 유연하게 타입을 받을 수 있도록 한다.

  • 정의 : 와일드카드는 지네릭스가 아닌 지네릭스 내부에 들어갈 수 있는 타입 중에서 해당 문제점들을 해결하기 위해 만들어진 유연한 타입이다.

 

7.1. 예시

  • 와일드 카드 정의 파악하기
// 지네릭스의 내부 타입 지정시 타입의 다형성 적용 불가
ArrayList<Tv> list = new ArrayList<Tv>();

// 와일드 카드란 유연한 타입을 이용해서 지네릭스 타입 유연성 적용
ArrayList<? extends Tv> list = new ArrayList<Tv>();
ArrayList<? extends Tv> list = new ArrayList<CaptionTv>();

 

  • 사용자 구현 와일드 카드
public class Algorithm {
    public static void main(String[] args) {
        // 와일드 카드는 지네릭스가 아닌 지네릭스의 타입에 들어가는 type 중 유연한 type 이다.
        FruitBox<? extends Fruit> fb = new FruitBox<Fruit>();
        FruitBox<? extends Fruit> fb2 = new FruitBox<Apple>();
    }
}

class Fruit {}
class Apple extends Fruit {}
class Banana extends Fruit {}

class FruitBox<T> {} // 지네릭 타입으로 어떤 타입이 들어올지 단일타입이 올지, 와일드카드 타입이 올지 대기중

 

  • static method의 다형성 해결 문제점
class Juicer {
    static Juice makeJuice (FruitBox<Fruit> box) {
        // to-do
    }
}

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

Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox); // error

---

class Juicer {
    static Juice makeJuice (FruitBox<Fruit> box) {
        // to-do
    }

    // error
    static Juice makeJuice (FruitBox<Apple> box) {
        // to-do
    }
}

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

Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox); // error

 

  • 와일드 카드로 해결
class Juicer {
    static Juice makeJuice (FruitBox<? extends Fruit> box) {
        // to-do
    }
}

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

Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox);

 

7.2. 와일드 카드 종류

  1. <? extends T> 와일드카드의 상한 제한
  2. <? super T> 와일드 카드의 하한 제한
  3. <?> 제한 없음, 모든 타입이 가능

 

  • 지네릭스 제한과 차이점
이름 정의 사용 차이
제한된 지네릭 클래스 extends T extends Fruit 지네릭 타입 T 유지
와일드 카드 ? extends T
? super T
?
? extends/super Fruit 실제 타입 작성

 

8. 지네릭 메서드

  • class에 선언된 지네릭스의 타입은 원칙적으로 모든 곳에 영향을 끼친다.
  • 해당 클래스의 E type이 정해지면 매개변수, method 반환타입에 동일하게 적용된다.
public class ArrayList<E> extends AbstractList<E>
    public transient [E] elementData;
    public boolean add(E o) {/* 내용 생략 */}
    public E get(int index) {/* 내용 생략 */}
    ...
}

 

  • 하지만 지네릭스 메서드를 작성하게 되면 해당 메서드만큼은 class의 type에 영향을 받지 않고 지네릭 메서드의 영향을 받는다.
public interface Map<K, V> {
...
// 지네릭 메서드의 K, V와 Map의 K, V 는 별개이고
// 지네릭 메서드 선언으로 인해서 해당 메서드 모든 부분의 K,V는 다 지네릭 메서드 K,V의 영향을 받는다
    static <K, V> Map<K, V> of() {
        return (Map<K,V>) ImmutableCollections.EMPTY_MAP;
    }
}

// 알아보기 쉽게 지네릭 명칭만 바꿈
public interface Map<K, V> {
...
    static <methodK, methodV> Map<methodK, methodV> of() {
        return (Map<methodK, methodV>) ImmutableCollections.EMPTY_MAP;
    }
}

 

9. 중간 정리

  1. Class
    1. <T> :기본 타입 형식
    2. <T extends type & interface> : 제한된 타입 형식
      위 2가지의  generics type이 들어갈 수 있음만 작성 가능
  2. 실제 객체 생성 시
    1. <T> 혹은 <T extends type & interface>에 들어갈 수 있는 실제 class type을 작성
    2. 이때 다형성을 적용하고 싶을 때 <? 와일드카드 타입 > 사용한다.
  3. method
    1. 매개변수
      1. 메서드가 받는 매개변수 class type에 generic가 정의되어 있으면 실제 객체 생성때와 똑같이 작성하면 된다.
    2. method 반환타입 앞에 작성된 타입변수
      1. 지역변수들의 타입을 지정해 주는 generics type
      2. class에 generics 타입이 존재하지 않아도  사용 가능
      3. static과 인스턴스 상관없음
      4. 실제 메서드 사용 시 생략가능

 

10. Annotation

  • 자바 어노테이션은 컴파일러에게 코드 분석 정보를 제공하기 위한 메타데이터이다.
  • 메타데이터란 데이터를 설명하는 데이터로, 코드에 대한 설명이나 구조, 실행 방법 등의 정보를 컴파일러나 런타임 시 사용할 수 있도록 도와준다.
  • 어노테이션은 주로 @으로 시작하는 형태의 특별한 문법을 사용하여 작성한다.
  • 어노테이션은 컴파일러나 런타임 시점에서 소스 코드를 분석하고, 해당 정보를 이용하여 다양한 작업을 수행한다.
  • 어노테이션을 사용하는 방법은 크게 세 가지
    1. 어노테이션을 이용하여 소스 코드에 부가 정보를 추가
    2. 컴파일러가 어노테이션 정보를 이용하여 코드를 검증
    3. 런타임 시점에서 어노테이션 정보를 이용하여 코드를 실행

 

11. Annotation 종류

어노테이션 설명
@Override 컴파일러에게 오버라이딩하는 메서드 알림
@Deprecated 앞으로 사용하지 않을 것을 권장
@SuppressWarnings 컴파일러의 특정 경고 메시지 제거
@SafeVarargs 지네릭스 타입의 가변인자에 사용 (JDK1.7)
@FunctionalInterface 함수형 인테페이스라는 것을 알림 (JDK1.8)
@Native native메서드에서 참조되는 상수 앞에 붙임 (JDK1.8)
@Target 어노테이션이 적용가능한 대상을 지정
@Documented 어노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.
@Inherited 어노테이션이 자손 클래스에 상속되도록 한다.
@Retention 어노테이션이 유지되는 범위를 지정하는데 사용
@Repeatable 어노테이션을 반복해서 적용 (JDK 1.8)

 

12. Meta Annotaion

12.1. @Target

  • @Target으로 지정할 수  있는 어노테이션 적용대상 종류
대상 타입 의미
ANNOTATION_TYPE 어노테이션
CONSTRUCTOR 생성자
FIELD 필드(멤버변수, enum 상수)
LOCAL_VARIABLE 지역변수
METHOD 메서드
PACKAGE 페키지
PARAMETER 매개변수
TYPE 타입 (클래스, 인터페이스, enum)
TYPE_PARAMETER 타입 매개변수(JDK 1.8)
TYPE_USE 타입이 사용되는 모든 곳 (JDK 1.8)

 

  • 예시
    • FIELD : 기본형에 적용
    • TYPE_USE: 참조형에 적용
@Target(FIELD, TYPE, TYPE_USE)
public @interface MyAnnotation {}

@MyAnnotation //TYPE
class MyClass {
    @MyAnnotation // FIELD
    int i;
    
    @MyAnnotation // TYPE_USE
    MyClass mc;
}

 

12.2. @Retetion

  • @Retention policy 종류
유지 정책 의미
SOURCE 소스 파일에만 존재, 컴파일러가 사용하는 어노테이션
RUNTIME 실행시 reflection을 통해 처리하는 정보
CLASS 컴파일러가 어노테이션의 정보를 클래스 파일에 저장

하지만, .class가 JVM에 로딩시 어노테이션 정보가 무시되어 실행되기 때문에 어노테이션 정보를 얻을 수 없다.

 

 

12.3. @Document

  • 어노테이션 정보를 javadoc으로 작성한 문서에 포함되도록 한다.
  • @Override, @SuppressWarnings 제외하고 모두 해당 메타 어노테이션이 붙어 있다.

 

12.4. @Interited

@Interited
@interface SupperAnno {}


@SuperAnno
class Parent {}

class Child extends Parent {} // @SuperAnno가 붙은 것으로 인식

 

12.5. @Repeatable

  • 같은 이름의 어노테이션이 여러 개가 하나의 대상에 적용될 수 있기 때문에, 이 어노테이션들을 하나로 묶어서 다룰 수 있는 어노테이션도 추가로 정의 되야한다.
@interface ToDos { // 여러 개의 ToDo 어노테이션을 담을 컨테이너 어노테이션
    ToDo[] value(); // 이름은 반드시 value
}

@Repeatable(ToDos.class) // 괄호안에 컨테이너 어노테이션을 지정
@interface ToDo {
    String value();
}

 

13. Annotation 정의 및 요소

  • Annotation 정의
    • 어노테이션은 주석, 주해, 메모라는 의미를 가지고 있다.
      여러가지 어노테이션들이 존재하지만 각 어노테이션 마다 해당 어노테이션을 보고 compile 및 runtime 때 어노테이션 정보를 이용해서 특정 동작을 수행한다.
      따라서, 해당 어노테이션이 어떤 프로그램에서는 의미가 있고 어떤 프로그램에서는 의미를 가지지 않게 된다. 의미를 가지지 않는 경우는 주석과 같이 존재하지 않는 것이나 다름없다.

    • 예시
      @Test는 test 프로그램에게는 의미가 있지만 javadoc.exe에게는 주석과 다름없다. javadoc.exe에게는  @author, @since가 오히려 의미를 가지는 어노테이션이다.
  • 규칙
    1. 요소로 추상 메서드만 가능
    2. 요소의 타입은 기본현, String, enum, 어노테이션, Class 만 허용
    3. 요소의 선언부 () 내부에 매개변수를 선언할 수 없다.
    4. 예외 선언 불가
    5. 요소를 타입 매개변수로 정의 불가 == 지네릭스 타입 불가

 

@interface 어노테이션이름 {
    타입 요소이름1(); // 추상메서드로 선언
    타입 요소이름2(); // 추상메서드로 선언
    타입 요소이름3(); // 추상메서드로 선언
    // 순서 상관 없다.
}

@어노테이션이름(요소이름3=값, 요소이름2=값, 요소이름1=값)
class Practice {
...
}

 

  • Annotation 내부는 인터페이스와 동일하게 추상메서드 작성
  • 상수는 작성 가능 static final int = 7;
  • 디폴트 메서드 정의 불가

 

13.1. 어노테이션 사용 규칙

 

  • default 값을 지정할 수 있다. - null은 불가
  • 실제 사용 시 값 생략 가능
@interface TestInfo{
    int count() default 1; // 기본값 1
}

@TestInfo // 생략가능
class Try{}

 

 

  • 어노테이션 요소가 하나
  • 요소의 이름 생략 작성 가능
@interface TestInfo{
    int value();
}

@TestInfo(3) // @TestInfo(count = 3) -> 원래는 이렇게 해야함
class Try{}

 

  • 어노테이션 요소가 여러 개이고 요소들이 default 값이 있어서 실제 사용 시 value 하나만 작성해야 할 때
  • value는 요소이름 생략 가능
@interface test{
    int str() default 1;
    int i()default 3;
    String[] value();
}

@test("vvv")
class Try {}

 

 

13. Annotation 조상

  • 애너테이션은 상속이 허용되지 않는다.
  • 하지만 모든 애너테이션의 조상인 Annotation은 일반 interface로 정의 되어있어서 조상이 될 수 있다.
  • 그래서 모든 애너테이션은 조상 annotation의 추상 메서드를 상속받는다.

 

  • 메서드 종류
public interface Annotation{
    boolean equals(Object obj);
    int hashCode();
    String toString();
    
    Class<? extends Annotation> annotationType();
}

 

  • 해당 메서드 호출하고 싶을 때. class를 붙여서 사용하면 된다.
@interface Test{}

@Test
class Try{
    public static void main(String[] args) {
        Test.class.toString
    }
}

 

 

 

이전 발행글 : 11. Collection framework 개념

 

다음 발행글 : 13. 쓰레드