
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);
}
}
제네릭과 배열을 같이 사용할 수 없다.
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을 다 바꿔서 줘야 하므로 더 안전하게 작성 가능하다.

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);
}
}
제네릭이 떨어지니까 경고가 발생하는데 안에서 하는게 없다.
경고를 무시해주는 애들은 세이프 한 곳에만 작성하는게 맞다.
