아이템 26. 로 타입은 사용하지 말라.

무한성장개발자·2024년 10월 20일

아이템 26. 핵심 정리 - 제네릭 용어 정리

package me.whiteship.chapter05.item26.terms;

import java.util.ArrayList;
import java.util.List;

public class GenericBasic {

    public static void main(String[] args) {
        // Generic 사용하기 전
        List numbers = new ArrayList();
        numbers.add(10);
        numbers.add("whiteship");

        for (Object number: numbers) {
            System.out.println((Integer)number);
        }

        // Generic 등장 이후
//        List<Integer> nuberms = new ArrayList<>();
//        nuberms.add(10);
//        nuberms.add("whiteship");
//
//        for (Integer number: nuberms) {
//            System.out.println(number);
//        }
    }
}

Law Type은 Object를 넣을 수 있다. Integer가 들어갔다고 가정하는데 String이 들어가면 에러가 안나기 때문에 문제를 발견해내기 어렵다.

제네릭 사용 이후는 컴파일 오류가 나오기 때문에 좋다.

형변환을 할 필요가 없어진다.

package me.whiteship.chapter05.item26.terms;

public class Box<E> {

    private E item;

    private void add(E e) {
        this.item = e;
    }

    private E get() {
        return this.item;
    }

    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        box.add(10);
        System.out.println(box.get() * 100);

        printBox(box);
    }

    private static void printBox(Box<?> box) {
        System.out.println(box.get());
    }

}

E라는 타입을 사용할 수 있다고 말하는 것이다.
어떤 타입인지는 모르겠지만 E라는 타입으로 지칭하겠다.
E는 타입 매개변수라고 불린다.

E라는 자리에 Integer라는 것을 넣었고 실제 타입 매개변수라고 한다. 실제 존재하는 타입 매개변수를 줘야 한다.

박스의 인티저, 박스 오브 인티저라고 부를 수 있고 매개변수화 타입이라고 불린다.
한정적인 타입 매개변수가 있다. E 대신에 Object, Integer 아무거나 되는데

<E extends Number>

를 사용하여 Number를 상속받은 타입만 들어가게 할 수도 있다.

String이 들어가면 오류가 나온다.

? : 비한정적 와일드카드라고 한다. 아무 타입이나 대응 된다는 것을 의미한다.

<? extends Number>
<? super Number>

와일드카드를 쓰면 아무것도 넣을 수 없다. 와일드카드는 뭔가를 넣을 때 쓰는게 아니다.

    private static void printBox(Box<?> box) {
        System.out.println(box.get());
    }

아무런 박스나 와도 된다는 의미다.

String으로 된 Box만 담겠어 하면 Integer로 만들어진 박스는 올 수가 없다.

Object로 된 박스만 담겠어라고 해도 Integer와 Object는 다른 것이다.

Integer를 받으려면 다음과 같이 해야 한다.

package me.whiteship.chapter05.item26.terms;

public class Box<E> {

    private E item;

    private void add(E e) {
        this.item = e;
    }

    private E get() {
        return this.item;
    }

    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        box.add(10);
        printBox(box);
    }

    private static void printBox(Box<? extends Object> box) {
        System.out.println(box.get());
    }

}

extends Object는 다음과 같이 생략 가능하다.

package me.whiteship.chapter05.item26.terms;

public class Box<E> {

    private E item;

    private void add(E e) {
        this.item = e;
    }

    private E get() {
        return this.item;
    }

    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        box.add(10);
        printBox(box);
    }

    private static void printBox(Box<?> box) {
        System.out.println(box.get());
    }

}

아이템 26. 핵심 정리 - 로(raw) 타입은 사용하지 말라.

이렇게 제네릭을 사용한 코드를 컴파일하면 모든 제네릭이 다 벗겨진다.
그리고 코드를 이렇게 자동으로 컨버팅해준다.

package me.whiteship.chapter05.item26.terms;

public class Box<E> {

    private E item;

    private void add(E e) {
        this.item = e;
    }

    private E get() {
        return this.item;
    }

    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        box.add(10);
        System.out.println(box.get() * 100);

        printBox(box);
    }

    private static void printBox(Box<?> box) {
        System.out.println(box.get());
    }

}

오브젝트로 꺼내고 Integer로 캐스팅하는 코드를 java가 컴파일할 때 넣어주는 것이다.
전부 다 타입은 없어진다.

사실상 컴파일된 코드는 다 로우 타입처럼 보이긴 하지만 우리가 입력했던 이런 타입정보들 때문에 컴파일 할 때 Java 컴파일러가 타입 형변화되는 코드를 안에 넣어 줄 수가 있는 것이다.

이렇게 만들어진 이유는 이전 버전과의 호환성을 유지하기 위해서 그렇게 동작한다.

package me.whiteship.chapter05.item26.object;

import java.util.ArrayList;
import java.util.List;

// 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용 (156-157쪽)
public class Raw {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다.
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }
}

우리는 String을 기대했는데 Integer를 꺼냈기 때문에 오류가 난다. 넣을 때 나오는게 아니라 꺼낼때 나온다.

