본문 바로가기
Programming/Java

[whiteship 온라인 스터디] 14주차 과제: 제네릭

by 읽고 쓰는 개발자 2021. 2. 27.

github.com/whiteship/live-study/issues/14

 

14주차 과제: 제네릭 · Issue #14 · whiteship/live-study

목표 자바의 제네릭에 대해 학습하세요. 학습할 것 (필수) 제네릭 사용법 제네릭 주요 개념 (바운디드 타입, 와일드 카드) 제네릭 메소드 만들기 Erasure 마감일시 2021년 2월 27일 토요일 오후 1시까

github.com

목표

자바의 제네릭에 대해 학습하세요.

학습할 것 (필수)

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure

https://www.journaldev.com/1663/java-generics-example-method-class-interface

0. 제네릭이란? 

자바5에 추가된 기능. 

잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 됨.

타입 변환(casting)을 제거함. 

제네릭 타입도 상속이 가능. 상속받은 child는 추가적인 타입 파라미터를 가질 수 있음.

기본 타입 인수 사용 불가

List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); 	// 타입 변환 필요



List<String> list = new ArrayList<String>();
list.add("hello");
String str = list.get(0);	// 타입 변환을 하지 않는다.

 

1. 제네릭 사용법

제네릭 타입 (class<T>, interface<T>)

  • 타입을 파라미터로 가지는 클래스와 인터페이스 
  • 클래스 or 인터페이스 뒤에 "<>" 부호가 붙고, 사이에 타입 파라미터가 위치
  • 일반적으로 타입 파라미터는 대문자 알파벳 한 글자로 표현
public class 클래스명<T> {

}

public interface 인터페이스명<T> {

}

 

모든 종류에 객체를 저장할 수 있지만, 타입 변환은 발생하지 않도록 도와줌.

실제 예제 코드는 다음과 같다.

public class Box<T> {			// 타입 파라미터 T를 사용하여 Object 타입을 t로 변경
	
	private T t;
    public T get() { return T; }
    public void set(T t) { this.t = t; } 

}



// Box 클래스로 객체를 생성할 때 구체적인 타입으로 변경된다
Box<String> box = new Box<String>();


// 타입 파라미터 T는 String 타입으로 변경되어 Box 클래스 내부는 다음과 같이 자동으로 재구성된다.
public class Box<String> {
	
	private String t;
    public String get() { return T; }
    public void set(String t) { this.t = t; } 

}




// 필드 타입, set 파라미터 와 get 리턴타입 모두 String 타입으로 변경되었기에 저장, 읽을 때 타입 변환 일어나지 않는다.
Box<String> box = new Box<String>();
box.set("hello");
String str = box.get();

 

멀티 타입 파라미터(class<K,V,...>, interface<K,V,...>)

  • 제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 가질 수 있음.
public class Product<T, M> {
	private T kind;
    private M model;
    
    
    public T getKind() { return this.kind; }
    public M getModel() { return this.model; }
    
    public void setKind(T kind){ this.kind = kind; }
    public void setModel(M model){ this.model = model;}
}

 

2. 제네릭 주요 개념 (바운디드 타입, 와일드 카드)

제한된 타입 파라미터( <T extends 최상위타입> )  : 클래스 혹은 인터페이스 타입 파라미터의 상위 타입을 제한

  • 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한. 하위 타입에만 있는 필드, 메소드 사용 불가
public <T extends 상위타입> 리턴타입 메소드/인터페이스(매개변수, ... ) { 		//interface도 extends

}

와일드카드 타입 (<?>, <? extends ...> , <? super ...> ) 

코드에서 ?를 일반적으로 와일드카드(wildcard)라고 부른다. 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 다음과 같이 세 가지 형태로 사용할 수 있다.

  • 제네릭타입<?> : Unbounded Wildcards(제한없음) 
    • 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
  • 제네릭타입<? extends 상위타입> : Upper Bounded Wildcards(상위클래스 제한)
    • 타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올 수 있다.
  • 제네릭타입 <? super 하위타입> : Lower Bounded Wildcards (하위클래스 제한)
    • 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올 수 있다.

 

3. 제네릭 메소드 만들기

제네릭 메소드(<T,R> R method(T t)) : 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드 

선언 방법 : 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 기술한 다음, 리턴 타입과 매개 타입으로 타입 파라미터를 사용하면 됨.

public <타입파라미터, ...> 리턴타입 메소드명(매개변수 ,... ) { 

}


// boxing() 제네릭 메소드는 <> 기호 안에 타입 파라미터 T를 기술한 뒤, 매개 변수 타입으로 T를 사용하였고,
// 리턴 타입으로 제네릭 타입 Box<T>를 사용했다.
public <T> Box<T> boxing(T t) {

}

제네릭 메소드 호출 방법

리턴타입 변수 = <구체적타입> 메소드명(매개값) ; 	//명시적으로 구체적 타입을 지정
Box<Integer> box = <Integer>boxing(100);

리턴타입 변수 = 메소드명(매개값); 	// 매개값을 보고 구체적 타입 추정 
Box<Integer> box = boxing(100);

 

4. Erasure : 타입 소거

실행 시간에 제네릭은 모두 raw 형태 (제네릭 타입에서 타입이 소거된 타입).

즉, 제네릭 타입을 정의하면 해당 타입은 로(raw) 타입으로 컴파일된다.

public class Product<T, M> {
	private T kind;
    private M model;
    
    
    public T getKind() { return this.kind; }
    public M getModel() { return this.model; }
    
    public void setKind(T kind){ this.kind = kind; }
    public void setModel(M model){ this.model = model;}
}


컴파일 후

public class Product<Object, Object> {
	private Object kind;
    private Object model;
    
    
    public Object getKind() { return this.kind; }
    public Object getModel() { return this.model; }
    
    public void setKind(Object kind){ this.kind = kind; }
    public void setModel(Object model){ this.model = model;}
}

 

오토 박싱/언박싱 등장

기본 데이터 타입에 대한 오토 박싱 / 언박싱 기능은 제네릭에 기반을 두고 있음

자바5 제네릭 등장 이전에는 컴파일러와 가상 머신이 데이터 타입을 추론할 수 없었기 때문에 기본 타입 데이터를 자동으로 래핑 데이터 타입으로 변환할 수 없었음. 

하지만 제네릭 등장으로 이를 추론할 수 있게 되었고, 따라서 데이터 자동 변환이 가능해짐.

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

for(int number : list) {
	...
}

위 list의 제네릭 타입은 <Integer>이다. 해당 리스트에 int 데이터를 추가할 때 오토 박싱이 되면서 Integer 객체로 치환되었고, 다시 for each문에서 데이터 추출할 때도 Integer를 int로 오토 언박싱 되어서 처리가 된다.

 

 

■ 라이브 스터디 

under bounded : 객체 정의할 때 사용 불가. 메소드 파라미터에 사용 가능, 클래스 뿐 아니라 인터페이스도 지원. 

Dao 객체에 generic 적용하기.

런타임 중에 제네릭 타입을 알아낼 수 있을까? yes. 리플렉션으로 가능

 

 

 

출처 : 이것이 자바다 (신용권 저), 가장 빨리 만나는 코어 자바9 (카이 호스트만 저), Practical 모던 자바 (장윤기 저)