--> [Java] 자바 - 제네릭(Generic)

[Java] 자바 - 제네릭(Generic)

자바(Java)에서 제네릭(Generic)은 클래스 내부에서 사용하는 데이터의 타입(Type)을 클래스의 인스턴스를 생성할 때 결정하는 것을 의미합니다. 객체의 타입을 컴파일 시점에 체크하기 때문에 타입 안정성을 높이고 형 변환의 번거로움을 줄일 수 있습니다. 아래 간략하게 제네릭(Generic)을 사용하여 선언된 클래스와 객체를 선언한 부분을 살펴보겠습니다.

public class TestGeneric<T> 
{
    public T sample;

    public void showYourType()
    {
        if(sample instanceof Integer)
            System.out.println("Integer 타입이군요!!");
       	else if(sample instanceof String)
            System.out.println("String 타입이군요!!");
    }
}

▼ TestGeneric 클래스의 멤버변수인 sample 멤버 변수는 T라는 Type을 가집니다. 하지만 T라는 타입은 존재하지 않는 타입이죠. T 타입은 나중에 TestGeneric 클래스의 인스턴스가 생성될 때 결정이 됩니다. TestGeneric 객체를 생성하는 main 함수 영역을 보죠.

public class Main{
    public static void main(String[] args)
    {
        TestGeneric<String> stringType = new TestGeneric<String>();
        TestGeneric<Integer> integerType = new TestGeneric<Integer>();

		stringType.sample = "Hello";
		integerType.sample = 1;

        stringType.showYourType();
        integerType.showYourType();
    }
}

▼ stringType 객체 선언부를 보시면 <> 안에 타입을 지정하게 되는데 이때 들어가는 타입이 멤버변수 sample의 타입으로 결정이 됩니다. 즉 stringType 객체의 sample 멤버 변수의 타입은 String이 되는 셈이죠.

 

▼ integerType 객체의 sample 멤버 변수의 타입은 Integer가 됩니다.

 

main 실행 결과

▼ 이런 식으로 객체 생성 시점에 타입이 결정되기 때문에 의도하지 않은 타입의 객체 저장을 막을 수 있고 명시적으로 타입을 지정하였기 때문에 객체를 참조할 때 명시적으로 형 변환을 해줄 필요가 없습니다. 대표적으로 우리가 자주 보던 제네릭 기반의 클래스로 ArrayList가 존재합니다. 

 

public class Main{
    public static void main(String[] args)
    {
       ArrayList<String> list = new ArrayList<String>();

       list.add("Hello");
       list.add("World");

       for(int i = 0 ; i < list.size(); i++)
       {
		   System.out.println(list.get(i));
	   }
    }
}

 1. 복수 제네릭

위에서는 한 개의 제네릭을 사용하지만 복수개의 제네릭을 사용하는 것도 가능합니다. 

public class TestGeneric<T, K>
{
    public T sample;
    public K sapmle2;
}

▼ 제네릭의 구분은 쉼표(,)를 통해 하며 <T, K>와 같은 형식으로 사용하며 어떠한 문자도 사용 가능합니다. 위 코드에서 T 타입은 sample 멤버 변수의 타입이 되고 K 타입은 sample 멤버변수의 타입이됩니다. T와 K 둘다 아래와 같이 TestGeneric 클래스의 객체가 생성될 때 결정됩니다.

public class Main{
    public static void main(String[] args)
    {
       TestGeneric<String, Integer> generic = new TestGeneric<String, Integer>();
    }
}

▼ generic 객체의 sample 멤버변수의 타입은 String 타입이며 sample2 멤버변수의 타입은 Integer로 결정이 됩니다.


2. 제네릭(Generic)의 특징

2.1 객체 생성이 가능한 타입에 대해서만 제네릭(Generic) 사용이 가능하다

제네릭(Generic)은 기본 데이터 타입(int, long..)에 대해서는 지정이 불가능합니다. 다만 기본 타입을 객체 타입으로 사용하는 Wrapper 클래스(Integer, Boolean..)에는 제네릭(Generic) 사용이 가능합니다.

public class Main{
    public static void main(String[] args)
    {
       TestGeneric<int, long> generic = new TestGeneric<int, long>();
    }
}

▼ 위와 같이 기본 데이터 타입인 int와 long을 제네릭으로 사용하게 되면 컴파일 에러가 발생합니다.


2.2 제네릭(Generic) 생략

public class Data<T> {
    public T data;

    public Data(T data)
    {
        this.data = data;
    }
}
public class Main{
    public static void main(String[] args)
    {
        Data data = new Data("Hello World");
        Data<String> data2 = new Data("Hello World");
    }
}

▼ Data 클래스의 인스턴스 data를 생성할 때 생성자 함수 인자 정보로 String형 데이터를 넘겨줘서 Data의 data 멤버 변수의 타입을 알기 때문에 제네릭 생략이 가능합니다. data와 data2 객체를 생성하는 코드는 정확하게 같은 동작을 수행하게 됩니다.


3. 제네릭(Generic) 메서드

클래스의 메서드에서도 제네릭 메서드를 정의할 수 있으며 타입 매개변수의 사용은 메소드 내부로 제한됩니다.

public class Data {

    public static <T> T showData(T data)
    {
        if(data instanceof String)
            System.out.println("String");
        else if(data instanceof Integer)
            System.out.println("Integer");
        else if(data instanceof Double)
            System.out.println("Double");

        return data;
    }
}
public class Main{
    public static void main(String[] args)
    {
        Data.<String>showData("Hello World");
        Data.showData(1);
        Data.showData(1.0);
    }
}

▼ 제네릭 메소드를 호출할 때는 실제 타입을 <> 안에 넣어줘도 되고 생략을 해도 됩니다. 


4. 타입 매개변수 제한

제네릭의 타입으로 특정 클래스나 자식 클래스 타입만 오도록 타입 매개변수를 제한할 수 있습니다. 

public class Parent {
}

public class Child extends Parent {
}

public class Data<T extends Parent> {
}

public class Main{
    public static void main(String[] args)
    {
        Data<Parent> data = new Data();
        Data<Child> data2 = new Data();
        //Data<String> data3= new Data(); //컴파일 오류가 난다
    }
}

▼ Data 클래스의 제네릭 매개변수 타입을 보면 <T extends parent>로 정의되어 있습니다. 이를 해석하자면 T 매개변수의 타입은 parent 객체 타입이거나 parent 클래스를 상속받는 클래스의 타입만 올 수 있도록 제한하겠다는 의미입니다. 실제로 이 타입 이외의 타입이 오는 경우 컴파일 시점에 오류가 발생합니다.

 

댓글(0)

Designed by JB FACTORY