[Java] 제네릭 Generic

7534 단어 JavaTILJava

제네릭 Generic

한국어로는 포괄적인, 일반적인을 뜻한다.
클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다.


무슨말이냐

class Person을 보면 < T >가 보인다.
보통은 미리 지정한 (변수타입, 매개변수)가 들어오는데 왜 저게 들어올까?



class Person<T>{
	
	// T는 info라는 필드의 데이터타입이다
	// 처음에는 명시적으로 아무렇게 사용하지 않다가, 
	// 인스턴스화 할때 넣은 <구체적인 데이터타입>에 따라서 바뀌게 된다 
    public T info;
}
 
public class GenericDemo {
 
    public static void main(String[] args) {
    
    	Person<String> p1 = new Person<String>();  //T = String 
        Person<StringBuilder> p2 = new Person<StringBuilder>(); //T = StringBuilder 
        
       
    }
}

위의 코드처럼 처음에는 익명의 < T > 를 사용하다가 인스턴스화를 할때 구체적으로 <데이터 타입>을 명시하면 해당 데이터 타입으로 바뀌는 것을 볼 수 있다.
여기서 T 는 Type variable 타입 변수를 의미한다

ArrayList의 제네릭

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); //타입변환이 필요없음

타입인자


왜 사용해?

코드의 중복을 피하기 위해서

만약 제네릭을 사용하지 않는다면 각각의 타입에 맞는 클래스를 만들어야 한다.
클래스의 내용은 똑같은데!!! 같은 기능의 클래스를 계속 만들어야 한다.
따라서 코드의 중복을 피하기 위해 사용한다.


제네릭의 extends

제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식 클래스로 제한 할 수 있다.
제네릭은 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다.

//부모클래스 Info
abstract class Info{
    public abstract int getLevel();
}

//자손 클래스 
class EmployeeInfo extends Info{
	
    public int rank;
    
    EmployeeInfo(int rank){
    	
    	this.rank = rank; 
    	}
    
    public int getLevel() {
 
        return this.rank;
    }
    
}

		//info클래스의 자식들만 T로 올 수 있다. 
		//인스턴스로 생성된 타입변수가 Info의 자식이 맞는지 확인후 사용 가능 
class Person<T extends Info>{
	
    public T info;
    Person(T info){ this.info = info; 
    }
}

public class Generic {
    public static void main(String[] args) {
    	
//        Person <EmployeeInfo> p1 = new Person<EmployeeInfo> (new EmployeeInfo(1));
        Person p1 = new Person (new EmployeeInfo(1));
       
        Person<String> p2 = new Person<String>("부장"); //에러! 
        
    }
}

인터페이스로 제네릭을 사용할 경우는?

똑같이 extends를 사용한다.
상속한다의 extneds가 아닌 부모가 누구다 라고 의미로 사용하기 때문에 주의해야한다.

//extends에서 interface로 바뀜
interface Info{
    int getLevel();
}
class EmployeeInfo implements Info{
    public int rank;
    EmployeeInfo(int rank){ 
    this.rank = rank; 
    }
    
    public int getLevel(){
        return this.rank;
    }
}
			// implements가 아니라 extends를 사용!!! 
            // 상속한다의  extnes가 아닌 부모가 누구다 라는 의미로사용
class Person<T extends Info>{
    public T info;
    Person(T info){ this.info = info; }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person p1 = new Person(new EmployeeInfo(1));
        Person<String> p2 = new Person<String>("부장");
    }
}

제네릭 와일드 카드<?>

와일드 카드란 비장의 카드라는 의미다.
그런데 왜 제네릭에 와일드 카드라는 용어가 있을까?
여기서 와일드 카드란 제네릭 클래스의 객체를 메소드의 매개변수로 받을 때, 그 객체의 타입변수를 제한하는 것을 말한다.

사용하는 이유?

// String 타입의 값을 상위 타입인 object 타입변수에 넣을 수 있다 
	String string = "yeah";
	Object object = string;
	
	//List는? List<Object>에 넣을 수 없다 
	List<String> stringList = new ArrayList<>();
	List<Object> objectList = stringList; //에러

List< Object >가 List< String >의 상위 타입이 아니라는 것을 알 수 있다.
다시말해 하위호환이 안된다.

여기서 나온것이 ? 와일드 카드이다.
와일드 카드는 어떤 컬렉션이든지 받아서 출력이 가능하다.

와일드 카드< ? >의 제한 종류

  • < ? extends T > 와일드 카드의 상한 제한(upper bound) - T와 그 자손들을 구현한 객체들만 매개변수로 가능. T 타입보다 하위 타입인 어떤 것을 저장하는 컬렉션을 인자로 받는 메소드인 것
  • < ? super T > 와일드 카드의 하한 제한(lower bound) -T와 그 조상들을 구현한 객체들만 매개변수로 가능
  • < ? > 제한 없음


그동안 스프링부트를 사용하면서도 제네릭을 무지성으로 사용했었다.
제네릭인가 보다 넘어갔었는데 점점 스프링부트를 공부할수록 자바에 대한 기초가 중요함을 깨닫는것 같다.

참고자료

https://coding-factory.tistory.com/573
https://opentutorials.org/module/516/6446
https://thecodinglog.github.io/java/2020/12/15/java-generic-wildcard.html

좋은 웹페이지 즐겨찾기