이것이 자바다 ch13 제네릭 (13.1~13.5)

dev·2022년 11월 7일
0

이것이 자바다

목록 보기
7/7

13.1 제네릭(Generic)이란?

제네릭

  • 데이터의 타입(data type)을 일반화한다(generalize)는 뜻
  • 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법
  • 결정되지 않은 타입을 파라미터로 처리하고 실제 사용시 파라미터를 구체적인 타입으로 대체시키는 방법
  • 컴파일 시에 미리 타입 검사(type check)를 수행 가능하므로 클래스나 메소드 내부에서 사용되는 객체의 타입 안정성을 높임
  • 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일수 있음
//제네릭은 클래스와 메소드에만 다음과 같은 방법으로 선언
class MyArray<T> {

    T element;

    void setElement(T element) { this.element = element; }

    T getElement() { return element; }

}
//선언된 제네릭 클래스(generic class)를 생성할 때에는 타입 변수 자리에 사용할 실제 타입을 명시해야 함
MyArray<Integer> myArr = new MyArray<Integer>();

<T>

  • T가 타입 파라미터임을 뜻하는 기호
  • 'T'를 타입 변수(type variable)라고 하며, 임의의 참조형 타입을 의미
  • 타입이 필요한 자리에 T를 사용할 수 있음을 알려주는 역할을 한다.
  • 'T'뿐만 아니라 어떠한 문자를 사용해도 상관없으며, 여러 개의 타입 변수는 쉼표(,)로 구분하여 명시할 수 있음
//Box 클래스에서 결정되지 않은 content의 타입을 T라는 타입 파라미터로 정의한것
public class Box <T> {
	public T content;
}

Box 클래스는 T를 content 필드의 타입으로 사용했다.
Box는 T가 뭔지 모르지만 Box 객체가 생성될 시점에 다른 타입으로 대체된다는것을 알고있다.

//Box의 내용물로 String을 저장하고 싶을때
Box<String> box = new Box<String>(); //Box 생성시 <String>으로 지정
box.content = "안녕하세요.;
String content = box.content; //강제 타입 변환이 필요 없이 "안녕하세요"를 바로 얻을수 있음
//Box의 내용물로 정수값을  저장하고 싶을때
Box<Integer> box = new Box<Integer>(); //Box 생성시 <Integer>으로 지정
box.content = 100;
int content = box.content; //강제 타입 변환이 필요없이 100을 바로 얻을 수 있음

주의 : 타입 파라미터를 대체하는 타입은 클래스인터페이스

그래서 Box<int>와 같이 사용할 수 없다. (int는 클래스나 인터페이스가 아니므로)

변수 선언시와 동일한 타입으로 호출 하는 방법

생성자 호출 시 생성자에는 타입을 명시하지 않고 <>만 붙일수 있음

package ch13.sec01;

public class Box <T> {
    public T content; //타입 파라미터로 T 사용
}
package ch13.sec01;

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<Integer>();
        Box<Integer> box2 = new Box<>();
        box2.content = 100;
        int value = box2.content;
        System.out.println(value); //100
    }
}

13.2 제네릭 타입

제네릭 타입

결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스
선언부에 '<>' 부호가 붙고 그 사이에 타입 파라미터들이 위치

public class 클래스명<A, B, ...> {...}
public interface 인터페이스명<A, B, ...> {...}

타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만 일반적으로 대문자 알파벳 한글자로 표현한다.

외부에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야한다.
지정하지 않으면 Object 타입이 암묵적으로 사용된다.

  • Product 클래스를 제네릭 타입으로 선언하고, kind, model 필드를 타입 파라미터로 선언하고, Getter의 매개변수와 Setter의 리턴 타입 역시 타입파라미터로 선언

  • Product 에 다양한 종류와 모델 제품을 저장하기 위해서 타입 파라미터를 사용함

package ch13.sec02.exam01;

public class Product <K,M> { // 제네릭타입(타입 파라미터로 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;}
}
package ch13.sec02.exam01;

public class Tv {
}
package ch13.sec02.exam01;

