본문 바로가기
Java

[자바] Generics 제네릭

by Sky Titan 2020. 9. 3.
728x90
 

Lesson: Generics (Updated) (The Java™ Tutorials > Learning the Java Language)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See JDK Release Notes for information about new fe

docs.oracle.com

Generics 제네릭

  • 클래스 내부에서 사용할 데이터 타입을 외부에서 임의로 명시할 수 있게 해주는 문법입니다.
  • 런타임 시 발생할 버그를 줄이고 최대한 컴파일 시에 미리 발견할 수 있게 해줍니다.

왜 제네릭을 쓰는가

  • 위에서 말했듯 제네릭은 클래스 혹은 인터페이스, 메소드를 정의할 때 데이터 타입을 파라미터 값처럼 받아올 수 있게 해줍니다.
  • 이렇게 받아온 데이터 타입은 다른 곳에서 재사용할 수 있습니다.
  • 이렇게 됨으로써 컴파일 시에 강력한 타입 체크가 가능하집니다.
  • 프로그래머들이 Collections의 다른 타입에서도 작동하는 generic 알고리즘을 구현할 수 있습니다.
  • 타입 캐스팅 (형 변환)이 필요 없습니다.

EX) 타입 캐스팅 사용 경우

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

EX) 제네릭 사용 경우

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast

 


Generic 유형

1. Non-Generic 클래스

public class Person {

    Object age;
    Object name;

    public Person(Object age, Object name) {
        this.age = age;
        this.name = name;
    }

    public Object getAge() {
        return age;
    }

    public void setAge(Object age) {
        this.age = age;
    }

    public Object getName() {
        return name;
    }

    public void setName(Object name) {
        this.name = name;
    }
}

 generic을 사용하지 않은 경우에는 Object를 사용해서 데이터를 받아오기 때문에 어떤 객체라도 자유롭게 넘길 수 있지만 컴파일 단계에서는 발견할 수 없는 오류가 발생할 수 있습니다.

 

 예를 들어 보겠습니다.

public class Main {


    public static void main(String[] args) {

        Person person = new Person(0, "Park");
        
        Integer name = (Integer)person.name;
    }
}

 위의 코드는 작성단계에서는 오류를 발견할 수 없습니다. 하지만 String으로 넘긴 값을 Integer형으로 타입 캐스팅을 하려고 하기 때문에 실행 시 ClassCastException이 발생하게 됩니다.

 

2. Generic 사용 클래스

public class Person <T1, T2> {

    T1 age;
    T2 name;

    public Person(T1 age, T2 name) {
        this.age = age;
        this.name = name;
    }

    public T1 getAge() {
        return age;
    }

    public void setAge(T1 age) {
        this.age = age;
    }

    public T2 getName() {
        return name;
    }

    public void setName(T2 name) {
        this.name = name;
    }
}

 이번엔 Generic을 사용한 클래스를 보겠습니다. age와 name 속성의 타입을 각각 T1, T2라는 Generic 변수로 받아옵니다.

 

 그리고 위와 같은 코드를 똑같이 사용해보겠습니다. 아까 Generic을 사용하지 않은 클래스랑 다르게 컴파일 단계에서부터 타입 캐스팅이 불가능하다고 에러가 발생합니다.

 이처럼 Generic을 사용하면 실제 실행할 때까지 에러를 가지고 가지 않고 코드 작성단계에서 미리 탐지하고 해결할 수 있습니다.

 

 

3. Type 파라미터의 네이밍 규칙

 관례적으로 Type 파라미터의 이름은 한 글자의 대문자로 이루어집니다. 이는 일부러 기존의 변수 이름을 짓는 관습과 차이를 두어서 Type 파라미터와 일반 변수 파라미터의 차이를 개발자가 의식할 수 있게 하기 위함입니다.

 아래는 가장 보편적으로 사용되는 파라미터 이름들입니다.

  • E - Element (자바 Collections 프레임워크에서 널리 쓰입니다.)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S, U, V 등 - 2번째, 3번째, 4번째 타입

 


Generic 메서드

 제네릭 메서드는 그들의 고유한 타입 파라미터를 알려주는 메서드입니다. static, non-static 메서드 둘 다에서 가능합니다.

 문법적으로 제네릭 메서드의 타입 파라미터 리스트는 return 타입 전에 선언되어야합니다.

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

public class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

 

 참고로 메서드 타입 선언 부분은 아래와 같이 생략이 가능합니다.

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
728x90

댓글