Object로 넣으면 아예 넣을 수가없다.

이렇게 되면 타입 안정성을 잃었다고 표현을 한다.

package me.whiteship.chapter05.item26.object;

import java.util.ArrayList;
import java.util.List;

// 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용 (156-157쪽)
public class Raw {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다.
    }

    private static void unsafeAdd(List<String> list, String o) {
        list.add(o);
    }
}

제대로 쓰려면 이렇게 써서 42를 잘못 넣었다는 것을 컴파일할 때 알수 있게 했어야 했다.

package me.whiteship.chapter05.item26.unbounded;

import java.util.HashSet;
import java.util.Set;

public class Numbers {

    static int numElementsInCommon(Set s1, Set s2) {
    	s1.add("whiteship');
        int result = 0;
        for (Object o1 : s1) {
            if (s2.contains(o1)) {
                result++;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        System.out.println(Numbers.numElementsInCommon(Set.of(1, 2, 3), Set.of(1, 2)));
    }
}

String이 들어가도 들어가진다.

package me.whiteship.chapter05.item26.unbounded;

import java.util.HashSet;
import java.util.Set;

public class Numbers {

    static int numElementsInCommon(Set<?> s1, Set<?> s2) {
        int result = 0;
        for (Object o1 : s1) {
            if (s2.contains(o1)) {
                result++;
            }
        }

        return result;
    }

    public static void main(String[] args) {
        System.out.println(Numbers.numElementsInCommon(Set.of(1, 2, 3), Set.of(1, 2)));
    }
}

그러나 ?는 한 종류의 타입만 들어오게 해준다.
raw 타입은 아무거나 추가할 수 있다.
비한정적 타입을 선언한 셋에는 null 밖에 넣을 수가 없다.

package me.whiteship.chapter05.item26.raw;

public class UseRawType<E> {

    private E e;

    public static void main(String[] args) {
        System.out.println(UseRawType.class);

        UseRawType<String> stringType = new UseRawType<>();

        System.out.println(stringType instanceof UseRawType);
    }
}
class를 컴파일 했을 때는 UseRawType<Integer>.class 이런거는 없다.
package me.whiteship.chapter05.item26.raw;

public class UseRawType<E> {

    private E e;

    public static void main(String[] args) {
        System.out.println(UseRawType<Integer>.class);

        UseRawType<String> stringType = new UseRawType<>();

        System.out.println(stringType instanceof UseRawType);
    }
}
package me.whiteship.chapter05.item26.raw;

public class UseRawType<E> {

    private E e;

    public static void main(String[] args) {
        System.out.println(UseRawType.class);

        UseRawType<String> stringType = new UseRawType<>();

        System.out.println(stringType instanceof UseRawType<String>);
    }
}

instanceof에는 쓸 수는 있지만 어차피 소거가 된다.

아이템 26. 완벽 공략 - GenericRepository

package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class AccountRepository {

    private Set<Account> accounts;

    public AccountRepository() {
        this.accounts = new HashSet<>();
    }

    public Optional<Account> findById(Long id) {
        return accounts.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Account account) {
        this.accounts.add(account);
    }
}
package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class MessageRepository {

    private Set<Message> messages;

    public MessageRepository() {
        this.messages = new HashSet<>();
    }

    public Optional<Message> findById(Long id) {
        return messages.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Message message) {
        this.messages.add(message);
    }
}

Account를 Message로만 변경을 했다.

package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class GenericRepository<E extends Entity> {
    private Set<E> entities;

    public GenericRepository() {
        this.entities = new HashSet<>();
    }

    public Optional<E> findById(Long id) {
        return entities.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(E entity) {
        this.entities.add(entity);
    }
}

getId를 Entity 타입이라 사용가능하다.위를 사용하여 아래와 같이 변경가능하다. 생산성이 좋아진다.

package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class AccountRepository extends GenericRepository<Account> {

}

package me.whiteship.chapter05.item26.genericdao;

import java.time.LocalDateTime;

public class Schedule implements Entity{
    private Long id;

    private LocalDateTime when;
    @Override
    public Long getId() {
        return null;
    }

    public LocalDateTime getWhen() {
        return when;
    }
}
package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class ScheduleRepository {
    private Set<Schedule> schedules;

    public ScheduleRepository() {
        this.schedules = new HashSet<>();
    }

    public Optional<Message> findById(Long id) {
        return schedules.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Message message) {
        this.schedules.add(message);
    }
}

등등 이렇게 기존 코드를 개발하여 사용하는 경우 변수명 등을 놓치기 때문에 새로 만들 때 오류가 나올 수 있다.

그러나 GenericRepository를 사용하면 쉽게 바꿀 수 있다.

package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class ScheduleRepository extends GenericRepository<Schedule> {

}

이게 다다.

package me.whiteship.chapter05.item26.genericdao;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class MessageRepository extends GenericRepository<Message> {
}

MessageRepository도 이렇게 변경 가능하다.

0개의 댓글