본문 바로가기

개발지/Today I learn

[0808] 자바 컬렉션 2 (제네릭)

#제네릭

: 클래스와 메서드의 코드를 작성할 때, 타입을 추후에 지정할 수 있도록 일반화해 두는 것.

- 작성한 클래스와 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해 둔 것.

- 제네릭의 활용

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> { // 제네릭이 사용된 코드
	priivate T item;
    
	public Basket(T item) {
		this.itemp = item;
	}
    
	public T getItem() {
		return item;
	}
    
	public void setItem(T item) {
		this.item = item;
	}
}

- 제너릭 코드를 사용한 코드는 String으로 지정된 타입들이 <T>로 바뀌었다.

- 후에 객체 생성시 <타입>으로 T가 치환될 타입을 지정할 수 있다.

Basket<타입> basket1 = new Basket<타입>("축구공");

 

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

- T는 타입 매개변수. 

- 타입 매개 변수는 여러개 선언가능하다. (주로 쓰이는 철자는 T, K, V, E, N, R 등이 있음)

- 타입 매개 변수는 static 이 붙은 변수 or 메서드에 사용할 수 없다. 

- 타입 매개변수에 치환될 타입에 기본 타입은 사용할 수 없다. ( int -> Integer / char -> Character같이 래퍼클래스로 변경)

- 제너릭 클래스에서 다형성의 적용이 가능하다. (상속 가능)

class Mineral { ... }
class Gold extends Mineral { ... }
class Crystal { ... }

class Basket<T> {
	private T item;
    
	public T getIteml() {
		return item;
	}
    
    public void setItem(T item) {
		this.item = item;
	}
}

class Main {
	public static void main(String[] args) {
		Basket<Mineral> mineralBasket = new Basket<>();
		mineralBasket.setItem(new Gold()); // 다형성 적용
		mineralBasket.setItem(new Crystal()); // 에러
	}
}

 

#제한된 제네릭 클래스

- 제네릭 클래스의 타입을 지정하는 데 제한이 없다.

- 아래와 같은 코드를 통해 특정 클래스를 상속받은 클래스를 타입으로 지정할 수 있도록 제한하거나

class Mineral { ... }
class Gold extends Mineral { ... }
class Crystal { ... }

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

class Main {
	public static void main(String[] args) {
		Basket<Gold> goldBasket = new Basket<>();
		Basket<Crystal> CrystalBasket = new Basket<>(); // 에러
	}
}

- 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한 가능하다.

interface Matter { ... }
class Mineral implements Matter { ... }
class Gold extends Mineral implements Matter { ... }

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

class Main {
	public static void main(String[] args) {
		// 인스턴스화
		Basket<Mineral> mineralBasket = new Basket<>();
		Basket<Crystal> crystalBasket = new Basket<>(); 
	}
}

- 특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한하려면

  아래와 같이 &를 사용하여 코드를 작성한다.

interface Matter { ... }
class Mineral implements Matter { ... }
class Gold extends Mineral implements Matter { ... }

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

class Main {
	public static void main(String[] args) {
		// 인스턴스화
		Basket<Mineral> mineralBasket = new Basket<>();
		Basket<Crystal> crystalBasket = new Basket<>(); 
	}
}

 

#제너릭 메서드

: 클래스 내부의 특정 메서드만 제네릭으로 선언한 것.

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

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

  해당 메서드 내에서 선언한 타입 매개 변수를 사용한다.

- 제네릭 메서드의 타입 매개 변수는 제네릭 클래스의 타입 매개 변수와 구별된다.

class Basket<T> { // 제네릭 클래스의 매개변수 T
	...
	public <T> void add(T element) { // 제네릭 메서드의 매개변수 T
		...
	}
}

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

- 제네릭 클래스의 타입 지정은 인스턴스화 될 때 이루어진다.

- 클래스 타입 매개 변수와 달리 메서드 타입 매개 변수는 static 메서드에서도 선언하여 사용할 수 있다.

- 제네릭 메서드는 타입 지정이 되기 전 특정 타입의 메서드(String 클래스의 length())를 사용할 수 없다.

- Object 클래스 메서드를 사용 가능하다. (모든 클래스는 Object 클래스 상속)

 

#와일드카드

: 어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미.

- 기호  ? 로 사용한다.

- 일반적으로 extends와  super를 함께 사용한다.

- extends 와 사용하면 상한 제한을 둔다 ( TT를 상속받는 하위 클래스 타입만 타입 파라미터로 받을 수 있도록 지정)

- super 와 사용하면 하한 제한을 둔다 ( TT의 상위 클래스만 타입 파라미터로 받도록 한다.)

- <?> 처럼 단순 사용은 <? extends Object> 와 같다. ( 모든 클래스 타입을 타입 파라미터로 받을 수 있음)

