제네릭

muz·2021년 4월 24일
1

Java

목록 보기
21/21
post-thumbnail

제네릭(Generic)

제네릭은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법이다.

package practice;

class Student<T> {
    public T info;
}

public class GenericDemo {
    public static void main(String[] args) {
        Student<String> s1 = new Student<String>();
        Student<StringBuilder> s2 = new Student<StringBuilder>();
    }
}

s1.info와 s2.info의 데이터 타입은 각각의 인스턴스를 생성할 때 사용한 <> 사이에 어떤 데이터 타입을 사용했느냐에 달려있다.

  • s1.info : String
  • s2.info : StringBuilder

public T info; : 클래스 Student의 필드 info의 데이터 타입은 T이다. 이 값은 class Student<T>{}의 T에서 정해진다. 여기서의 T는 Student<String> s1 = new Student<String>();<>안에 지정된 데이터 타입에 의해서 결정된다.

Student<String> s1 = new Student<String>();에서 Student<String> p1은 변수 p1의 데이터 타입을 정의하고 있고, new Student<String>();은 인스턴스를 생성하고 있다.

👓 제네릭이란 클래스를 정의할 때 info의 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 기능이다.

제네릭을 사용하는 이유

1. 타입 안정성

package practice;

class StudentInfo{
    public int grade;
    StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person{
    public Object info;
    Person(Object info) { this.info = info; }
}

public class GenericDemo {
    public static void main(String[] args) {
        Person p1 = new Person("부장");
        EmployeeInfo ei = (EmployeeInfo)p1.info;
        System.out.println("Rank is... " + ei.rank); // 1
    }
}

해당 코드를 컴파일하면 런타임 에러가 발생한다. 클래스 Persion의 생성자는 매개변수 info의 데이터 타입이 Object여서, 모든 객체가 될 수 있기 때문이다.

2. 제네릭화

package practice;

class StudentInfo{
    public int grade;
    StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T>{
    public T info;
    Person(T info) { this.info = info; }
}

public class GenericDemo {
    public static void main(String[] args) {
        Person<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));
        EmployeeInfo ei1 = p1.info;
        System.out.println("Rank is... " + ei1.rank); // 성공

        Person<String> p2 = new Person<String>("부장");
        String ei2 = p2.info;
        System.out.println(ei2.rank); // 컴파일 실패
    }
}

p2는 컴파일 오류가 발생한다. p2.info가 String이고, String은 rank 필드가 없는데 이를 호출하고 있기 때문이다. 이로써 제네릭을 사용하는 이유는 다음과 같다고 볼 수 있다.

  • 컴파일 단계에서 오류가 검출됨
  • 중복의 제거, 타입 안정성을 동시에 추구할 수 있음

제네릭의 특징

1. 복수의 제네릭

클래스 내에서 여러 개의 제네릭을 필요로 하는 경우가 있다.

package practice;

class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S>{
    public T info;
    public S id;
    Person(T info, S id){ 
        this.info = info; 
        this.id = id;
    }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);
    }
}

2. 기본 데이터 타입과 제네릭

제네릭은 참조 데이터 타입에 대해서만 쓸 수 있다. (기본 데이터 타입에서는 쓸 수 없음)

package practice;

class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S>{
    public T info;
    public S id;
    Person(T info, S id){ 
        this.info = info;
        this.id = id;
    }
}
public class GenericDemo {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);
        Integer i = new Integer(10);
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
        System.out.println(p1.id.intValue());
    }
}

new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 '래퍼(wrapper) 클래스'라고 한다. 이 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다.

3. 제네릭의 생략

제네릭은 생략 가능하다.

public class GenericDemo {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);
        Integer i = new Integer(10);
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
        Person p2 = new Person(e, i);
    }
}

e와 i의 데이터 타입을 이미 알고 있으므로 Person p2 = new Person(e, i); 만 작성해도 된다.

4. 메소드에 적용

package practice;

class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S>{
    public T info;
    public S id;
    Person(T info, S id){ 
        this.info = info;
        this.id = id;
    }
    public <U> void printInfo(U info){
        System.out.println(info);
    }
}
public class GenericDemo {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);
        Integer i = new Integer(10);
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
        p1.<EmployeeInfo>printInfo(e);
        p1.printInfo(e);
    }
}

5. 제네릭의 제한

extends

  • 제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다. (클래스나 자식 외에는 올 수 없다.)
  • 상속(extends)뿐만 아니라, 구현(implements)의 관계에서도 쓸 수 있다.

Reference
1. 제네릭

profile
Life is what i make up it 💨

0개의 댓글