아이템 28. 배열보다는 리스트를 사용하라

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

아이템 28. 핵심 정리 1

package me.whiteship.chapter05.item28.erasure;

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

public class IntegerToString {

    public static void main(String[] args) {
        // 공변
        Object[] anything = new String[10];
        anything[0] = 1;

        // 불공변
        List<String> names = new ArrayList<>();
//        List<Object> objects = names;


//        // 제네릭과 배열을 같이 사용할 수 있다면...
//        List<String>[] stringLists = new ArrayList<String>[1];
//        List<Integer> intList = List.of(42);
//        Object[] objects = stringLists;
//        objects[0] = intList;
//        String s = stringLists[0].get(0);
//        System.out.println(s);
    }
}

오브젝트가 최상위 타입이니까 String은 오브젝트로 변환이 가능하다.
제네릭은 불공변이다.
배열은 공변이다. 배열안에 들어가는 타입을 어떤 타입으로 변환하는 것이 가능하다.
안에 들어가는 실제 인스턴스는 String의 배열이다.
anyting[0] = 1; 인 경우 버그이다. 근데 컴파일러에 못잡는다. 왜냐면 배열이 공변이기 때문이다.

불공변 : 상위타입, 하위타입이 의미가 없는 것이다.

List에서 String과 Object는 그냥 서로 다른 타입이다.

실체화를 한다. 실체화라는 것은 내가 작성한 타입이 런타임에도 유지가 되느냐
런타임에도 스트링 배열은 스트링인거다.
배열은 실체화가 된다.

제네릭은 List<String>은 사라진다. String 정보가 컴파일 하고 나면 사라진다. 하위 버전 호환성 때문에 이전 컴파일러에서 잘 가져올 수 있도록 하기 위해서이다. 
package me.whiteship.chapter05.item28.erasure;

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

public class MyGeneric {

    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("keesun");
        String name = names.get(0);
        System.out.println(name);
    }

}
package me.whiteship.chapter05.item28.erasure;

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

public class MyGeneric {

    public static void main(String[] args) {
//        List<String> names = new ArrayList<>();
//        names.add("keesun");
//        String name = names.get(0);
//        System.out.println(name);


        List names = new ArrayList();
        names.add("keesn");
        Object o = names.get(0);
        String name = (String) o;
        System.out.println(name);
    }

}

위의 주석 코드는 아래와 같이 흘러가게 된다.

new ArrayList를 만드는데 String이라는 정보는 없다.

package me.whiteship.chapter05.item28.erasure;

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

public class IntegerToString {

    public static void main(String[] args) {
        // 공변
        Object[] anything = new String[10];
//        anything[0] = 1;

        // 불공변
        List<String> names = new ArrayList<>();
//        List<Object> objects = names;


//        // 제네릭과 배열을 같이 사용할 수 있다면...
        List<String>[] stringLists = new ArrayList<String>[1];
        List<Integer> intList = List.of(42);
        Object[] objects = stringLists;
        objects[0] = intList;
        String s = stringLists[0].get(0); //이때 깨진다. String의 배열이니까 Integer를 String으로 형변환하기 때문이다.
//        System.out.println(s);
    }
}

제네릭과 배열을 같이 사용할 수 없다.

아이템 28. 핵심 정리 2

package me.whiteship.chapter05.item28.array_to_list;

import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

// 코드 28-6 배열 기반 Chooser
public class Chooser_Array {
    private final Object[] choiceList;

    public Chooser_Array(Collection choices) {
        choiceList = choices.toArray();
    }

    public Object choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList[rnd.nextInt(choiceList.length)];
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);

        Chooser_Array chooser = new Chooser_Array(intList);

        for (int i = 0; i < 10; i++) {
            Number choice = (Number) chooser.choose();
            System.out.println(choice);
        }
    }
}
package me.whiteship.chapter05.item28.array_to_list;

import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

// 코드 28-6 배열 기반 Chooser
public class Chooser_Array {
    private final Object[] choiceList;

    public Chooser_Array(Collection choices) {
        choiceList = choices.toArray();
    }

    public Object choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList[rnd.nextInt(choiceList.length)];
    }

    public static void main(String[] args) {
//        List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);
        List<String> intList = List.of("whiteship", "keesun");

        Chooser_Array chooser = new Chooser_Array(intList);

        for (int i = 0; i < 10; i++) {
            Number choice = (Number) chooser.choose();
            System.out.println(choice);
        }
    }
}

위와 같이 String 변수가 들어와도 적용이 되어야 한다. String을 넘겼는데 Number로 형변환을 한다면 문제가 생긴다.

이렇게 범용적인 클래스를 만들 때 이런 타입 형변환의 문제를 해결하고자 등장한게 제네릭이다.

타입안전성이 컴파일 할 때 모르기 때문이다. 상호호환하는 타입인지 보장을 못하기 때문이다.

package me.whiteship.chapter05.item28.array_to_list;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

// 코드 28-6 배열 기반 Chooser
public class Chooser_Array<T> {
    private final List<T> choiceList;


    public Chooser_Array(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }

    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }

    public static void main(String[] args) {
        List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);

        Chooser_Array<Integer> chooser = new Chooser_Array<>(intList);

        for (int i = 0; i < 10; i++) {
            Number choice = (Number) chooser.choose();
            System.out.println(choice);
        }
    }
}
package me.whiteship.chapter05.item28.array_to_list;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

// 코드 28-6 배열 기반 Chooser
public class Chooser_Array<T> {
    private final List<T> choiceList;


    public Chooser_Array(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }

    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }

    public static void main(String[] args) {
        List<String> intList = List.of("keesun","whiteship");

        Chooser_Array<String> chooser = new Chooser_Array<>(intList);

        for (int i = 0; i < 10; i++) {
            String choice = chooser.choose();
            System.out.println(choice);
        }
    }
}

String으로 바꾸려면 generic을 다 바꿔서 줘야 하므로 더 안전하게 작성 가능하다.

아이템 28. 완벽 공략 @SafeVarargs

package me.whiteship.chapter05.item28.safevarags;

import java.util.List;

public class SafeVaragsExample {

//    @SafeVarargs // Not actually safe!
    static void notSafe(List<String>... stringLists) {
        Object[] array = stringLists; // List<String>... => List[], 그리고 배열은 공변이니까.
        List<Integer> tmpList = List.of(42);
        array[0] = tmpList; // Semantically invalid, but compiles without warnings
        String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
    }

    @SafeVarargs
    static <T> void safe(T... values) {
        for (T value: values) {
            System.out.println(value);
        }
    }

    public static void main(String[] args) {
        SafeVaragsExample.safe("a", "b", "c");
        SafeVaragsExample.notSafe(List.of("a", "b", "c"));
    }

}
List<String>... stringLists

전달받은 객체가 오염될 수 있다.
내부적으로 generic 타입의 배열이 생길 수 있기 때문이다.
List의 배열이니까 당연히 Object 배열에 Assign 할 수 있다.
배열은 공변이니까 그 상위타입, 하위타입을 따르니까다.
막상 꺼냈을 때 클래스 캐스트 익셉션이 발생하게 된다.

    //@SafeVarargs
    static <T> void safe(T... values) {
        for (T value: values) {
            System.out.println(value);
        }
    }

제네릭이 떨어지니까 경고가 발생하는데 안에서 하는게 없다.
경고를 무시해주는 애들은 세이프 한 곳에만 작성하는게 맞다.

0개의 댓글