class SmartPhone {...}

class IPhone extends SmartPhone {..}
class Galaxy extends SmartPhone {..}

class IPhone14Pro extends IPhone {...}
class IPhoneSE extends IPhone {...}

class S23 extends Galaxy {...}
class ZFlip5 extends Galaxy {...}

class User<T> {
	public T phone;
    
	public User(T phone) {
		this.phone = phone;
	}
}

// 휴대폰 기능에 관한 클래스
class PhoneFunction {
	public static void call(User<? extends Phone> user) { //모든 휴대전화에서 사용가능하도록 상한 제한
		System.out.println("-----------------------------");
		System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
		System.out.println("모든 Phone은 통화를 할 수 있습니다.");
    }
    
    public static void faceId(User<? extedns IPhone> user) { //아이폰만 사용가능하도록 상한 제한
		System.out.println("-----------------------------");
		System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
		System.out.println("IPhone만 Face ID를 사용할 수 있습니다.");
	}
    
    public static void samsungPay(User<? extends Galaxy> user) { //갤럭시시리즈만 사용가능하도록 상한 제한
		System.out.println("-----------------------------");
		System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
		System.out.println("Galaxy만 삼성 페이를 사용할 수 있습니다. ");
    }
    
	public static void recordVoice(User<? super Galaxy> user) { // 안드로이드 폰에서 가능하도록 하향 제한
		System.out.println("-----------------------------");
		System.out.println("user.phone = " + user.phone.getClass().getSimpleName());
		System.out.println("안드로이드 폰에서만 통화 녹음이 가능합니다. ");
	}
}

- 위 클래스에 대하여 인자로 전달하면 PhonFunction의 메서드를 호출한 클래스는 아래와 같다.

public class Example {
	public static void main(String[] args) {
		PhoneFunction.call(new User<Phone>(new Phone()));
		PhoneFunction.call(new User<IPhone>(new IPhone()));
		PhoneFunction.call(new User<Galaxy>(new Galaxy()));
		PhoneFunction.call(new User<IPhone12Pro>(new IPhone14Pro()));
		PhoneFunction.call(new User<IPhoneXS>(new IPhoneSE()));
		PhoneFunction.call(new User<S22>(new S22()));
		PhoneFunction.call(new User<ZFlip3>(new ZFlip5()));

		System.out.println("\n######################################\n");

//		  PhoneFunction.faceId(new User<Phone>(new Phone())); // X
		PhoneFunction.faceId(new User<IPhone>(new IPhone()));
		PhoneFunction.faceId(new User<IPhone12Pro>(new IPhone14Pro()));
		PhoneFunction.faceId(new User<IPhoneXS>(new IPhoneSE()));
//		  PhoneFunction.faceId(new User<Galaxy>(new Galaxy())); // X
//		  PhoneFunction.faceId(new User<S22>(new S23())); // X
//		  PhoneFunction.faceId(new User<ZFlip3>(new ZFlip5())); // X

        System.out.println("\n######################################\n");

//		  PhoneFunction.samsungPay(new User<Phone>(new Phone())); // X
//		  PhoneFunction.samsungPay(new User<IPhone>(new IPhone())); // X
//		  PhoneFunction.samsungPay(new User<IPhone12Pro>(new IPhone14Pro())); // X
//		  PhoneFunction.samsungPay(new User<IPhoneXS>(new IPhoneSE())); // X
		PhoneFunction.samsungPay(new User<Galaxy>(new Galaxy()));
		PhoneFunction.samsungPay(new User<S22>(new S23()));
		PhoneFunction.samsungPay(new User<ZFlip3>(new ZFlip5()));

		System.out.println("\n######################################\n");

		PhoneFunction.recordVoice(new User<Phone>(new Phone()));
//		  PhoneFunction.recordVoice(new User<IPhone>(new IPhone())); // X
//		  PhoneFunction.recordVoice(new User<IPhone12Pro>(new IPhone14Pro())); // X
//		  PhoneFunction.recordVoice(new User<IPhoneXS>(new IPhoneSE())); // X
		PhoneFunction.recordVoice(new User<Galaxy>(new Galaxy()));
//		  PhoneFunction.recordVoice(new User<S22>(new S23())); // X
//		  PhoneFunction.recordVoice(new User<ZFlip3>(new ZFlip5())); // X
    }
}

- 주석은 매개 변수의 타입이 일치하지 않아 에러가 발생하는 코드다.

- PhoneFunction.recordVoice 메서드는 Galaxy를 기준으로 해당 클래스 및 상위 클래스만 되도록 하향 제한 되어 있다.

  (S23ZFlip5을 타입으로 지정 불가하다)

Phone의 계급도