티스토리 뷰

Java+Spring/Java

[Java]제네릭(Generic)

Vagabund.Gni 2022. 7. 15. 22:51
728x90
반응형

제네릭(Generic)

 

제네릭(Generic)은 '일반적인'이라는 뜻을 지니고 있다.

 

자바에서 제네릭이란 타입을 내부에서 지정하는 것이 아닌 외부에서 사용자가 지정하는 것을 말한다.

 

필요에 의해 지정할 수 있는 일반(Generic) 타입을 사용한다는 의미이다.

 

예를 들면, 아래 Basket 클래스는 오직 String 타입만을 저장할 수 있다.

 

따라서 여러 타입의 데이터를 저장할 수 있는 객체를 만들려면, 그 수만큼 클래스를 생성해야 한다.

class Basket {
    private String item;

    Basket(String item) {
        this.item = item;
    }

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }
}

이런 상황에서 필요한 것이 제네릭이다. 제네릭을 사용하면

 

하나의 클래스로 모든 타입의 데이터를 저장할 수 있는 객체를 만들 수 있다.

class Basket<T> {
    private T item;

    public Basket(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

위 코드를 비교해서 살펴보면, 우선 클래스 이름 옆에 <T>가 붙었으며

 

데이터의 타입을 가리키는 키워드들이 T로 치환된 것을 확인할 수 있다.

 

또한 위 클래스는 다음과 같이 인스턴스화 할 수 있다.

Basket<String> basket1 = new Basket<String>("기타줄");

데이터 타입을 String이 아닌 정수형으로 바꾸고 싶으면 아래와 같이 하면 된다.

Basket<Integer> basket2 = new Basket<Integer>(42);

여기서 타입을 나타내는 키워드로 int 대신 Integer를 사용했는데,

 

이는 제네릭 문법의 파라미터로 명시할 수 있는 것이 참조타입밖에 없기 때문이다.

 

따라서 int, double과 같은 기본타입들은 래퍼 클래스(Wrapper Class)인 Integer, Double과 같이 사용해야 한다.

 


제네릭 클래스(Generic Class)

 

제네릭 클래스는 제네릭이 사용된 클래스를 말한다.

 

위에서 살펴본 Basket 클래스가 그 쉬운 예가 될 것이다.

 

여기서 <T>를 타입 매개변수라고 하며, 한 클래스에서 여러 개의 타입 매개변수를 사용할 수도 있다.

class Basket<T, V> { ... }

클래스에서 타입 매개변수를 선언해주고 나면,

 

위의 예에서 볼 수 있듯이 그 매개변수를 원하는 타입으로 사용할 수 있게 된다.

 

여기서 주의할 한 가지는, 클래스 변수에는 타입 매개변수를 사용할 수 없다는 점인데,

class Basket<T> {
	private T item1; // O 
	static  T item2; // X 
}

이는 클래스 변수가 모든 인스턴스에 공통으로 적용되는 변수이기 때문이다.

 

제네릭 클래스를 인스턴스화 하는 과정에서 원하는 타입을 지정해주어야 하는데,

 

그 방법을 다시 보자면 다음과 같다.

        Basket<String> basket1 = new Basket<String>("기타줄");
        Basket<Integer> basket2 = new Basket<Integer>(42);
        Basket<Boolean> basket3 = new Basket<Boolean>(true);
        Basket<Double> basket4 = new Basket<>(2.71); // 생략 가능

원하는 타입을 <>안에 적었으며, new Basket<> 내부의 타입은 생략이 가능함을 볼 수 있다.

 

또한, 제네릭 클래스에도 다형성을 적용할 수 있는데, 그 예는 다음과 같다.

class Flower {}
class Rose extends Flower {}
class RosePasta {}
class Basket<T> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

public class Main {
    public static void main(String[] args) {

        Basket<Flower> flowerBasket = new Basket<>();
        flowerBasket.setItem(new Rose());	   // 다형성 적용
        flowerBasket.setItem(new RosePasta()); // 에러

짧게 복습하자면 다형성이란 상위 클래스 타입의 참조변수를 통해 하위 클래스의 객체를 참조할 수 있도록 허용된 것이다.

 

위 예에서 Basket<Flower>를 통해 생성된 인스턴스 flowerBasket의 item의 타입은 Flower이며,

 

이를 상속받은 Rose 클래스는 item에 할당될 수 있으나 RosePasta 클래스는 그렇지 않아 에러가 나는 것을 볼 수 있다.

 

계속해서 타입 변수를 제한하는 방법에 대해 알아보자.

class Flower {}
class Rose extends Flower {}
class RosePasta {}
class Basket<T extends Flower> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

public class Main {
    public static void main(String[] args) {

        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();
        Basket<RosePasta> rosePastaBasket = new Basket<>(); // 에러

위 예를 살펴보면 Basket의 타입 변수 자리에 <T extends Flower>가 들어온 것을 볼 수 있다.

 

이는 Basket 클래스를 인스턴스화 할 때 타입으로 Flower의 하위 클래스만 올 수 있게 지정하는 것이다.

 

때문에 마지막 라인의 Basket<RosePasta> 타입의 객체를 생성하려고 시도할 때 에러가 나는 것이다.

interface Plant {}
class Flower implements Plant{}
class Rose extends Flower implements Plant {}
class Basket<T extends Plant> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

public class Main {
    public static void main(String[] args) {

        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();

이번에는 특정 클래스가 아닌 특정 인터페이스를 구현한 클래스만 타입으로 받는 예이다.

 

특이한 점은 타입 변수에 <T implements Plant>가 아닌 <T extends Plant>가 쓰인다는 점이다.

interface Plant {}
class Flower implements Plant{}
class Rose extends Flower implements Plant {}
class RosePasta {}
class Basket<T extends Flower & Plant> {
    private T item;

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

public class Main {
    public static void main(String[] args) {

        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();

마지막으로, 특정 클래스를 상속 받으면서 동시에 특정 인터페이스를 구현한 클래스 타입으로

 

타입 변수를 지정하고 싶은 경우, &를 사용하면 된다.

 


제네릭 메서드(Generic Method)

 

클래스 전체가 아닌, 특정 메서드만 제네릭으로 선언할 수도 있다.

 

이를 제네릭 메서드라고 부르며, 선언 방법은 다음과 같다.

class Basket {
		...
    public <T> void add(T element) {
				...
    }
}

제네릭 메서드의 타입 선언은 반환타입 앞에서 이루어지며,

 

여기에 붙어있는 <T>는 제네릭 클래스에 붙는 <T>와는 별개의 존재이다.

class Basket<T> {                    // 1 : 여기에서 선언한 타입 매개변수 T와 
		...
    public <T> void add(T element) { // 2 : 여기에서 선언한 타입 매개변수 T는 서로 다른 것
				...
    }
}

이는 외부에서 타입을 지정하는 타이밍이 다르기 때문인데,

 

제네릭 클래스명에 붙은 타입 매개변수는 클래스가 인스턴스화 될 때,

 

제네릭 메서드의 타입 지정은 메서드가 호출될 때 이루어진다.

Basket<String> basket = new Bakset<>(); // 위 예제의 1의 T가 String으로 지정 
basket.<Integer>add(10);                // 위 예제의 2의 T가 Integer로 지정 
basket.add(10);                         // 타입 지정 생략 가능

또한 클래스 타입 매개변수와 달리, 메서드의 경우는 static 메서드 앞에도 사용될 수 있다.

class Basket {
		...
    static <T> int setPrice(T element) {
				...
    }
}

마지막으로 제네릭 메서드 안에서는 length()와 같은 String 클래스의 메서드들을 사용할 수 없는데,

 

이는 당연하게도 호출되기 전까지 메서드의 제네릭 타입이 정해지지 않기 때문이다.

class Basket {
    public <T> void print(T item) {
        System.out.println(item.length()); // 불가
    }
}

하지만 equals(), toString() 등 Object 클래스의 메서드는 사용할 수 있으며,

 

이는 모든 클래스가 공통으로 Object를 상속받기 때문이다.

class Basket {
    public <T> void getPrint(T item) {
        System.out.println(item.equals("Kim coding")); // 가능
    }
}
반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함