개발/Java

[Java] Generic (제네릭) 문법 / 필요한 이유

서해쭈꾸미 2024. 4. 25. 23:02
제네릭이 필요한 이유
  • 첫 번째로 제네릭은 타입 언어에서 중복되거나 필요 없는 코드를 줄여준다.
  • 두 번째로 그러면서도 제네릭은 타입 안정성을 해치지 않는다.

 

 

제네릭 제한점

 

1. static 사용불가

제네릭은 인스턴스 변수 취급되는데 static은 컴파일 된 시점부터 바로 사용가능하기에 인스턴스화가 필요 없다.

static T get() { ... } // 에러

static void set(T t) { ... } // 에러

 

2. 제네릭 배열은 생성 할 수 없다.

 

 

 

 

제네릭 클래스 / 메소드 사용 예시
package week04.gen;

// 제네릭은 클래스 또는 메서드에 사용 가능 !
// <>안에 들어가야 할 타입을 명시해주면 된다.

public class Generic<T> {
    // 2. 내부 필드에 String
    private T t;

    // 3. method의 return type도 String
    public T get() {
        return this.t;
    }
	// 4. method의 매개변수 type도 String
    public void set(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        // 1. Generic 클래스 사용 T를 -> String으로 만들음.
        Generic<String> stringGeneric = new Generic<>();
        // 5. method의 매개변수 type이 String으로 사용되는 것을 볼 수 있다.
        stringGeneric.set("Hello World");

        String tValueTurnOutWithString = stringGeneric.get();

        System.out.println(tValueTurnOutWithString);
    }
}

 

 

 

 

 

제네릭 문법

 

1. 다수의 타입 변수를 사용할 수 있다.

public class Generic<T, U, E> {
    public E multiTypeMethod(T t, U u) { ... }
}


Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);

 

 

2. 다형성 즉 상속과 타입의 관계는 그대로 적용된다.

 

 

3. 제네릭 타입으로는 기본형(int, char, double, long 등)이 아닌 래퍼 클래스 및 참조형 변수(String, Integer, Double, Long 등) 만 들어갈 수 있다. 

 

 

4. 와일드카드

와일드카드를 통해 제네릭의 제한을 구체적으로 정할 수 있다.

  1. <? extends T> : T와 그 자손들만 사용 가능
  2. <? super T> : T와 그 조상들만 가능
  3. <?> : 제한 없음
public class ParkingLot<T extends Car> { ... }

ParkingLot<BMW> bmwParkingLot = new ParkingLot();
ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!

 

이렇게 제한을 하는 이유는 다형성 때문이다. 위의 코드에서, T는 Car의 자손 클래스들이라고 정의했기 때문에, 해당 클래스 내부에서 최소 Car 객체에 멤버를 접근하는 코드를 적을 수 있다. 반대로 그러한 코드들이 있을 여지가 있기 때문에, Car 객체의 자손이 아닌 클래스는 제한해주어야 한다.

 

 

 

 

제네릭 메서드 별도 선언

메서드를 스코프로 제네릭을 별도로 선언할 수 있다.

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

 

제너릭 클래스가 없이 제네릭 메소드만 있어도 사용이 가능하다. 이렇게 반환 타입 앞에 <> 제네릭을 사용한 경우, 해당 메서드에만 적용되는 제네릭 타입 변수를 선언할 수 있다.

 

참고로 메서드 제어자로 Static 사용 가능하다. 제네릭 메소드의 제네릭 타입변수는 해당 메소드에만 적용되기 때문에 메소드 하나를 기준으로 선언하고 사용할 수 있기때문이다.

 


 

주의할 점

public class Generic<T, U, E> {
		// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
    static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}

 

같은 이름의 변수를 사용했다고 해도 제네릭 메소드의 타입변수는 제네릭 클래스의 타입변수와 다르다. 위 코드의 두 T는 같은 것이 아니다. 따로 따로 선언된 것이다.

 

 

 

 

 

자바에서 실제 제네릭이 사용된 예시 : List

 

자바에서 많이 사용하는 컬렉션인 List 인터페이스도 제네릭으로 구현되어있다.

// 실제 java.util의 List 코드
public interface List<E> extends Collection<E> {
		int size();
    boolean isEmpty();
		...
		boolean addAll(Collection<? extends E> c);
		boolean add(E e);
		...
}