public class Car {
}
package ch13.sec02.exam01;

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();
    }
}
  • Rentable 인터페이스를 제네릭 타입으로 선언함
  • 다양한 대상을 렌트하기 위해 rent() 메소드의 리턴타입으로 타입 파라미터로 선언함
  • 렌트 대상은 Home, Car
package ch13.sec02.exam02;

public interface Rentable <P> { //타입파라미터 P 정의
    P rent(); //타입 파라미터 P를 리턴 타입으로 사용
}
package ch13.sec02.exam02;

public class Home {
    public void turnOnLight() {
        System.out.println("전등을 켭니다.");
    }
}
package ch13.sec02.exam02;

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

집과 차를 렌트해주는 대리점 클래스 HomeAgency, CarAgency
Rentable의 타입 파라미터를 Home, Car 로 대체해서 구현하는 방법을 보여준다.

package ch13.sec02.exam02;

public class HomeAgency implements Rentable<Home> { //타입파라미터 P를 Home으로 대체
    @Override
    public Home rent() {
        return new Home(); //리턴 타입이 반드시 Home 이어야함
    }
}
package ch13.sec02.exam02;

public class CarAgency implements Rentable<Car> {
    @Override
    public Car rent() {
        return new Car(); //리턴 타입이 반드시 Car여야 함
    }
}

HomeAgency, CarAgency 에서 대여한 Home, Car를 이용하는 방법

package ch13.sec02.exam02;

public class GenericExmaple {
    public static void main(String[] args) {
        HomeAgency homeAgency = new HomeAgency();
        Home home = homeAgency.rent();
        home.turnOnLight(); //전등을 켭니다.

        CarAgency carAgency = new CarAgency();
        Car car = carAgency.rent();
        car.run(); //자동차가 달립니다.
    }
}

타입 파라미터는 기본적으로 Object 타입으로 간주되므로 Object 가 갖고있는 메소드를 호출가능

package ch13.sec02.exam03;

public class Box <T>{
    public T content;

    //Box 내용물이 같은지 비교
    public boolean compare(Box<T> other) {
        boolean result = content.equals(other.content);
        return result;
    }
}
package ch13.sec02.exam03;

public class GenericExample {
    public static void main(String[] args) {
        Box box1 = new Box();
        box1.content = "100";

        Box box2 = new Box();
        box2.content = "100";

        Box box3 = new Box();
        box3.content = 100;

        boolean result1 = box1.compare(box2);
        System.out.println("result 1 " + result1); //Box 의 내용물 비교
        boolean result2 = box2.compare(box3);
        System.out.println("result 2 " + result2); //Box 의 내용물 비교


//result1 : true
//result2 : false
    }
}

13.3 제네릭 메소드

제네릭 메소드

타입 파라미터를 가지고 있는 메소드
타입 파라미터가 메소드 선언부에 정의된다는 점에서 제네릭 타입과 차이가 있음

제네릭 메소드 사용법

리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 정의한 뒤,
리턴 타입과 매개변수 타입에서 사용

public <A, B, ...> 리턴타입 메소드명(매개변수, ...) {...}

다음 boxing() 메소드는 타입 파라미터로 를 정의하고 매개변수 타입과 리턴 타입에서 T를 사용한다.
정확한 리턴 타입은 T를 내용물로 갖는 Box 객체이다.

public <T> Box<T> boxing(T t) {...}

타입 파라미터 T는 매개값이 어떤 타입이냐에 따라 컴파일 과정에서 구체적인 타입으로 대체된다.

Box<Integer> box1 = boxing(100);Box<String> box2 = boxing("안녕하세요");

①은 100의 클래스 타입이 Integer 이므로 타입 파라미터 T 는 Integer 로 대체되어 Box<Integer>가 리턴
②는 “안녕하세요” 의 클래스 타입이 String 이므로 타입 파라미터 T 는 String으로 대체되어 Box<String> 이 리턴

package ch13.sec03.exam01;

  //제네릭 타입인 Box 클래스 선언
public class Box <T> {
    //필드
    public T t;

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

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

