[ TIL ] Generics

지네릭스란?
다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.
타입 파라미터라고 이해하면 쉽다.
우리에게 익숙한 ArrayList 도 지네릭스를 사용한다.
List<String> strList = new ArrayList<String>();
strList.add("string");
strList.add(1); // 컴파일러가 에러를 띄운다. <> 에 선언된 타입과 다르기 때문이다.지네릭 클래스 선언하기
간단히 직접 지네릭 클래스를 만들어보자.
class MyClass<T> {
	T element;
    
    	void setElement(T element){
        	this.element = element;
        }
        T getElement(){
        	return element;
        }
}T 를 타입 파라미터라고 하며 T가 아닌 다른것을 사용해도 된다.
예로 ArrayList는 E 를 사용한다.
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable문자만 다를 뿐 의미는 같다.
MyClass의 객체를 생성할 때는 다음과 같이 타입을 지정해주어야 한다.
MyClass<String> cls = new MyClass<String>();
cls.setElement("element");
cls.setElement(123); // 에러. String 이외의 타입은 지정 불가.
String element = cls.getElement();MyClass의 T 는 타입변수, MyClass는 원시타입이라고 부른다.
지네릭스의 제한
static 멤버에 사용 불가
static 멤버에 타입 변수 T를 사용할 수 없다.
T는 인스턴스 변수로 간주되기 때문이다.
class MyClass<T> {
	static T element; // 에러
}생각해보면 당연하다.
static 멤버는 객체를 생성하지 않고도 사용 가능해야 한다.
그러나 지네릭 클래스의 객체를 생성하기 전에는 T의 타입이 뭔지 알 수가 없다.
지네릭 배열 생성 불가
지네릭 배열 타입의 참조 변수를 선언하는 것은 가능하지만
new T[10] 과 같이 배열을 생성하는 것은 안된다.
class Limit<T> {
    T[] elements;
    
    T[] toArray(){
        T[] tmpArr = new T[elements.length]; // 에러
        ...
        return tmpArr;
    }
    
    ...
}이것은 new 연산자 때문인데, new 연산자는 컴파일 시에 타입 T 가 뭔지 정확히 알아야 한다.
같은 이유로 instanceof 연산자도 new 연산자와 같은 이유로 T를 피연산자로 사용할 수 없다.
지네릭 클래스의 생성과 사용
지네릭 클래스의 생성
지네릭 클래스의 객체를 생성할때,
변수와 객체의 타입변수가 정확히 일치해야 한다.
ArrayList<Parent> list = new ArrayList<Child>(); // 에러
ArrayList<Parent> list = new ArrayList<Child>();두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다.
Parent<String> a = new Child<String>();지네릭 클래스의 사용
대입된 타입과 다른 타입의 객체는 추가할 수 없다.
List<Car> list = new ArrayList<Car>();
list.add(new Car());
list.add(new Bike()); // 에러만약 대입된 타입과 추가되는 타입이 상속관계에 있으면 상관 없다.
List<Vehicle> list = new ArrayList<Vehicle>();
list.add(new Car()); // 가능. Car는 Vehicle 의 자손
list.add(new Bike()); // 가능. Bike도 Vehicle의 자손지네릭스 제한하기
특정 타입의 자손들만 타입 변수로 받기
extends 를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
class MyClass<T extends Vehicle> {
	T vehicle;
    	void setVehicle(T vehicle){
        	this.vehicle = vehicle;
        }
}만일 MyClass의 타입변수로 Vehicle 의 자손이 아닌 클래스를 지정한다면 에러가 발생한다.
MyClass<Car> cls1 = new MyClass<Car>(); // 가능. Car는 Vehicle의 자손
MyClass<String> cls2 = new MyClass<String>(); 
// 에러. String 은 Vehicle의 자손이 아니다.특정 클래스의 자손이면서 동시에 특정 인터페이스를 구현한 클래스들만 받기
Vehicle 의 자손이면서 Product라는 인터페이스를 구현하는 클래스들만 타입 변수로 허용하고 싶다면 아래와 같이 앰퍼샌드를 쓰면 된다.
class MyClass<T extends Vehicle & Product> {
	//...
}와일드카드
다음 코드를 보자.
class Rider {
    String name;
	
