제네릭
- 제네릭(Generic)이란 데이터의 타입을 일반화(generalize)한다는 의미로 클래스나 메소드에서 사용할 데이터의 타입을 컴파일 시 미리 지정하는 방법이다.
- 제네릭을 사용 시 장점
- 타입 변환 및 타입 검사같은 번거로운 작업을 제거할 수 있다.
- 클래스나 메소드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있다. → 실수가 컴파일 오류로 이어짐
제네릭 선언 및 생성
💡 제네릭 클래스 정의
인스턴스 생성시 결정이 되는 자료형의 정보를 <T>로 대체한다.
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get( ) {
return ab;
}
}
💡 제네릭 인스턴스 생성
Box<Apple> aBox = new Box<Apple>( ); // T를 Apple로 결정하여 인스턴스 생성
Box<Orange> oBox = new Box<Orange>( ); // T를 Orange로 결정하여 인스턴스 생성
❓ 관련 용어
용어 | 예시 |
타입 매개변수 (Type Parameter) | Box<T>에서 T |
타입 인자 (Type Argument) | Box<Apple>에서 Apple |
매개변수화 타입 (Parameterized Type) = 제네릭 타입 (Generic Type) |
Box<Appel> |
제네릭 이전의 코드의 문제점
- 여러 타입을 사용하는 대부분의 클래스나 메소드에서 인수나 반환값으로 Object 타입을 사용할 경우 반환된 Object 객체를 다시 원하는 타입으로 타입 변환을 해줘야 한다.
- 프로그래머가 실수를 해도 그 실수가 드러나지 않을 수 있다.
- 오류의 발견
- 컴파일 과정에서 오류 발견 → 컴파일 오류는 원인을 바로 찾을 수 있다.
- 실행 과정에서 예외 발견 → 예외의 원인은 쉽게 발견되지 않는 경우도 많다.
- 발견되지 않음 → 오류가 있는지도 모른채 넘어갈 수 있다.
- 오류의 발견
class Apple { // 사과를 단순히 표현한 클래스
public String toString() {
return "I am an apple";
}
}
class Orange { // 오렌지를 단순히 표현한 클래스
public String toString() {
return "I am an arange";
}
}
class AppleBox { // 사과를 담는 상자를 표현한 클래스
private Apple ap;
public void set(Apple a) { // 사과를 담는다.
ap = a;
}
public Apple get() { // 사과를 꺼낸다.
return ap;
}
}
class OrangeBox { // 오렌지를 담는 상자를 표현한 클래스
private Orange or;
public void set(Orange o) { // 오렌지를 담는다.
or = o;
}
public Orange get() { // 오렌지를 꺼낸다.
return or;
}
}
class FruitBox {
public static void main(String[] args) {
AppleBox aBox = new AppleBox(); // 사과 상자 생성
OrangeBox oBox = new OrangeBox(); // 오렌지 상자 생성
aBox.set(new Apple()); // 사과를 사과 상자에 담는다.
oBox.set(new Orange()); // 오렌지를 오렌지 상자에 담는다.
Apple ap = aBox.get(); // 사과 상자에서 사과를 꺼낸다.
Orange or = oBox.get(); // 오렌지 상자에서 오렌지를 꺼낸다.
System.out.println(ap);
System.out.println(or);
}
}
// 실행 결과
I am an apple
I am an arange
class Apple { // 사과를 단순히 표현한 클래스
public String toString() {
return "I am an apple";
}
}
class Orange { // 오렌지를 단순히 표현한 클래스
public String toString() {
return "I am an arange";
}
}
class Box { // 무엇이든 담을 수 있는 상자
private Object ob;
public void set(Object o) {
ob = o;
}
public Object get() {
return ob;
}
}
class FruitBox {
public static void main(String[] args) {
Box aBox = new Box();
Box oBox = new Box();
aBox.set(new Apple()); // 상자에 사과를 담는다
oBox.set(new Orange()); // 상자에 오렌지를 담는다
// Box 인스턴스에서 내용물을 꺼낼 때 형 변환을 해야한다
Apple ap = (Apple)aBox.get(); // 상자에서 사과를 꺼낸다
Orange or = (Orange)oBox.get(); // 상자에서 오렌지를 꺼낸다
System.out.println(ap);
System.out.println(or);
}
}
// 실행 결과
I am an apple
I am an arange
class Apple {
public String toString() {
return "I am an apple";
}
}
class Orange {
public String toString() {
return "I am an arange";
}
}
class Box {
private Object ob;
public void set(Object o) {
ob = o;
}
public Object get() {
return ob;
}
}
class FruitBoxFault {
public static void main(String[] args) {
Box aBox = new Box();
Box oBox = new Box();
/* 프로그래머의 실수 */
// 아래 두 문장에서는 사과와 오렌지가 아닌 "문자열"을 담았다.
aBox.set("Apple");
oBox.set("Orange");
/* 실수가 드러나지 않음 */
System.out.println(aBox.get());
System.out.println(oBox.get());
}
}
// 실행 결과
Apple
Orange
제네릭 이후의 코드
👉🏻 제네릭 이후 예제(1)
- 형 변환을 해줘야 한다는 번거러움이 없다.
/* 관련 예제 */
class Apple {
public String toString() {
return "I am an apple";
}
}
class Orange {
public String toString() {
return "I am an arange";
}
}
// 제네릭 기반의 코드
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class FruitBox_Generic {
public static void main(String[] args) {
Box<Apple> aBox = new Box<Apple>(); // T를 Apple로 결정
Box<Orange> oBox = new Box<Orange>(); // T를 Orange로 결정
aBox.set(new Apple()); // 사과를 상자에 담는다
oBox.set(new Orange()); // 오렌지를 상자에 담는다
Apple ap = aBox.get(); // 사과를 꺼내는데 형 변환이 필요 없다
Orange or = oBox.get(); // 오렌지를 꺼내는데 형 변환이 필요 없다
System.out.println(ap);
System.out.println(or);
}
}
// 실행 결과
I am an apple
I am an arange
👉🏻 제네릭 이후 예제(2)
- 실수가 컴파일 오류로 이어져 코드의 안정성이 높아진다.
class Apple {
public String toString() {
return "I am an apple";
}
}
class Orange {
public String toString() {
return "I am an arange";
}
}
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class FruitBoxFault_Generic {
public static void main(String[] args) {
Box<Apple> aBox = new Box<Apple>();
Box<Orange> oBox = new Box<Orange>();
aBox.set("Apple"); // 프로그래머의 실수가 컴파일 오류로 이어짐
oBox.set("Orange"); // 프로그래머의 실수가 컴파일 오류로 이어짐
Apple ap = aBox.get();
Orange or = oBox.get();
System.out.println(ap);
System.out.println(or);
}
}
// 실행 결과
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
The method set(Apple) in the type Box<Apple> is not applicable for the arguments (String)
The method set(Orange) in the type Box<Orange> is not applicable for the arguments (String)
at ch21.FruitBoxFault_Generic.main(FruitBoxFault_Generic.java:34)
제네릭 기본 문법
타입 매개변수의 이름
- 한 문자로 이름을 짓는다.
- 대문자로 이름을 짓는다.
- 보편적인 선택
E Element K Key N Number T Type V Value
다중 매개변수 기반의 제네릭
- 타입 매개변수가 둘 이상인 제네릭 클래스를 정의할 경우 쉼표( , )로 구분하여 명시해준다.
class MBox<L,R> {
private L left;
private R right;
public void set(L l, R r) {
left = l;
right = r;
}
}
Box<String, Integer> box = new Box<String, Integer>();
기본 자료형에 대한 제한
- 제네릭 클래스에 대하여 매개변수화 타입을 구성할때 기본 자료형의 이름은 타입인자로 쓸 수 없다.
- 이때는 기본 자료형에 대한 레퍼 클래스를 사용해야만 한다.
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class PrimitiveDataGeneric {
public static void main(String[] args) {
Box<Integer> IBox = new Box<Integer>();
IBox.set(125); // 오토 박싱 진행
int num = IBox.get(); // 오토 언박싱 진행
System.out.println(num);
}
}
// 실행 결과
125
다이아몬드 기호
- <>를 다이아몬드 기호라 한다.
- 자바 7부터 인스턴스 생성시 타입을 추정할 수 있는 경우 타입을 생략할 수 있다.
- 컴파일러가 프로그래머가 작성하는 제네릭 관련 문장에서 자료형의 이름을 추론하는 능력을 갖고 있기 때문이다.
// Box<Apple> aBox = new Box<Apple>();
Box<Apple> aBox = new Box<>();
매개변수화 타입을 타입인자로 전달
- Box<String>과 같은 매개변수화 타입이 타입 인자로 사용될 수 있다.
Box<Box<String>> wBox = new Box<>();
더보기
class Box<T> {
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
public class BoxInBox {
public static void main(String[] args) {
Box<String> sBox = new Box<>();
sBox.set("I am so happy");
Box<Box<String>> wBox = new Box<>();
wBox.set(sBox);
Box<Box<Box<String>>> zBox = new Box<>();
zBox.set(wBox);
System.out.println(zBox.get().get().get());
}
}
// 실행 결과
I am so happy
타입 매개변수의 제한
💡 클래스로 제한
- 제네릭은 'T'와 같은 타입 매개변수를 사용하여 타입을 제한한다.
- 이때 extends를 사용해 타입 매개변수에 특정 타입만을 사용하도록 제한할 수 있다.
// Number 또는 이를 상속하는 클래스만이 타입 매개변수(T)로 올 수 있다.
class Box<T extends Number> {...}
더보기
class Box<T extends Number> { // Number 또는 이를 상속하는 클래스만 T로 올 수 있음
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
class TypeArguLimit {
public static void main(String[] args) {
Box<Integer> iBox = new Box<>(); // Integer는 Number를 상속
iBox.set(24);
Box<Double> dBox = new Box<>(); // Double은 Number를 상속
dBox.set(5.97);
System.out.println(iBox.get());
System.out.println(dBox.get());
}
}
// 실행 결과
24
5.97
💡 인터페이스로 제한
- 제네릭 클래스의 타입 매개변수를 인터페이스의 이름으로 제한할 수 있다.
- 클래스가 아닌 인터페이스를 구현할 경우에도 implements 키워드가 아닌 extends를 사용해야 한다.
// Eatable 인터페이스를 구현하는 클래스만이 타입 매개변수(T)로 올 수 있다.
class Box<T extends Eatable> {...}
더보기
interface Eatable {
public String eat();
}
class Apple implements Eatable {
public String toString() {
return "I am an apple";
}
@Override
public String eat() {
return "It tastes so good";
}
}
class Box<T extends Eatable> { // 타입 인자를 Eatable 인터페이스로 제한
T ob;
public void set(T o) {
ob = o;
}
public T get() {
System.out.println(ob.eat()); // Eatable로 제한했기에 eat메소드 호출 가능
return ob;
}
}
class TypeArguLimit_Interface {
public static void main(String[] args) {
Box<Apple> box = new Box<>();
box.set(new Apple()); // 사과 저장
Apple ap = box.get(); // 사과 꺼내기
System.out.println(ap);
}
}
// 실행 결과
It tastes so good
I am an apple
💡 인터페이스로 제한
- 제네릭 클래스의 타입 매개변수를 인터페이스의 이름으로 제한할 수 있다.
- 클래스가 아닌 인터페이스를 구현할 경우에도 implements 키워드가 아닌 extends를 사용해야 한다.
// Number 클래스를 상속하면서 동시에 Eatable 인터페이스를 구현하는 클래스만이 타입 매개변수(T)로 올 수 있다.
class Box<T extends Number & Eatable> {...}
제네릭 메소드
- 메소드가 호출 시점에 제네릭 메소드의 T를 결정한다.
- 전달받는 타입 인자를 통해 타입 인자 생략 가능
Reference
http://www.tcpschool.com/java/java_generic_concept
'☕ Java > 이론' 카테고리의 다른 글
[Java] Map 컬렉션 클래스 (0) | 2022.01.25 |
---|---|
[Java] 컬렉션 프레임워크 (0) | 2022.01.18 |
[Java] 제네릭3 (와일드 카드) (0) | 2022.01.17 |
[Java] 제네릭2 (상속, 제네릭 인터페이스) (0) | 2022.01.14 |
[Java] Arrays 클래스 (0) | 2022.01.12 |
[Java] 기본 클래스 (Math, Random, StringTokenizer) (0) | 2022.01.12 |
[Java] BigInteger, BigDecimal 클래스 (0) | 2022.01.11 |
[Java] Wrapper, Number 클래스 (0) | 2022.01.11 |