본문 바로가기

Java 심화/2. 제네릭 < >

1. Java 자바 제네릭 - 제네릭 (Generic) 타입

 

 

Java 5 부터 제네릭 (Generic) 타입이 새로 추가되었다.

 

제네릭을 사용해서 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다.

 

제네릭은 클래스, 인터페이스, 메소드를 정의할 때 타입(type) 을 파라미터로 사용할 수 있도록 한다.

 

타입 파라미터는 코드 작성 시 구체적인 타입으로 대체되어서 다양한 코드를 생성하도록 해준다.

 


1. 제네릭의 장점

 

- 컴파일 시 강한 타입 체크를 할 수 있다.

 

  컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해

  제네릭 코드에 대해 강한 타입 체크를 한다.

 

  실행 시 타입 에러가 나는 것보다 컴파일 시에 미리 타입을 강하게 체크해서 에러를 사전에 방지한다.

 

- 타입 변환(casting) 을 제거한다.

 

  비 제네릭 코드는 불필요한 타입 변환을 하기 때문에 성능에 악영향을 미친다.

 

  예)

 

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);   // 타입 변환을 하지 않는다. (필요X)

 

 


2. 제네릭 타입 Class<T>, interface<T>

 

타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.

 

제네릭 타입은 클래스, 인터페이스 이름 뒤에 "< >" 부호가 붙고, 사이에 타입 파라미터 T 가 위치한다.

 

public class 클래스명<T> { . . . }

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

 

- 타입 파라미터는 변수명과 동일한 규칙에 따라 작성한다.

  (일반적으로 대소문자 알파벳 한 글자로 표현)

 

 

① Object 클래스를 사용하는 경우

 

예) Box 클래스

 

public class Box {
    private Object object;
    public void set(Object object) { this.object = object; }
    public Object get() { return 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 타입을 String 타입으로 강제 타입 변환을 해서 얻어야 한다.

 

 

예) Box.java : Box 클래스

 

public class Box {
    private Object object;
    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

 

Apple.java : Apple 클래스

 

public class Apple {
}

 

BoxExample.java : 비 제네릭 타입 이용 실행 클래스

 

public class BoxExample {
    public static void main(String[] args) {
        Box box = new Box();
        
        box.set("홍길동");                   // String -> Object 자동 타입 변환
        String name = (String) box.get();    // Object -> String 강제 타입 변환
        
        box.set(new Apple());                // Apple -> Object 자동 타입 변환
        Apple apple = (Apple) box.get();     // Object -> Apple 강제 타입 변환
    }
}

 

Object 타입을 사용하면 모든 종류의 자바 객체를 저장할 수 있는 장점이 있지만,

 

저장할 때 타입 변환이 발생, 읽어올 때도 타입 변환이 발생한다.

 

이러한 타입 변환이 빈번해지면 전체 프로그램 성능에 악영향을 미치게된다.

 

 

② 제네릭 사용

 

Box.java

 

public class Box<T> {
    private T t;
    public T get() { return t; }
    public void set(T t) { this.t = t; }
}

 

타입 파라미터를 사용해서 Object 타입을 모두 T 로 대체했다.

 

T 는 Box 클래스로 객체를 생성할 때 구체적인 타입으로 변경된다.

 

Box<String> box = new Box<String>();   // Box 객체 생성

 

타입 파라미터 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 객체를 생성한다면??

Integer 는 int 값에 대한 객체 타입으로 자바에서 제공하는 표준 API 이다. (래퍼 객체)

 

Box<Integer> box = new Box<Integer>();

 

타입 파라미터 T 는 Integer 타입으로 변경되어 Box 클래스는 내부적으로 아래와 같이 자동으로 재구성된다.

 

public class Box<Integer> {
    private Integer t;
    public void set(Integer t) { this.t = t; }
    public Integer get() { return t; }
}

 

필드 타입 : Integer 로 변경

set() 메소드의 매개값 타입 : Integer 로 변경

get() 메소드의 리턴 타입 : Integer 로 변경

 

따라서 아래 코드를 실행하면 타입 변환이 전혀 발생하지 않는다.

 

Box<Integer> box = new Box<Integer> {
    private Integer t;
    public void set(Integer t) { this.t = t; }
    public Integer get() { return t; }
}

 

 

제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고, 타입 파라미터로 대체했다가,

실제 클래스가 사용될 때 (객체 생성 시) 구체적인 타입을 지정함으로써 타입 변환을 최소화한다.

 

예)

 

Box.java : 제네릭 타입

 

public class Box<T> {
    private T t;
    public T get() { return t; }
    public void set(T t) { this.t = t }
}

 

BoxExample.java : 제네릭 타입 이용 실행 클래스

 

public class BoxExample {
    public static void main(String[] args) {
        Box<String> box1 = new Box<String>();
        box1.set("hello");
        String str = box1.get();
        
        Box<String> box2 = new Box<Integer>();
        box2.set(6);
        
        int value = box2.get();
    }
}