  //제네릭 메소드인 boxing을 선언하고 호출하는 방법에 대한 예제
public class GenericExample {
    //제네릭 메소드
    public static <T> Box<T> boxing(T t) { //타입파라미터 <T> 정의
        Box<T> box = new Box<>();
        box.set(t);
        return box;
    }

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

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

13.4 제한된 타입 파라미터

제한된 타입 파라미터

타입 파라미터를 대체하는 구체적인 타입을 제한할 필요가 있을때 사용
모든 타입으로 대체할 수 없고 특정 타입과 자식 또는 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터

public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) {...}

예를 들어, 숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number 또는 자식 클래스(Byte, Short, Integer, Long, Doubld) 로 제한할 필요가 있음

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

다음은 Number 타입과 자식 클래스(Byte, Short, Integer, Long, Doubld) 에만 대체 가능한 타입 파라미터를 정의한 것이다.

  //Number 타입과 자식 클래스(Byte, Short, Integer, Long, Double)에만 대체 가능한 타입 파라미터 정의한것
public <T extends Number> boolean compare(T t1, T t2) {
	double v1 = t1.doubleValue(); //Number 의 doubleValue() 메소드 사용
	double v2 = t2.doubleValue(); //Number 의 doubleValue() 메소드 사용
	return (v1 == v2);
}

타입 파라미터가 Number 타입으로 제한되면서 Object 의 메소드 뿐 아니라, Number 가 가지고 있는 메소드도 사용 가능

package ch13.sec04;

public class GenericExample {
    //제한된 타입 파라미터를 갖는 제네릭 메소드
    public static <T extends Number> boolean compare(T t1, T 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); // T를 Integer 타입으로 대체
        System.out.println(result1);
        System.out.println();


        //제네릭 메소드 호출
        boolean result2 = compare(4.5, 4.5); //T를 Double 타입으로 대체
        System.out.println(result2);

        //compare(Integer, Integer)
        //false

        //compare(Double, Double)
        //true
    }
}

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

와일드카드 (?)

  • 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터 ?(와일드카드)를 사용할 수 있다.
  • '?' 는 범위에 있는 모든 타입으로 대체할 수 있다는 표시

예를 들어 다음과 같은 상속 관계가 있다고 가정해보자.

  //Student 와 자식 클래스인 HighStudent 와 MiddleStudent 만 가능하도록 매개변수를 다음과 같이 선언
리턴타입 메소드명(제네릭타입<? extends Student> 변수) {...}
//Worker와 부모 클래스인 Person 만 가능하도록 매개변수를 다음과 같이 선언
리턴타입 메소드명(제네릭타입<? extends Student> 변수) {...}
//어떤 타입이든 가능하도록 매개변수를 선언
리턴타입 메소드명(제네릭타입<?> 변수) {...}

Course 클래스의 메소드

  • registerCourse1() 은 모든 사람이 들을 수 있는 과정을 등록
  • registerCourse2() 는 학생만 들을 수 있는 과정을 등록
  • registerCourse3() 은 직장인과 일반인만 들을 수 있는 과정을 등록
package ch13.sec05;

public class Person {

}

class Worker extends Person {

}

class Student extends Person {

}

class HighStudent extends Student {

}

class MiddleStudent extends Student {

}
package ch13.sec05;

public class Applicant<T> {
    public T kind;

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

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를 등록함");
    }
}
package ch13.sec05;

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 Student()));
//        Course.registerCourse2(new Applicant<Worker>(new Student()));
        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()));
//        Course.registerCourse3(new Applicant<HighStudent>(new HighStudent()));
//        Course.registerCourse3(new Applicant<MiddleStudent>(new MiddleStudent()));

//        Person이(가) Course1을 등록함
//        Worker이(가) Course1을 등록함
//        Student이(가) Course1을 등록함
//        HighStudent이(가) Course1을 등록함
//        MiddleStudent이(가) Course1를 등록함
//
//        Student이(가) Course2를 등록함
//        HighStudent이(가) Course2를 등록함
//        MiddleStudent이(가) Course2를 등록함
//
//        Person이(가) Course3을 등록함
//        Worker이(가) Course3을 등록함
    }
}

출처: 이것이 자바다 (개정판) : JAVA 프로그래밍의 기본서 - 신용권, 임경균 저

profile
hello world!

0개의 댓글