제네릭스

황상익·2023년 12월 15일

열혈 자바

목록 보기
18/30

제너릭스
제너릭스가 갖는 의미는 일반화. 일반화에 대상은 자료형이다.
제너릭스 이전 코드
public class Main1 {
public static void main(String[] args) {
Box box = new Box();
Box ox = new Box();

    box.setObject(new Apple());
    ox.setObject(new Orange());

    Apple ap = (Apple) box.getObject();
    Orange org = (Orange) ox.getObject();

    System.out.println(ap);
    System.out.println(org);
}

}

class Apple {
public String toString() {
return "I am Apple";
}
}

class Orange {
public String toString() {
return "I am Orange";
}
}

//class AppleBox {
// private Apple ap;
//
// public void set(Apple apple) {
// ap = apple;
// }
//
// public Apple get(){
// return ap;
// }
//}
//
//class OrangeBox{
// private Orange org;
//
// public void set(Orange or){
// org = or;
// }
//
// public Orange get(){
// return org;
// }

class Box {
private Object object;

public void setObject(Object object) {
    this.object = object;
}

public Object getObject() {
    return object;
}

}

class Box {
private Object object;

public void setObject(Object object) {
    this.object = object;
}

public Object getObject() {
    return object;
}

}
AppleBox와 OrangeBox가 하는 일은 -> 클래스 하나로 대체. Object를 상속하는 인스턴스면 무엇이든 담는다.

public class Main1 {
public static void main(String[] args) {
Box box = new Box();
Box ox = new Box();

    box.setObject(new Apple());
    ox.setObject(new Orange());

    Apple ap = (Apple) box.getObject();
    Orange org = (Orange) ox.getObject();

    System.out.println(ap);
    System.out.println(org);
}

}

class Apple {
public String toString() {
return "I am Apple";
}
}

class Orange {
public String toString() {
return "I am Orange";
}
}
Box 인스턴스에서 내용물을 꺼내 형 변환을 해야 한다.

두 문장에서 Apple 인스턴스와 Orange 인스턴스를 담으려한 프로그래머의 의도와 달리 실수로 만들어진 문장
 box.set(“apple”)
 ox.set(“Orange)
이러한 실수는 컴파일 과정에서 발견되지 않는다. 단, 다음 문장을 실행하는 순간 예외 발생
Apple ap = (Apple)Box.get(); -> 형 변환 과정에서 예외 발생

제너릭스 기반 클래스 정의하기
자료형에 의존적이지 않은 클래스를 정의 할 수 있게 되었다.
class Org{
public String toString(){
return "I am Apple";
}
}

class App{
public String toString(){
return "I am Orange";
}
}

class Box1{
private T Obj;

public void setObj(T obj){
    Obj = obj;
}

public T get(){
    return Obj;
}

}

public class Main2 {
public static void main(String[] args) {
Box1 aBox = new Box1();
Box1 oBox = new Box1();

    aBox.setObj(new App());
    oBox.setObj(new Org());

    App ap = aBox.get();
    Org org = oBox.get();

    System.out.println(ap);
    System.out.println(org);
}

}
Object형 인스턴스를 저장하고 반환, 따라서 자료형에 의존적이지 않은 형태로 클래스를 정의하기 위해 Object를 T로 한 다음, T 인스턴스를 생성할 때 결정하면 된다. 사과를 저장할 목적이라면 T를 App으로 오랜지를 저장할 목적이라면 T를 Orange로 결정하면 된다. 그리고 T는 인스턴스 생성시 자료형을 결정하기 위한 표식임을 알려야 한다.
class Box1{
private T Obj;

public void setObj(T obj){
    Obj = obj;
}

public T get(){
    return Obj;
}

}
< T > 완성된 제네릭 기반의 클래스 정의

Box1 aBox = new Box1();
Box1 oBox = new Box1();
클래스를 대상으로 인스턴스를 생성

따라서 T를 가리켜, 타입 매개변수라고 한다. 메소드와 매개변수와 유사하게 자료형 정보를 인자로 전달 받는 형태
 Box 에서 T – 타입 매개변수
 Box 에서 Apple – 타입인자
 Box - 매개변수화 타입
매개변수화 타입은 제네릭 타입이라고도 한다.

제네릭 기본 문법
둘 이상의 타입 매개변수에 대한 제네릭 클래스도 정의할 수 있다.
public class Main3 {
public static void main(String[] args) {
DBox<String, Integer> box = new DBox<String, Integer>();
box.set("Apple", 25);
System.out.println(box);
}
}

