generic
)이란?자바에서 제네릭이란 데이터의 타입을 일반화한다는 것을 의미한다.
제네릭은 Java 5
부터 새롭게 추가된 타입으로, 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법이다. 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 되었다.
제네릭은 클래스와 인터페이스, 그리고 메소드를 정의할 때 타입을 파라미터로 사용할 수 있도록 한다. 타입 파라미터는 코드 작성 시 구체적인 타입으로 대체되어 다양한 코드를 생성하도록 해준다.
제네릭을 사용하는 코드는 비제네릭 코드에 비해 다음과 같은 이점을 가진다.
✅ 컴파일 시 강한 타입 체크를 할 수 있다.
자바 컴파일러는 컴파일 시에 제네릭 코드에 대해 강한 타입 체크를 하여 잘못 사용된 타입 때문에 발생하는 에러를 사전에 방지한다.
✅ 타입 변환을 제거한다.
비제네릭 코드는 불필요한 타입 변환을 하기 때문에 프로그램 성능에 악영향을 미친다.
다음 코드를 보면 List에 문자열 요소를 저장했지만, 요소를 찾아올 때는 반드시 String으로 타입 변환을 해야 한다.
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); //타입 변환을 해야 한다.
다음과 같이 제네릭 코드로 수정하면 List에 저장되는 요소를 String 타입으로 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어서 프로그램 성능이 향상된다.
List<String> list = new ArrayList<String>();
list.add("hello");
String str = list.get(0); //타입 변환을 하지 않는다.
Object
→ Generic
제네릭을 사용하기 전에는, 여러 타입을 사용하는 대부분의 클래스나 메소드에서 인수나 반환값으로 Object
타입을 사용했다. 하지만 Object
를 사용하면 반환된 Object
객체를 다시 원하는 타입으로 타입 변환해야 하며, 이때 오류가 발생할 가능성도 있다.
⭐ 제네릭을 사용하면 컴파일 시에 미리 타입이 정해지므로, 타입 검사나 타입 변환과 같은 번거로운 작업을 생략할 수 있다!
class<T>, interface<T>
)]제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 <>
부호가 붙고, 사이에 타입 파라미터(T)
가 위치한다.
public class 클래스명<T> { ... }
public interface 인터페이스명<T> { ... }
타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만, 일반적으로 대문자 알파벳 한 글자로 표현한다. 제네릭 타입을 실제 코드에서 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다.
그렇다면 타입 파라미터를 사용해야 하는 이유는 무엇일까?
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
Box
클래스의 필드 타입이 Object
인데 Object
타입으로 선언한 이유는 필드에 모든 종류의 객체를 저장하고 싶어서이다. Object
클래스는 모든 자바 클래스의 최상위 조상(부모) 클래스이고, 자식 객체는 부모 타입에 대입할 수 있다는 성질 때문에 모든 자바 객체는 Object
타입으로 자동 타입 변환되어 저장된다.
Object object = 자바의 모든 객체;
set()
메소드는 매개 변수 타입으로 Object
를 사용함으로써 매개값으로 자바의 모든 객체를 받을 수 있게 했고, 받은 매개값을 Object
필드에 저장시킨다. 반대로 get()
메소드는 Object
필드에 저장된 객체를 Object
타입으로 리턴한다. 만약 필드에 저장된 원래 타입의 객체를 얻으려면 다음과 같이 강제 타입 변환을 해야 한다.
Box box = new Box();
box.set("hello"); //String 타입을 Object 타입으로 자동 타입 변환해서 저장
String str = (String) box.get(); //Object 타입을 String 타입으로 강제 타입 변환해서 얻음
이와 같이 Object
타입을 사용하면
이러한 타입 변환이 빈번해지면 전체 프로그램 성능에 좋지 못한 결과를 가져올 수 있다.
제네릭을 이용하면 모든 종류의 객체를 저장하면서 타입 변환이 발생하지 않도록 할 수 있다!
다음은 제네릭을 이용해서 Box
클래스를 수정한 것이다.
public class Box<T> {
private T t;
public T get() { return t; }
public void set(T t) { this.t = t; }
}
타입 파라미터 T
를 사용해서 Object
타입을 모두 T
로 대체했다. T
는 Box
클래스로 객체를 생성할 때 구체적인 타입으로 변경된다.
예를 들어 다음과 같이 Box
객체를 생성했다고 가정해보자.
Box<String> box = new Box<String>();
타입 파라미터 T
는 String
타입으로 변경되어 Box
클래스의 내부는 다음과 같이 자동으로 재구성된다.
public class Box<String> {
private String t;
public void set(String t) { this.t = t; }
public String get() { return t; }
}
필드 타입이 String
으로 변경되었고, set()
메소드도 String
타입만 매개값으로 받을 수 있게 변경되었다. 그리고 get()
메소드 역시 String
타입으로 리턴하도록 변경되었다. 그래서 다음 코드를 보면 저장할 때와 읽어올 때 전혀 타입 변환이 발생하지 않는다.
Box<String> box = new Box<String>();
box.set("hello");
String str = box.get();
이번에는 다음과 같이 Box
객체를 생성했다고 가정해보자.
Box<Integer> box = new Box<Integer>();
타입 파라미터 T
는 Integer
타입으로 변경되어 Box
클래스는 내부적으로 다음과 같이 자동으로 재구성된다.
public class Box<Integer> {
private Integer t;
public void set(Integer t) { this.t = t; }
pulic Integer get() { return t; }
}
필드 타입이 Integer
으로 변경되었고, set()
메소드도 Integer
타입만 매개값으로 받을 수 있게 변경되었다. 그리고 get()
메소드 역시 Integer
타입으로 리턴하도록 변경되었다. 그래서 다음 코드를 보면 저장할 때와 읽어올 때 전혀 타입 변환이 발생하지 않는다.
Box<Integer> box = new Box<Integer>();
box.set(6); //자동 Boxing
int value = box.get(); //자동 UnBoxing
📌 이와 같이 제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고, 타입 파라미터로 대체했다가 실제 클래스가 사용될 때 구체적인 타입을 지정함으로써 타입 변환을 최소화시킨다.