Generic

제네릭이란?

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

class Person<T>{
    public T info; //T는 info의 데이터 타입
}
Person<String> p1 = new Person<String>(); //info가 String type이 된다.
Person<StringBuilder> p2 = new Person<StringBuilder>(); //T가 StringBuilder type이 된다.

 

[전체 코드]

package study0208;
class Person<T>{
    public T info;
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<String> p1 = new Person<String>();
        Person<StringBuilder> p2 = new Person<StringBuilder>();
    }
}

 


제네릭의 사용 이유

타입 안전성

아래 코드를 보자.

package study0208;

class StudentInfo{
    public int grade;
    StudentInfo(int grade){
        this.grade = grade;
    }
}
class StudentPerson{
    public StudentInfo info;
    StudentPerson(StudentInfo info){
        this.info = info;
    }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){
        this.rank = rank;
    }
}
class EmployeePerson{
    public EmployeeInfo info;
    EmployeePerson(EmployeeInfo info){
        this.info = info;
    }
}
public class GenericDemo2 {
    public static void main(String[] args) {
        StudentInfo si = new StudentInfo(90);
        StudentPerson sp = new StudentPerson(si);
        System.out.println(sp.info.grade); // 90
        EmployeeInfo ei = new EmployeeInfo(1);
        EmployeePerson ep = new EmployeePerson(ei);
        System.out.println(ep.info.rank); // 1
    }
}

StudentPerson과 EmployeePerson가 사실상 같은 구조를 가지고 있다. 중복이 발생하고 있는 것이다. 중복을 제거해보자.

 

package study0208;

class StudentInfo{
    public int grade;
    StudentInfo(int grade){
        this.grade = grade;
    }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){
        this.rank = rank;
    }
}
class Person1{
    public Object info;
    Person1(Object info){ //Object로 해버리는 바람에 엉뚱한 값이 들어갈 수 있음
        this.info = info;
    }
}
public class GenericDemo2 {
    public static void main(String[] args) {
        Person1 p1 = new Person1("부장"); //엉뚱한 값이 들어가는 것이 허용됨 => 타입이 안전하지 않다.
        EmployeeInfo ei = (EmployeeInfo) p1.info; //형변환해서 ei변수에 담음
        System.out.println(ei.rank);
    }
}

이 코드는 성공적으로 컴파일된다. 하지만 실행을 하면 아래와 같은 오류가 발생한다.

특정 클래스의 객체를 호환되지 않는 다른 클래스의 객체로 변환하려고 할 때 발생하는 런타임 에러

 

Person1 p1 = new Person1("부장");

클래스 Person의 생성자는 매개변수 info의 데이터 타입이 Object이다. 따라서 모든 객체가 될 수 있다. 그렇기 때문에 위와 EmployeeInfo의 객체가 아니라 String이 와도 컴파일 에러가 발생하지 않는다. 대신 런타임 에러가 발생한다. 컴파일 언어의 기본은 모든 에러는 컴파일이 발생할 수 있도록 유도해야 한다는 것이다. 런타임은 실제로 애플리케이션이 동작하고 있는 상황이기 때문에 런타임에 발생하는 에러는 항상 심각한 문제를 초래할 수 있기 때문이다. 

 

위와 같은 에러를 타입에 대해서 안전하지 않다고 한다. 즉 모든 타입이 올 수 있기 때문에 타입을 엄격하게 제한 할 수 없게 되는 것이다. 제네릭을 사용하는 이러한 문제가 해결된다.

 

제네릭의 편의성

  1. 타입이 안전하다.
  2. 코드의 중복을 제거한다.

 


제네릭의 특징1

복수의 제네릭

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

