제네릭 (Generic)

다람·2023년 2월 24일
0

JAVA

목록 보기
21/27
post-thumbnail

제네릭

  • 데이터의 타입을 일반화하는 것을 의미 즉, 데이터 형식에 의존하지 않고 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법
  • 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것이다.

제네릭을 사용해야 하는 이유

// 제네릭을 사용하지 않은 경우 
ArrayList list = new ArrayList();
list.add("test");
String temp = (String) list.get(0); // 타입 변환이 필요함

// 제네릭을 사용한 경우
ArrayList<String> list2 = new ArrayList();
list2.add("test");
temp = list2.get(0);	// 타입 변환이 필요 없음

제네릭의 장점

  1. 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
  2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉 관리하기 편리하다.
  3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.

제네릭 사용법

ArrayList<Integer> list = new ArrayList<Integer>();

위 예시처럼 <> 괄호 안에 들어가는 타입을 지정해준다.

  • 제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
public class 클래스명<T>{
	...
}

public interface 인터페이스명<T> {
	...
}
  • 제네릭 사용 예시

    반드시 한 글자일 필요는 없다.
class Person <T> {
	public T info;

Person<String> p1 = new Person<String>();
Person<StringBuilder> p2 = new Person<StringBuilder>();

}

제네릭 클래스

class ExGeneric<T> {
	private T t;
    
    public void setT(T t){
    	this.t = t;
    }
    
    public T getT(){
    	return t;
    }
}

위 예시처럼 클래스를 설계할 때 구체적인 타입을 명시하지 않고 타입 파라미터로 넣어두었다가 실제 설계한 클래스가 사용되어질 때

ExGeneric<String> exGeneric = new ExGeneric<>();

이런식으로 구체적인 타입을 지정하여 사용하면 타입 변환을 최소화할 수 있다.

제네릭 인터페이스

인터페이스도 클래스처럼 제네릭으로 설정해두고 사용할 수 있다.

interface InterfaceExGeneric<T>{
	T example();
}

class ExGeneric implements InterfaceExGeneric<String> {
	@Override
    public String example(){
    	return null;
    }
}
  • 멀티타입 파라미터 사용
    멀티 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있고 이 경우 각 타입 파라미터를 콤마로 구분한다.
class ExMultiTypeGeneric<K, V> implements Map.Entry<K,V> {
	private K key;
    private V value;
    
    @Override
    public K getKey(){
    	return this.key;
    }
    
    @Override
    public V getValue(){
    	return this.value;
    }
    
    @Override
    public V setValue(V value){
    	this.value = value;
    }	
}

제네릭 메서드

class People<T,M>{
	private T name;
    private M age;
    
    People(T name, M age){
    	this.name = name;
        this.age = age;
    }
    
    public T getName(){
    	return name;
    }	
    
    public void setName(T name){
    	this.name = name;
    }
    
    public M getAge(){
    	return age;
    }
    
    public void setAge(M age){
    	this.age = age;
    }
    
    // 제네릭 메서드
    public static<T, V> boolean compare(People<T,V> p1, People<T,V> p2){
    	boolean nameCompare = p1.getName().equals(p2.getName());
        boolean ageCompare = p1.getAge().equals(p2.getAge());
        return nameCompare && ageCompare;
    }
}

public class ExGeneric {
	public static void main(String []args){
    	// 타입 파라미터 지정
        People<String, Integer> p1 = new People<String, Integer>("Jack",20);
        
        // 타입 파라미터 추정
        People<String, Integer> p2 = new People("Steve", 30);
        
        // 제네릭 메서드 호출
        boolean result = p1.compare(p1,p2);
        System.out.println(result);
    }
}

제네릭 메서드를 정의할 때 리턴타입이 무엇인지 상관없이 내가 제네릭 메서드라는 것을 컴파일러에게 알려줘야 한다. 그러기 위해 리턴타입을 정의하기 전에 제네릭 타입에 대한 정의를 반드시 명시해야 한다.
제네릭 클래스가 아닌 일반 클래스 내부에도 제네릭 메서드를 정의할 수 있다.
즉, 클래스에 지정된 타입 파라미터와 제네렉 메서드에 정의된 타입 파라미터는 상관이 없다는 것!

제네릭과 다형성 예시


제네릭의 제거 시기

자바 코드에서 선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사되어 타입 변환하게 된다.
그리고서 코드 내에 모든 제네릭 타입은 제거되며 컴파일된 class 파일에는 어떠한 제네릭 타입도 포함되지 않게 된다.
이런식으로 동작하는 이유는 제네릭을 사용하지 않는 코드와의 호환성을 유지하기 위해서이다.

타입 변수의 제한

  • 제네릭은 T와 같은 타입 변수를 사용하여 타입을 제한한다.
    extends 키워드를 사용하면 타입 변수에 특정 타입만을 사용하도록 제한할 수 있다.
class AnimalList<T extends LandAnimal>{
	...
}

위 같이 클래스의 타입 변수에 제한을 걸어 놓으면 클래스 내부에 사용된 모든 타입 변수에 제한이 걸린다.
이땐 인터페이스를 구현할 때에도 extends 키워드를 사용해야 한다.

클래스와 인터페이스 동시에 상속받고 구현해야 한다면?

class AnimalList<T extends LandAnimal & WarmBlood> { ... }

와일드카드

  • 이름에 제한을 두지 않음을 표현하는데 사용하는 기호
  • 물음표 기호를 사용하여 와일드카드를 사용할 수 있다.
<?>				// 타입 변수에 모든 타입을 사용할 수 있다.
<? extends T>	// T 타입과 T 타입을 상속받는 자손 클래스 타입만 사용할 수 있다.
<? super T>		// T 타입과 T 타입이 상속받은 조상 클래스 타임만이 사욯알 수 있다.

와일드카드 사용 예시

class LandAnimal { public void crying() { System.out.println("육지동물"); } }

class Cat extends LandAnimal { public void crying() { System.out.println("냐옹냐옹"); } }

class Sparrow { public void crying() { System.out.println("짹짹"); } }

class AnimalList<T> {
    ArrayList<T> al = new ArrayList<T>();
    public static void cryingAnimalList(AnimalList<? extends LandAnimal> al) {
        LandAnimal la = al.get(0);
        la.crying();
    }
    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    
    int size() { return al.size(); }
}

public class Generic03 {

    public static void main(String[] args) {

        AnimalList<Cat> catList = new AnimalList<Cat>();
		catList.add(new Cat());
        AnimalList<Dog> dogList = new AnimalList<Dog>();
        dogList.add(new Dog());
        AnimalList.cryingAnimalList(catList);
        AnimalList.cryingAnimalList(dogList);
    }

}

// 결과
냐옹냐옹
멍멍
profile
안녕

0개의 댓글