class DBox<L, R> {
private L left;
private R right;

public void set(L left, R right){
    left = left;
    right = right;
}

@Override
public String toString() {
    return left + " & " + right;
}

}
타입 매개변수 이름은 -> 한문자로 짓는다. 대문자로 짓는다.
기본 자료형에 대한 제한 그리고 레퍼 클래스
Box과 매개변수화 타입을 구성할 때 기본 자료형의 이름은 타입인자로 사용할 수 있다.
Box box = new Box<>()
 타입 인자로 기본 자료형이 올 수 없으므로 컴파일 에러
기본 자료형에 대한 래퍼 클래스가 존재하고, 필요한 상황에서 박싱과 언박싱이 자동으로 이뤄지기 때문에
class Box2{
private T ob;

public void setOb(T o){
    ob = o;
}

public T get(){
    return ob;
}

}

public class Main4 {
public static void main(String[] args) {
Box2 iBox = new Box2<>();
iBox.setOb(125);

    int num = iBox.get();
    System.out.println(num);
}

}
위와 같은 코드를 볼 수 있다.

타입 인자의 생략 (다이아몬드 기호)
참조변수 선언을 통해서 <> 안에 Apple이 생략되었다고 컴파일러는 판단한다.

매개변수화 타입을 타입 인자로 전달하기

class Box4{
private T ob;

public void setOb(T ob){
    ob = ob;
}

public T get(){
    return ob;
}

}

public class Main5 {
public static void main(String[] args) {
Box4 sBox = new Box4<>();

    sBox.setOb("Happy");

    Box4<Box4<String>> wBox = new Box4<>();
    wBox.setOb(sBox);

    Box4<Box4<Box4<String>>> zBox = new Box4<>();
    zBox.setOb(wBox);

    System.out.println(zBox.get().get().get());
}

}
문자열을 저장한 다음에 상자를 다른 상자에 넣고자 한다. 이 상자를 한번 더 다른 상자에 넣을 생각이다. 즉 겹겹이 상자를 넣는다고 보면 됨.
Box4<Box4> wBox = new Box4<>();
wBox.setOb(sBox);
매개변수화 타입이 타입 인자로 사용 될 수 있음을 보여준다.

<문제>
class DBox1<L, R> {
private L left;
private R right;

public void setDBOx(L left, R right) {
    left = left;
    right = right;
}

@Override
public String toString() {
    return left + "&" + right;
}

}

class DDBox<U, D>{
private U up;
private D down;

public void setDDBox(U up, D down){
    up = up;
    down = down;
}

@Override
public String toString(){
    return up + "&" + down;
}

}

public class Main6 {
public static void main(String[] args) {
DBox<String, Integer> box1 = new DBox<>();
box1.set("Apple", 120);

    DBox<String, Integer> box2  = new DBox<>();
    box2.set("Orange", 180);

    DDBox<DBox<String, Integer>, DBox<String, Integer>> box = new DDBox<>();
    box.setDDBox(box1, box2);

    System.out.println(box);
}

}

제네릭 클래스의 타입 인자 제한하기.
담고 싶은 것을 제한할 수 있어야 한다. 이때 사용 하는 것이 extends이다. Number 클래스를 상속하는 클래스의 인스턴스만 담고 싶다면,
Class Box
 인스턴스 생성시 타입인자로 Number 또는 이를 상속하는 클래스만 올 수 있다.
public class Main7 {
public static void main(String[] args) {
Box3 iBox = new Box3<>();
iBox.set(24);

    Box3<Double> dBox = new Box3<>();
    dBox.set(5.97);

    System.out.println(iBox.get());
    System.out.println(dBox.get());
}

}

class Box3{
private T ob;

public void set(T ob){
    this.ob = ob;
}

public T get(){
    return ob;
}

}

제네릭 클래스의 타입 인자를 Number 또는 이를 상속하는 하위 클래스로 제한.
class Box3{
private T ob;

public void set(T ob){
    this.ob = ob;
}

public T get(){
    return ob;
}

}
 참조변수 ob가 참조하게 될 인스턴스이다. 하지만 인스턴스를 참조하게 될지 알 수 없기 때문에, ob를 통해서 호출할 수 있는 메소드는 Object 클래스의 메소드로 제한이 된다. 반면 타입 인자를 제한하면 Number 클래스의 intValue 메소드를 호출 할 수 있다.

제네릭 클래스의 타입 인자를 인터페이스로 제한하기

interface Eatable{
public String eat();
}

class Banana implements Eatable{

@Override
public String eat() {
    return "so Good";
}

}

class Box5{
T ob;

public void set(T ob){
    ob= ob;
}

public T get(){
    System.out.println(ob.eat());
    return ob;
}

}

public class Main8 {
public static void main(String[] args) {
Box5 box = new Box5<>();
box.set(new Banana());

    Banana banana = box.get();
    System.out.println(banana);
}

}
제네릭 클래스의 타입 인자를 인터페이스 이름으로 제한할 수 있다. 제한할 때는 클래스와 마찬가지로 extends를 사용. Eatable 인터페이스를 구현하는 클래스로 타입 인자를 제한했기 때문에 다음과 같이 인터페이스에 선언되어 있는 메소드 Eat의 호출이 가능.

