Section1-제네릭(Generic)

솜씨좋은 개발자·2022년 7월 22일
0

Section1

목록 보기
17/18

📖 학습 목표

  • 제네릭
  • 제네릭 클래스
  • 제네릭 메서드

✍ 제네릭

타입을 구체적으로 지정하는 것이 아니라, 추후에 지정할 수 있도록 일반화해두는 것을 의미합니다. 즉, 작성한 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둔 것을 의미합니다.

아래 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;
    }
}

단 하나의 Basket 클래스만으로 모든 타입의 데이터를 저장할 수 있는 인스턴스를 만들 수 있다.

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;
    }
}

Basket 클래스 내의 T를 String으로 바꿔라”
위 코드를 실행하면 Basket 클래스 내부의 T가 모두 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;
    }
}

<> 안에 Integer를 넣어 인스턴스화한다면 Basket 클래스 내부의 T는 모두 Integer로 치환된다.

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

// 위와 같이 인스턴스화하면 Basket 클래스는 아래와 같이 변환됩니다. 
class Basket<Integer> {
    private Integer item;

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

    public Integer getItem() {
        return item;
    }

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

✍ 제네릭 클래스

제네릭이 사용된 클래스를 제네릭 클래스라고 한다.

class Basket<T> {
    private T item;

    ...
}

타입 매개변수를 여러 개 사용해야 한다면

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

다만, 클래스 변수에는 타입 매개변수를 사용할 수 없다.

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

만약, 클래스 변수에 타입 매개변수를 사용할 수 있다면 클래스 변수의 타입이 인스턴스 별로 달라지게 된다.

즉, 클래스 변수에 타입 매개변수를 사용할 수 있다면, Basket으로 만든 인스턴스와, Basket로 만든 인스턴스가 공유하는 클래스 변수의 타입이 서로 달라지게 되어, 클래스 변수를 통해 같은 변수를 공유하는 것이 아니게 된다.

제네릭 클래스는 멤버를 구성하는 코드에 특정한 타입이 지정되지 않은 클래스이므로, 제네릭 클래스를 인스턴스화할 때에는 의도하고자 하는 타입을 아래와 같이 지정해주어야 한다.

단, 타입 매개변수에 치환될 타입으로 기본 타입을 지정할 수 없다. 만약, int, double과 같은 원시 타입을 지정해야 하는 맥락에서는 Integer, Double과 같은 래퍼 클래스를 활용한다.

Basket<String>  basket1 = new Basket<String>("Hello");
Basket<Integer> basket2 = new Basket<Integer>(10);
Basket<Double>  basket3 = new Basket<Double>(3.14);

위의 코드에서 new Basket<…>은 참조변수의 타입으로부터 유추할 수 있기 때문 구체적인 타입을 생략할 수 있다.

Basket<String>  basket1 = new Basket<>("Hello");
Basket<Integer> basket2 = new Basket<>(10);
Basket<Double>  basket2 = new Basket<>(3.14);

제네릭 클래스를 사용할 때에도 다형성을 적용할 수 있다.

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 static void main(String[] args) {
		Basket<Flower> flowerBasket = new Basket<>();
		flowerBasket.setItem(new Rose());      // 다형성 적용
		flowerBasket.setItem(new RosePasta()); // 에러
}

new Rose()를 통해 생성된 인스턴스는 Rose 타입이며, Rose 클래스는 Flower 클래스를 상속받고 있으므로, Basket의 item에 할당될 수 있다. Basket은 결국 item의 타입을 Flower로 지정하는 것이고, Flower 클래스는 Rose 클래스의 상위 클래스이기 때문이다.

반면, new RosePasta()를 통해 생성된 인스턴스는 RosePasta 타입이며, RosePasta 클래스는 Flower 클래스와 아무런 관계가 없다. 따라서, flowerBasket의 item에 할당될 수 없니다.

제한된 제네릭 클래스

타입 매개변수를 선언할 때 아래와 같이 코드를 작성해주면 Basket 클래스를 인스턴스화할 때 타입으로 Flower 클래스의 하위 클래스만 지정하도록 제한된다.

class Flower { ... }
class Rose extends Flower { ... }
class RosePasta { ... }

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

public static void main(String[] args) {

		// 인스턴스화 
		Basket<Rose> roseBasket = new Basket<>();
		Basket<RosePasta> rosePastaBasket = new Basket<>(); // 에러
}

특정 클래스를 상속받은 클래스만 타입으로 지정할 수 있도록 제한하는 것뿐만 아니라, 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한할 수도 있다. 이 경우에도 동일하게 extends 키워드를 사용한다.

interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }

class Basket<T extends Plant> {
    private T item;
	
		...
}

public static void main(String[] args) {

		// 인스턴스화 
		Basket<Flower> flowerBasket = new Basket<>();
		Basket<Rose> roseBasket = new Basket<>();
}

만약, 특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한하려면 아래와 같이 &를 사용하여 코드를 작성해주면 된다.

interface Plant { ... }
class Flower implements Plant { ... }
class Rose extends Flower implements Plant { ... }

class Basket<T extends Plant & Flower> {
    private T item;
	
		...
}

public static void main(String[] args) {

		// 인스턴스화 
		Basket<Flower> flowerBasket = new Basket<>();
		Basket<Rose> roseBasket = new Basket<>();
}

✍ 제네릭 메서드

클래스 전체를 제네릭으로 선언할 수도 있지만, 클래스 내부의 특정 메서드만 제네릭으로 선언할 수 있다. 이를 제네릭 메서드라고 한다.

제네릭 메서드의 타입 매개변수 선언은 반환타입 앞에서 이루어지며, 해당 메서드 내에서만 선언한 타입 매개변수를 사용할 수 있습니다.

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

제네릭 메서드의 타입 매개변수는 제네릭 클래스의 타입 매개변수와 별개의 것이다. 즉, 아래와 같이 동일하게 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()); // 불가
    }
}

하지만 모든 클래스는 최상위 클래스인 Object 클래스를 상속받기 때문에Object 클래스의 메서드는 사용가능하다. equals(), toString() 등이 Object 클래스의 메서드이다.

class Basket {
    public <T> void getPrint(T item) {
        System.out.println(item.equals("Kim coding")); // 가능
    }
}
profile
개발의 방으로

0개의 댓글

관련 채용 정보