package generic;
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){
        this.rank = rank;
    }
}
class Person<T, S>{ //복수의 제네릭이 필요할 때는 중간에 ,를 찍고 서로 이름이 달라야 한다.
    //T와 S에는 기본 데이터 타입이 올 수 없다. → 객체인 것처럼 만들 수 있는 wrapper class 사용
    public T info; //EmployeeInfo info
    public S id; //int id
    Person(T info, S id){
        this.info = info; //new EmployeeInfo(1) 인스턴스가 들어옴
        this.id = id; //1이 들어옴
    }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(new EmployeeInfo(1),1);
        //<EmployeeInfo, Integer>: int 대신 래퍼 클래스인 Integer 사용
        System.out.println("p1.id: "+p1.id);
    }
}

new Person<EmployeeInfo, Integer>(new EmployeeInfo(1), 1)
실행 결과

래퍼 클래스(Wrapper Class)란?

 

자바의 자료형은 기본 타입(primitive type)참조 타입(reference type)으로 나뉜다. 기본 타입에는 char, int, float, double, boolean 등이 있고 참조 타입은 class, interface 등이 있는데 프로그래밍을 하다 보면 기본 타입의 데이터를 객체로 표현해야 하는 경우가 종종 있다. 기본 자료타입(primitive type)을 객체로 다루기 위해서 사용하는 클래스들 래퍼 클래스(wrapper class)라고 한다.

 

래퍼 클래스의 종류

기본 타입(primitive type) 래퍼 클래스(wrapper class)
byte Byte
char Character
int Integer
float Float
double Double
boolean Boolean
long Long
short Short

 


제네릭의 특징2

제네릭의 제한

package generic;
interface Info{
    public abstract int getLevel();
}
class EmployeeInfo2 implements Info{
    public int rank;
    //setter
    EmployeeInfo2(int rank){
        this.rank = rank;
    }
    //getter
    @Override
    public int getLevel(){
        return this.rank;
    }
}
class Person2<T extends Info>{ //<>안에서는 인터페이스여도 extends 사용 가능
    //T로 올 수 있는 데이터 타입을 Info 또는 Info의 자식들만 오도록 강제함
    public T info;
    //setter
    Person2(T info){
        this.info = info;
    }
}
public class GenericDemo2 {
    public static void main(String[] args) {
        Person2<EmployeeInfo2> p1 = new Person2<EmployeeInfo2>(new EmployeeInfo2(1));
        
        //Person2<String> p2 = new Person<String>("부장");
        //String은 Info의 자식이 아니므로 컴파일 오류 발생
    }
}

 

이 코드에서 중요한 부분은 다음과 같다.

class Person2<T extends Info>{

즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.

extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

 


기본 타입(primitive type)의 제네릭화

[예제]

import java.awt.Color;
class Car{
    String color;
    int capa;

    @Override
    public String toString() {
        return "Car [color=" + color + ", capa=" + capa + "]";
    }
}

class Truck<T1, T2, T3>{
    T1 weight;
    T2 distance;
    T3 color;

    @Override
    public String toString() {
        return "Truck [weight=" + weight + ", distance=" + distance + ", color=" + color + "]";
    }
}

public class GenericTest {
    public static void main(String[] args) {
        Car car1 = new Car();
        car1.color = "white";
        car1.capa = 2000;
        System.out.println(car1);

        Truck truck = new Truck();
        System.out.println(truck); //초기값: null

        Truck<Double, Integer, String> truck1 = new Truck<>();
        truck1.weight = 2.5;
        truck1.distance = 5000;
        truck1.color = "gray";
        System.out.println(truck1);

        Truck<String, Float, Color> truck2 = new Truck<>();
        truck2.weight = "매우 무거움";
        truck2.distance = 450.3f;
        truck2.color = Color.red;
        System.out.println(truck2);
    }
}

실행 결과

Truck<Double, Integer, String> truck1 = new Truck<>();
Truck<String, Float, Color> truck2 = new Truck<>();

기본 타입(primitive type) 자료형인 double, int, float을 객체로 다루기 위해서 래퍼 클래스를 사용했다.

double → Double

int → Integer

float → Float

+ Recent posts