class Box5{
T ob;

public void set(T ob){
    ob= ob;
}

public T get(){
    System.out.println(ob.eat());
    return ob;
}

제네릭 메소드의 정의
클래스 전부가 아닌 일부 메소드에 대해서만 제네릭으로 정의하는 것도 가능. 이렇게 제네릭 메소드라고 한다. 또한 클래스 메소드에 대해서도 정의가 가능하다. Static 선언 유무에 상관없이 제네릭 메소드의 정의가 가능.
Public static Box makeBox(T o)
 메소드 이름은 makeBox 이고 반환형은 Box이다.
따라서 T가 타입 매개변수의 선언임을 표시
Public static Box makeBox(T o)
 Static과 Box 사이에 위치한 는 T가 타입 매개변수임을 알리는 표시
 public class Main9 {
public static void main(String[] args) {
Box6 sBox = BoxFactory.makeBox("sweet");
System.out.println(sBox.get());

    Box6<Double> dBox = BoxFactory.makeBox(7.59);
    System.out.println(dBox.get());
}

}

class Box6{
private T ob;

public void set(T ob){
    ob = ob;
}

public T get(){
    return ob;
}

}

class BoxFactory{
public static Box6 makeBox(T o){
Box6 box6 = new Box6<>();
box6.set(o);
return box6;
}
}

Box6 sBox = BoxFactory.makeBox("sweet");
System.out.println(sBox.get());

    Box6<Double> dBox = BoxFactory.makeBox(7.59);
    System.out.println(dBox.get());
}

}
제네릭 클래스는 인스턴스 생성시 자료형이 결정. 반면 제네릭 메소드는 메소드 호출시 자료형이 결정.

class Box7{
private T ob;

public void set(T ob){
    ob = ob;
}

public T getOb() {
    return ob;
}

}

class UnBoxer{
public static T oepnBox(Box7 box){
return box.getOb();
}
}

public class Main10 {
public static void main(String[] args) {
Box7 box = new Box7<>();
box.set("Hello");

    String str = UnBoxer.<String>oepnBox(box);
    System.out.println(str);
}

}
메소드 반환형은 T이고, 전달인자의 자료형이 Box 인 경우.
ublic class Main10 {
public static void main(String[] args) {
Box7 box = new Box7<>();
box.set("Hello");

    String str = UnBoxer.<String>oepnBox(box);

제네릭 메소드의 제한된 타입 매개변수 선언
제네릭 클래스를 정의 할 때 타입인자를 제한 할 수 있다. 마찬가지로 제네릭 메소드도 호출 시 전달되는 타입인자를 제한할 수 있다. 타입 인자를 제한할 때 생기는 특징이 제네릭 메소드의 타입 인자를 제한 할 때도 생긴다.

class Box8<T>{
    private T ob;

    public void set(T ob){
        ob = ob;
    }

    public T get(){
        return ob;
    }
}

class BoxFactory2{
    public static <T extends Number> Box8<T> makeBox(T o){
        Box8<T> box = new Box8<>();
        box.set(o);

        System.out.println(o.intValue());
        return box;
    }
}

class Unboxer1{
    public static <T extends Number> T openBox(Box8<T> box){
        System.out.println(box.get().intValue());
        return box.get();
    }
}

public class Main11 {
    public static void main(String[] args) {
        Box8<Integer> sBox =BoxFactory2.makeBox(new Integer(5959));
        int n = (int) Unboxer1.openBox(sBox);
        System.out.println(n);

    }
}

 다음과 같이 제네릭 메소드에 전달되는 타입 인자를 제한, Number를 상속하는 클래스로 타입인자를 제한.
 는 타입 인자를 Number를 상속하는 클래스로 제한,
 public static T openBox(Box8 box){
System.out.println(box.get().intValue());
return box.get();
}

 타입 인자를 Number를 상속하는 클래스로 제한

Public static <T extends Number> T openBox(Box<T> box)

<문제>
class Sbox<T>{
    private T ob;
    public void set(T ob){
        ob= ob;
    }

    public T get(){
        return ob;
    }
}

public class Main12 {
    public static <T extends Number> void swapBox(Sbox<T> sbox, Sbox<T> s1box){
        T temp = sbox.get();
        sbox.set(s1box.get());
        s1box.set(temp);
    }

    public static void main(String[] args) {
        Sbox<Integer> box = new Sbox<>();
        box.set(99);

        Sbox<Integer> box1 = new Sbox<>();
        box1.set(55);

        System.out.println(box.get() + " & " + box1.get());
        swapBox(box, box1);
        System.out.println(box1.get() + " & " + box.get());
    }
}
profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글