왜 제네릭을 사용해야 하는가?
Java 5부터 제네릭(Generic) 타입이 새로 추가,
제네릭 타입을 이용함 으로써 잘못된 타입이 사용될수 있는 문제를 컴파일 과정에서 제거
제네릭은 컬렉션, 람다식, 스트림, NIO에서 널리 사용되므로 확실히 이해
API 도큐먼트를 보면 제네릭 표현이 많다,
제네릭은 클래스와 인터페이스, 메소드를 정의할때 타입(type)을 파라미터(parameter)로 사용할 수 있게함
타입 파라미터는 코드 작성시 구체적인 타입으로 대체 되어 다양한 코드 생성하도록 해줌
제네릭 코드의 장점
컴파일 시 강한 타입 체크를 할수 있다.
-> 자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기위해 제네릭 코드에 대해 강한 타입 체크를 한다. 실행 시 타입 에러가 나는 것보다는 컴파일시 미리 타입을 강하게 체크해서 에러를 사전에 방지하는 것이 좋다
타입 변환(casting)을 제거한다
비제네릭 코드는 불필요한 타입 변화을 하기 때문에 프로그램 성능에 악영향을 미친다.
제네릭 타입(class<T>, interface<T>)
제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 '<>' 부호가 붙고, 사이에 타입 파라미터가 위치한다.
public class 클래스명<T> {...}
public interface 인터페이스명<T> {...}
타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만,
일반적으로 대문자 알파벳 한 글자로 표현한다.
제네릭 타입을 실제 코드에서 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다.
그렇다면 왜 이런 타입 파라미터를 사용해야 할까???
package Chapter13;
public class Box {
private Object object;
public void setObject(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
Box 클래스의 필드 타입을 Object 타입으로 선언한 이유는 필드에 모든 종류의 객체를 저장하고 싶어서이다.
Object 클래스는 모든 자바 클래스의 최상의 조상(부모) 클래스이다
따라서 자식 객체는 부모 타입에 대입할 수 있다는 성질 때문에 모든 자바 객체는 Object 타입으로 자동 타입 변환되어 저장된다.
만약 필드에 저장된 원래 타입의 객체를 얻으려면 다음과 같이 강제 타입 변환을 해야 한다.
public static void main(String[] args) {
Box box = new Box();
box.setObject("홍길동");
String name = (String) box.get(); // 강제 타입 변환
box.setObject(new Apple()); // 자동 타입 변환
Apple apple = (Apple) box.get(); // 강제 타입 변환
}
Object 타입을 사용하면 모든 종류의 자바 객체를 저장할 수 있다는 장점은 있지만, 저장 할 때 타입 변환이 발생하고,
읽어올 때에도 타입 변환이 발생한다
타입 변환이 빈번하게 발생하면 전체 프로그램 성능에 좋지 못한 결과를 가져올수 있다.
모든 종류의 객체를 저장하면서 타입변환이 발생하지 않도록 하는 방법은 ? 제네릭
public class Box<T> {
private T t ;
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
}
타입 파마리터 T를 사용해서 Object 타입을 모두 T로 대체했다.
T는 Box 클래스로 객체를 생성할 때 구체적인 타읍으로 변경된다.
Box<String> box = new Box<String>();
=>
public class Box<String> {
private String t;
public void set(String t) (this.t = t;)
public String get(){retrun t; }
}
필드 타입이 String 으로 변경 되었고, set() 메소드도 String 타입만 매개값으로 받을수 있게 변경 되었다.
그리고 get () 메소드 역시 String 타입으로 리턴하도록 변경 되었다.
멀티 타입 파라미터 (class<K,V , ...> , interface<K,V,...>)
제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있는데, 이 경우 각 타입 파라미터를 콤마로 구분한다,
package Chapter13;
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;
}
}
--------------------------------
package Chapter13;
public class ProductExample {
public static void main(String[] args) {
Product<String, String> product1 = new Product<String,String>(); // 자바 6
product1.setKind("SmartTv");
product1.setModel("Samsung");
String tv = product1.getKind();
String model = product1.getModel();
Product<String, String> product2 = new Product<>(); // 자바 7 다이아몬드 연산자
product2.setKind("SmartCar");
product2.setModel("Tesla");
String car = product1.getKind();
String model2 = product1.getModel();
System.out.println(tv+ model + car+ model2);
}
}
제네릭 타입 변수 선언과 객체 생성을 동시에 할 때,
타입 파라미터 자리에 구체적인 타입을 지정하는 코드가 중복해서 나와 다소 복잡해질수 있다.
자바 7 부터 다이아몬드 연산자를 <> 제공한다.
제네릭 메소드(<T,R> R method(T t ))
제네릭 메소드는 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말한다.
제네릭 메소드를 선언하는 방법은 리턴 타입 앞에 <>기호를 추가하고 타입 파라미터를 기술한 다음,
리턴 타입과 매개 타입으로 타입 파라미터를 사용하면된다
public <타입파라미터, ...> 리턴타입 메소드명(매개변수, ...) {...}
다음 boxing() 제네릭 메소드는 <> 기호 안에 타입 파라미터 T를 기술한 뒤, 매개 변수 타입으로 T를 사용했고,
리턴 타입으로 제네릭 타입 Box<T>를 사용했다.
public <T> Box<T> boxing(T t) {...}
제네릭 메소드는 두가지 방식으로 호출할 수 있다.
코드에서 타입 파라미터의 구체적인 타입을 명시적으로 저장해도 되고, 컴파일러가 매개값의 타입을 보고 구체적인 타입을 추정 하도록 할 수도 있다.
리턴타입 변수 = <구체적인타입> 메소드명(매개값); // 명시적으로 구체적 타입을 지정
리턴타입 변수 = 메소드명(매개값); // 매개값을 보고 구체적 타입을 추적
제한된 타입 파라미터(<T extends 최상위타입>)
타입 파라미터에 지정되는 구체적인 타입을 제한할 필요가 종종 있다.
예를 들어 숫자를 연산하는 제네릭 메소드는 매개값으로 Number 타입 또는 하위 클래스 타입(Byte, Short, Integer, Long Double) 의 인스턴스만 가져야 한다. 이것이 제한된 타입 파라미터 (bounded type parameter)가 필요한 이유다.
제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드를 붙이고 상위 타입을 명시하면 된다.
상위 타입은 클래스 뿐만 아니라 인터페이스도 가능하다. 인터페이스라고 해서 implements를 사용하지는 않는다.
public <T extends 상위<타입> 리턴타입 메소드(매개변수, ...) {...}>
타입 파라미터에 지정되는 구체적인 타입은 상위 타입이거나 상위 타입의 하위 또는 구현 클래스만 가능하다.
주의할 점은 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 타입의 멤버(필드, 메소드)로 제한된다.
하위 타입에만 있는 필드와 메소드는 사용할 수 없다.
다음은 숫자 타입만 구체적인 타입으로 갖는 제네릭 메소드 compare() 이다. 두개의 숫자 타입을 매개값으로 받아 차이를 리턴한다.
public <T extends Number> int compare(T t1, T t2){
double v1 = t1.doubleValue();
double v2 = t2.doubleValue();
return Double.compare(v1, v2);
}
doubleValue() 메소드는 Number 클래스에 정의되어 있는 메소드로 숫자를 double 타입으로 변환한다.
Double.compare() 메소드는 첫 번째 매개값이 작으면 -1을, 같으면 0을, 크면 1을 리턴 한다.
와일드카드 타입(<?>, <? extends ...>, <? super ...>)
코드에서 ?를 일반적으로 와일드카드 (wildcard)라고 부른다. 제네릭 타입을 매개값이나 리턴타입으로 사용할때 구체적인 타입 대신에 와일드카드를 다음과 같이 세가지 형태로 사용할 수 있다.
* 제네릭 타입 <?> : Unbounded Wildcards (제한없음)
타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
* 제네릭타입<? extneds 상위타입> : Upper Bounded Wildcards(상위 클래스 제한)
타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올 수 있다.
* 제네릭타입<? super 하위타입> : Lower Bounded Wildcards(하위 클래스 제한)
타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올 수 있다.
타입 파라미터가 T가 적용된 곳은 수강생 타입 부분이나
package Chapter13;
public class Course<T> {
private String name;
private T[] students;
public Course(String name, int capacity) {
this.name = name;
students = (T[]) new Object[capacity]; // 타입파라미터로 배열을 생성하려면 new T[N]을 사용할수 없고 (T[]) (new Object[n]로 해야함))
}
public String getName() {
return name;
}
public T[] getStudents() {
return students;
}
public void addStudent(T student) {
for (int i = 0; i < students.length; i++) {
if (students[i] == null) {
students[i] = student;
break;
}
}
}
// 배열이 비어있는 부분을 찾아서 수강생을 추가하는 메소드다
}
수강생이 될 수 있는 타입은 다음 4가지 클래스라고 가정하자. Person의 하위 클래스로 Worker와 Student가 있고, Student의 하위 클래스로 HighStudent가 있다.
*Course<?>
수강생은 모든 타입(Person, Worker, Student, Highstudent) 가 될수 있다
*Course<? extends Student>
수강생은 Student와 HighStudent만 될 수 있다.
*Course<? super Worker>
수강생은 Worker 와 Person 만 될 수 있다.
제네릭 타입의 상속과 구현
제네릭 타입도 다른 타입과 마찬가지로 부모 클래스가 될 수 있다.
다음은 Product<T, M> 제네릭타입을 상속해서 ChildProduct<T, M> 타입을 정의한다.
public class ChildProduct<T, M> extends Product<T, M> {...}
자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다.
다음은 세가지 타입 파라미터를 가진 자식 제네릭 타입을 선언한 것이다
public class ChildProduct<T,M,C> extends Product<T, M>{...}
제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 된다 .
'Back-end > 이것이 자바다[신용권 한빛미디어]' 카테고리의 다른 글
람다식 표준 API의 함수적 인터페이스, 메소드 참조 (0) | 2022.04.10 |
---|---|
람다식 (0) | 2022.04.06 |
스레드 풀 (0) | 2021.12.12 |
스레드 그룹(ThreadGroup) (0) | 2021.12.12 |
스레드 상태 제어 (0) | 2021.12.05 |