#제네릭
: 클래스와 메서드의 코드를 작성할 때, 타입을 추후에 지정할 수 있도록 일반화해 두는 것.
- 작성한 클래스와 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해 둔 것.
- 제네릭의 활용
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 와 사용하면 상한 제한을 둔다 ( T와 T를 상속받는 하위 클래스 타입만 타입 파라미터로 받을 수 있도록 지정)
- super 와 사용하면 하한 제한을 둔다 ( T와 T의 상위 클래스만 타입 파라미터로 받도록 한다.)
- <?> 처럼 단순 사용은 <? 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를 기준으로 해당 클래스 및 상위 클래스만 되도록 하향 제한 되어 있다.
(S23와 ZFlip5을 타입으로 지정 불가하다)
'개발지 > Today I learn' 카테고리의 다른 글
[0810] 자바 컬렉션 4 (컬렉션 프레임워크) (0) | 2023.08.10 |
---|---|
[0809] 자바 컬렉션 3 (예외 처리) (0) | 2023.08.09 |
[0807] 자바 컬렉션 1 (열거형) (0) | 2023.08.08 |
[0731] 자바 객체지향 프로그래밍 심화 (추상화) (2) | 2023.08.01 |
[0728] 자바 객체지향 프로그래밍 심화 (다형성) (0) | 2023.07.28 |