13. 제네릭

13.1 제네릭

generic = 포괄적인
어떤타입을 결정하지 않고 클래스를 설계
타입필요한곳? 필드선언 생성자 매개변수 메소드 매개변수 구체적이지 앟고 포괄적인 타입으로 선언가능
실제로 사용할때 구체적인 타입 결정되어야함. 대신 설계할때는 구체적으로 안하게 함.

어떠한 것이든 담고자 Box클래스를 만들어보자
public class Box{
public Object content; // 오브젝트 타입은 모든 클래스의 최상위임 따라서 모든 클래스는 Object이기도 하다.
그러나... 저장되어있는것을 꺼내려할때 문제가 발생할 수있다.
실제 저장된걸 알아야 강제타입변환을 거쳐 뽑을 수 있다. 알수없다면 문제가 발생한다.
이래서 제네릭을 사용한다.
결정되지 않은 타입을 파라미터로 처리하고 실제 사용할때 파라미터를 구체적인 타입으로 대체시키는 기능

public class Box { //타입을 T로 선언한다. 나중에 사용할때 T가 구체적인 타입으로 바꿔서 적용
public T content;
내용물로 String을 사용하고 싶다면
Box box = new Box(); 불러올때 결정
Box<String> box = new Box<String>();
box.content = "안녕하세요"
String content = box.content; // 강제타입변환이 필요없이 "안녕하세요"를 바로 얻을 수 있음.
내용물로 정수값을 저장하고 싶다면
Box<Integer> box = new Box<Integer>();

사실 타입 파리미터로 쓰이는 T는 단지 이름일뿐이기 때문에 어떤 알파벳을 사용해도 좋다.
<>안에는 클래스타입 인터페이스 타입 등만 올 수있다. 무조건 참조타입만 올수잇다.
12장 포장클래스에서 보기 자동언박싱 박싱을 알아야 사용가능

변수를 선언할때 동일한 타입으로 호출하고 싶다면 생성자 호출시 생성자엔은 타입을 명시하지않고 <>만 붙일수잇다.
Box<String> box = new Box<String>(); -> Box box = new Box<>();

public class Box <T>{
public T content;
}

public class GenericExample {
public static void main(String[] args) {
    //Box<String> box1 = new Box<String>();
    Box<String> box1 = new Box<>();
    box1.content = "안녕하세요"; //박싱
    String str = box1.content; //언박싱
    System.out.println(str);

    Box<Integer> box2 = new Box<>();
    box2.content = 100; //박싱
    int value = box2.content; //언박싱
    System.out.println(value);
}}

13.2 제네릭타입

제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
제네릭 타입은 선언부에 <> 부호가 붙고 그사이에 타입 파라미터들이 위치한다.
public class 클래스명<A,B,...> {}
public interface 인터페이스명<A,B,...> {}

타입파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만 일반적으로 대문자 알파벳 한글자로 표현한다.
외부에서 제네릭타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야한다. 만약지정하지 않으면 Object타입이 암묵적으로 사용된다.
인터페이스에 리턴값이 제네릭인게 있음. 구현객체 homeagnecy caragency만들고 리턴값을 home과 car가 나오게함.

13.2.1 클래스 타입 변수

//제네릭 타입
public class Product<K, M> {
//필드
private K kind;
private M model;

//메소드
public K getKind() {return this.kind;}
public M getModel() {return this.model;}
public void setKind(K kind) { this.kind = kind;}
public void setModel(M model) { this.model = model;}
}

public class GenericExample {
public static void main(String[] args) {
    // K는 Tv로 대체 M은 String 으로 대체
    Product<Tv, String> product1 = new Product<>();

    //Setter 매개값은 반드시 Tv와 String 을 제공
    product1.setKind(new Tv());
    product1.setModel("스마트Tv");

    //Getter 리턴값은 Tv와 String이 됨
    Tv tv = product1.getKind();
    String tvModel = product1.getModel();
    //-----------------------------------
    //k는 Car로 대체 M은 String으로 대체
    Product<Car, String> product2 = new Product<>();

    //Setter 매개값은 반드시 Car와 String 을 제공
    product2.setKind(new Car());
    product2.setModel("SUV자동차");

    //Getter 리턴값은 Car와 String이 됨
    Car car = product2.getKind();
    String carModel = product2.getModel();
    }}

13.2.2 인터페이스 타입변수

타입파라미터는 기본적으로 Object 타입으로 간주되므로 Object가 가지고 잇는 메소드를 호출할수잇다.
내용물을 비교하기 위해 Object의 equals()메소드를 호출한다.