    public Rider(String name){ this.name = name; }
    
    void run(Action<Vehicle> action){
        System.out.println(name + " is ready to run");
        action.run();
    }
}run 메서드에서 Action 객체를 받아 해당 탈것을 운전한다.
그런데 run 의 파라미터를 보면 Action의 타입변수가 Vehicle로 고정되어 버렸다.
위에서 말했듯이 타입 변수는 상속 관계와 상관없이 일치해야한다.
그래서 아래의 코드는 에러가 발생한다.
Rider rider = new Rider("Mike");
Action<Car> carAction = new Action<Car>(new Car("Lamborghini"));
rider.run(carAction);타입 변수로 무조건 Vehicle만 받고, Car나 Bike와 같은 그 자손은 받을 수 없게 됏다.
Car와 Bike 까지 받아서, 라이더가 자유롭게 차량을 바꾸면서 운전하게 하고 싶으면 어떻게 해야할까?
아래처럼 와일드카드(?) 를 쓰면 해결된다.
class Rider {
    String name;
	
    public Rider(String name){ this.name = name; }
    
    //와일드 카드 사용
    void run(Action<? extends Vehicle> action){
        System.out.println(name + " is ready to run");
        action.run();
    }
}와일드 카드로 다음과 같이 상한 제한과 하한 제한을 걸 수 있다.
<? extends T> T와 그 자손들만 가능
<? super T>   T와 그 조상들만 가능
<?> 	      모든 타입이 가능. 예를 들어, Rider 클래스의 run 메서드의 파라미터를 다음과 같이 바꾸면,
Car과 그 조상들 (Vehicle, Object) 만 타입 변수로 지정할 수 있게 된다.
void run(Action<? extends Vehicle> action){
	System.out.println(name + " is ready to run");
        action.run();
}추가로, 지네릭 클래스와 달리 와일드 카드에는 &를 사용해 extends 뒤에 복수의 클래스를 지정할 수 없다.
지네릭 메서드
지네릭 메서드란
메서드의 선언부에 지네릭 타입이 선언된 메서드다.
예를 들어 Collections.sort()가 있다.
// 지네릭 메서드의 지네릭 타입의 선언 위치는 반환타입 바로 앞이다.
static <T> void sort(List<T> list, Comparator<? super T> c)지네릭 메서드의 사용
위의 Rider 클래스의 run 메서드를 static 지네릭 메서드로 바꾸면 다음과 같다.
class Rider {
    static <T extends Vehicle>void run(Action<T> product){
        System.out.println("Rider is ready to run");
        product.run();
    }
}지네릭 메서드는 스태틱이어도 상관 없다. 메서드 내에서만 지역적으로 사용할 것이기 때문이다.
위의 run 메서드를 사용하려면 메서드를 호출할때 타입을 지정해줘야 한다.
그러나 대부분의 경우 컴파일러가 대입된 타입을 추정할 수 있기 때문에 생략해도 된다.
Action<Car> carAction = new Action<>(new Car("Lamborghini"));
Rider.<Car>run(carAction);지네릭 타입의 제거
컴파일러는 지네릭 타입을 이용해서 소스파일을 체크한다.
그리고 필요한 곳에 형변환을 넣어준다.
그 다음 지네릭 타입을 제거한다.
즉, 컴파일된 파일에는 지네릭 타입에 대한 정보가 없는 것이다.
이렇게 하는 주된 이유는 지네릭이 도입되기 이전의 소스 코드와의 호환성을 유지하기 위해서다.
기본적인 제거 과정
- 
지네릭 타입의 경계를 제거한다. 
 T extends Vehicle 이라면 R는 Vehicle로 치환된다.
 T 라면 Object로 치환된다.
- 
지네릭 타입을 제거한 후에 타입이 일치하지 않으면 형변환을 추가한다. 
Author And Source
이 문제에 관하여([ TIL ] Generics), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@cksdnr066/TIL-Generics-basic저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
                                
                                
                                
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (Collection and Share based on the CC Protocol.)