 public class Car {
public void run() {
    System.out.println("자동차가 달립니다.");
}}

 public class Home {
public void trunOnLight() {
    System.out.println("전등을 켭니다.");
}}

 public class CarAgency implements Rentable<Car>{
@Override
public Car rent() {
    return new Car();
}}

public class HomeAgency implements Rentable<Home>{
//타입 파라미터 p를 Home으로 대체 
@Override
public Home rent() {
    return new Home();
}}

public interface Rentable<P> {
// 인터페이스를 제네릭타입으로 선언하기 다양한 대상을 렌트하기 위해 rent()메소드의 리턴타입을 타입파라미터로 선언
P rent();}

 public class GenericExample {
public static void main(String[] args) {
    HomeAgency homeAgency = new HomeAgency();
    Home home = homeAgency.rent();
    home.trunOnLight();

    CarAgency carAgency = new CarAgency();
    Car car = carAgency.rent();
    car.run();
}}

13.3 제네릭 메소드

제네릭 메소드는 타입 파라미터를 가지고 있는 메소드를 의미한다. 타입 파라미터가 메소드 선언부에 정의된다는 점에서 제네릭타입과 차이가잇다.
public <A,B,...> 리턴타입 메소드명(매개변수,...){}
public <T> Box<T> boxing(T t){}
매개값이 어떤 타입이냐에 따라 컴파일과정에서 구체적인 타입으로 대체된다.

public class Box<T> {
//필드
private T t;

//getter메소드
public T get() {
    return t;
}

//Setter 메소드
public void set(T t) {
    this.t = t;
}}

public class GenericExample {
// 제네릭 메소드
public static <T> Box<T> boxing(T t){
    Box<T> box = new Box<>();
    box.set(t);
    return box;
}

public static void main(String[] args) {
    //제네릭 메소드 호출
    Box<Integer> box1 = boxing(100);
    //Box<Integer> box = new Box<>();
    //box.set(100);
    int intValue = box1.get();
    System.out.println(intValue); //100

    //제네릭 메소드 호출
    Box<String> box2 = boxing("홍길동");
    String strValue = box2.get();
    System.out.println(strValue); //홍길동
}}

13.4 제한된 타입 파라미터

매우 중요!!
Object타입이 너무 포괄적이어서 모든 객체가 대체 가능해서 이를 제한 한다.
경우에 따라서는 타입 파라미터를 대체하는 구체적인 타입을 제한할 필요가 있다.
예를들어 숫자 연산일시 숫자를 매개값으로 받아야하는데 다른걸 받으면 안된다.
대체타입으로 Number 또는 자식 클래스 Byte Short Integer Long Double만 써야함.

이처럼 모든 타입으로 대채할 수 없고 특정타입과 자식 또는 구현관계에 있는 타입만 대체할 수 있는 타입 파라미터를 제한된 파라미터라 한다.
public <T extends 상위타입> 리턴타입 메소드(매개변수,..){}

상위타입은 클래스뿐만이 아니라 인터페이스도 가능하다. 인터페이스라고해서 implements를 사용하지는 않는다.

public boolean compare(T t1, T t2){
Number이거나 Number의 자식객체 구현객체만 사용가능
double v1 = t1.doubleValue(); //Number의 doubleValue()메소드 사용
double v2 = t1.doubleValue(); //Number의 doubleValue()메소드 사용
return (v1 == v2)

public class GenericExample {
// 제한된 타입 파라미터를 갖는 제네릭 메소드
public static <T extends Number> boolean compare(T t1, T t2) { //Number t1 ,Number t2
    //T의 타입을 출력
    System.out.println("compare(" + t1.getClass().getSimpleName() + ", " + t2.getClass().getSimpleName()+ ")");
    //리플렉션 이용

    // Number의 메소드 사용
    double v1 = t1.doubleValue();
    double v2 = t2.doubleValue();

    return (v1 == v2);
}

public static void main(String[] args) {
    //제네릭 메소드 호출
    boolean result1 = compare(10, 20);
    System.out.println(result1);

    //제네릭 메소드 호출
    boolean result2 = compare(4.5, 4.5);
    System.out.println(result2);
    }}

13.5 와일드카드 타입 파라미터

매우 중요!!
4절하고 관련이 있음 이것도 타입을 제한하는 건데 방향이 있음.
제네릭 타입을 매개값이나 리턴타입으로 사용할 때 타입파라미터를 ?(와일드카드)로 사용할 수 있다.
?는 범위안에 있는 모든 타입으로 대체할 수 있다는 표시이다.

Student와 자식클래스인HighStudnet와 Middle만 가능하도록 매개변수 선언
리턴타입 메소드명(제네릭타입<? extends Student>)변수) {} extends 클래스명 = 클래스명 기준으로 아래쪽만 사용가능
반대로 Worker와 부모 클래스인 Person만가능하도록
리턴타입 메소드명(제네릭타입<? super Worker>변수) {} super 클래스명 = 클래스명을 기준으로 위쪽으로만 사용가능
어떤 타입이든 가능하도록
리턴타입 메소드명(제네릭타입<?> 변수){} - 이경우는 별로없음

Course 클래스의 메소드 resisterCourse1()은 모든사람이 들을수있는 과정을 등록
resisterCourse2()은 학생만
resisterCourse3()은 직장인만

public class Person {}
class Worker extends Person{}
class Student extends Person{}
class HighStudent extends Student{}
class MiddleStudent extends Student{}

public class Applicant<T> {
public T kind;

public Applicant(T kind) {
    this.kind = kind;
}}

public class Course {
//모든 사람이면 등록가능
public static void registerCourse1(Applicant<?> applicant) {
    System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course1을 등록함");
}

//학생만 등록가능
public static void registerCourse2(Applicant<? extends Student> applicant) {
    System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course2를 등록함");
}

//직장인 및 일반인만 등록가능
public static void registerCourse3(Applicant<? super Worker> applicant) {
        System.out.println(applicant.kind.getClass().getSimpleName() + "이(가) Course3을 등록함");
}}

public class GenericExample {
public static void main(String[] args) {
    //모든 사람이 신청 가능
    Course.registerCourse1(new Applicant<Person>(new Person()));
    Course.registerCourse1(new Applicant<Worker>(new Worker()));
    Course.registerCourse1(new Applicant<Student>(new Student()));
    Course.registerCourse1(new Applicant<HighStudent>(new HighStudent()));
    Course.registerCourse1(new Applicant<MiddleStudent>(new MiddleStudent()));
    System.out.println();

    //학생만 신청 가능
    //Course.registerCourse2(new Applicant<Person>(new Person())); (x)
    //Course.registerCourse2(new Applicant<Worker>(new Worker())); (x)
    Course.registerCourse2(new Applicant<Student>(new Student()));
    Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));
    Course.registerCourse2(new Applicant<MiddleStudent>(new MiddleStudent()));
    System.out.println();

    //직장인 및 일반인만 신청 가능
    Course.registerCourse3(new Applicant<Person>(new Person()));
    Course.registerCourse3(new Applicant<Worker>(new Worker()));
    //Course.registerCourse3(new Applicant<Student>(new Student()));         (x)
    //Course.registerCourse3(new Applicant<HighStudent>(new HighStudent()));     (x)
    //Course.registerCourse3(new Applicant<MiddleStudent>(new MiddleStudent()));     (x)
}}

우리가 직접 클래스를 작성할때는 제네릭을 잘 사용하지는 않는다.
그러나 우리가 API도큐먼트를 통해 메소드를 사용할때 이런 거를 해석할줄알아야 쓸수 있다.
그러니 4절과 5절은 꼭 알아두기

+ Recent posts