import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.StringTokenizer;
public class aa {
public static void main(String[] args) throws IOException{
long a = 2;
double b =1.0;
long z=1;
long c=200000;
long test=0;
for(int i=0;i<19;i++)
{
a*=10;
}
a+=1;
System.out.println(a+"이다~! "+(a+b)+"이다~! "+a%10+" "+(a+b)%10+"이다 "+(a+z)%10);
System.out.println(a+b);
System.out.println(c+b+ " "+(c+b)%10);
}
}
정수형에 실수형을 더하면 1.xx*10e형식으로 변하고 소수 16자리?까지 값 보장이 안되므로 수의 길이가 길면 값이 제대로 더해지지 않는다.
다른사람풀이
나와는 다르게 문자열로 보고 문제를 해결함
class Solution {
public int solution(int n) {
String a = "";
while(n > 0){
a = (n % 3) + a;
n /= 3;
}
a = new StringBuilder(a).reverse().toString();
return Integer.parseInt(a,3);
}
}
두 정수left와right가 매개변수로 주어집니다.left부터right까지의 모든 수들 중에서, 약수의 개수가 짝수인 수는 더하고, 약수의 개수가 홀수인 수는 뺀 수를 return 하도록 solution 함수를 완성해주세요.
내가짠 코드
class Solution {
public int solution(int left, int right) {
int answer = 0;
for(int k=left;k<=right;k++){
if(sum(k)%2==0)
{
answer+=k;
}
else
{
answer-=k;
}
}
return answer;
}
public int sum(int a)
{
int count=0;
for(int i=1;i<=a;i++)
{
if(a%i==0)
{++count;}
}
return count;
}
}
참고할만한 다른사람 코드
import java.util.*;
class Solution {
int yaksu(int x) {
if(x == 1) return 1;
Set<Integer> set = new HashSet<>();
for(int i = 1; i <= x / 2; i++) {
if(x % i == 0) {
set.add(i);
set.add(x / i);
}
}
return set.size();
}
public int solution(int left, int right) {
int answer = 0;
for(; left <= right; left++) answer += left * (yaksu(left) % 2 == 0 ? 1 : -1);
return answer;
}
}
@FunctionalInterface
interface Calculate{
int cal(int a, int b);
}
@FunctionalInterface 어노테이션을 붙여서 함수형 인터페이스에 부합하는지확인할 수 있다.
(컴파일타임에검사)
*static이나 default선언이 붙은 메서드의 경우 함수형 인터페이스에 영향을 끼치지는 않는다.
@FunctionalInterface
public interface Calculate{
int cal(int a, int b);
default int plus(int a, int b) { return a+b;}
static int sub(int a, int b){return a-b;}
}
인터페이스는 제네릭으로 사용 가능
함수형 인터페이스 종류(반환하는지, 매개변수 등에 따라 분류)
Function<T,R> T타입 인자 받고 R타입 객체 리턴
Consumer<T> T타입 인자 받고 리턴 없음
Runnable 인자도 안받고 리턴값도 없음
Predicate<T> T타입 인자 받고 결과로 boolean 리턴
Supplier<T> 인자를 받지않고 T타입 객체 리턴
[ 메소드 참조(Method Reference) ]
메소드 참조란함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법이다. 일반 메소드를 참조하기 위해서는 다음의 3가지 조건을 만족해야 한다.
함수형 인터페이스의 매개변수 타입 = 메소드의 매개변수 타입
함수형 인터페이스의 매개변수 개수 = 메소드의 매개변수 개수
함수형 인터페이스의 반환형 = 메소드의 반환형
참조가능한 메소드는일반 메소드, Static 메소드, 생성자가 있으며클래스이름::메소드이름으로 참조할 수 있다. 이렇게 참조를 하면 함수형 엔터페이스로 반환이 된다. 3가지의 메소드에 대해 메소드 참조 예시를 자세히 살펴보도록 하자.
1. 일반 메소드 참조
예를 들어 위에서 보여준 Function에 메소드 참조를 적용한다고 하자. 우선 해당 메소드(length)가 위의 3가지 조건을 만족하는지 살펴보아야 한다.
매개변수 없음
매개변수 개수 = 0개
반환형 = int
우리가 선언한 function과 String의 length 함수는 모두 매개변수가 없으며, 반환형이 int로 동일하기 때문에 String::length로 다음과 같이 메소드 참조를 적용할 수 있다.
// 기존의 람다식
Function<String, Integer> function = (str) -> str.length(); function.apply("Hello World");
// 메소드 참조로 변경 Function<String, Integer> function = String::length; function.apply
("Hello World");
또한 추가로 예시를 살펴보자. System.out.println() 메소드는 반환형이 void이며, 파라미터로 String을 받는 메소드이다. 그렇기 때문에 우리는 Consumer에System.out.println() 메소드를 참조시킬 수 있다.
// 일반 메소드를 참조하여 Consumer를 선언한다.
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!!");
// 메소드 참조를 통해 Consumer를 매개변수로 받는 forEach를 쉽게 사용할 수 있다.
List<String> list = Arrays.asList("red", "orange", "yellow", "green", "blue");
list.forEach(System.out::println);
//interface Iterable<T> default void forEach(Consumer<? super T> action)
{ Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
2. Static 메소드 참조
Static 메소드 역시 메소드 참조가 가능하다. 예를 들어 Objects의 isNull은 반환값이 Boolean이며, 매개변수 값은 1개이고, 매개 변수가 Object이므로 Predicate로 다음과 같이 메소드 참조가 가능하다.
Predicate<Boolean> predicate = Objects::isNull;
// isNull 함수
public static boolean isNull(Object obj) { return obj == null; }
3. 생성자 참조
생성자도 메소드 참조를 할 수 있다. 생성자는 new로 생성해주므로 클래스이름::new로 참조할 수 있다. Supplier는 매개변수가 없이 반환값만을 갖는 인터페이스이기 때문에, 매개변수 없이 String 객체를 새롭게 생성하는 String의 생성자를 참조하여 Supplier로 선언할 수 있다.
매개변수화 되는 대상은 원소가 아닌 컨테이너 자신이다 Set<Integer>가 있을 때 매개변수화 되는 것은Integer가 아니라List<Integer>이다.
하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다. 이보다 유연한 수단 :타입 안전 이종 컨테이너 패턴
타입 안전 이종 컨테이너 패턴(type safe heterogeneous container pattern) = 컨테이너 대신 키를 매개변수화 한 다음, 컨테이너에 값을 넣거나 뺄대 매개변수화 한 키를 함께 제공한다. 각 타입의 Class 객체를 매개변수화한 키 역할로 사용한다 : 이때 class 리터럴의 타입은Class<T>이다.
public class Favorites{ // 타입 이종 컨테이너 추상화
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type)
}
타입토큰: 컴파일 타임 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 class 리터럴
public class Favorites { // 타입 이종 컨테이너 구현
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
여기서 와일드 카드 타입으로 put 할수 없다고 생각할 수 있지만, 이때 키가 와일드 카드 타입이기 때문에 넣을 수 있다.
Map의 값이 Object를이기 대문에 Class의 cast 메서드를 사용해 동적 형변환한다. 이때 cast 메서드에서 제네릭의 이점을 완벽히 사용한다 : 비검사 형변환 없이도 Favorites를 타입 안전하게 한다.
public class Class<T>{
T cast(Object obj);
}
2. 타입 안전 이종 컨테이너의 제약
a. 악의적인 클라이언트가 Class 객체를 로타입으로 넘기면 Favorites 인스턴스의 타입 안정성이 쉽게 깨진다.
f.putFavorite((Class) Integer.class, "Integer의 인스턴스가 아니다.");
int favoriteInteger = f.getFavorite(Integer.class)
따라서 위에 구현한대로, put을 해줄 당시에 type.cast(instacne)와 같은 동적 형변환을 넣어주자
b. 실체화 불가 타입에는 사용할 수 없다.
List<String>용 Class 객체를 얻을 수 없기 때문이다.
List.class를 사용해야하지만 이렇게 했을 때List<String>.class,List<Integer>.class모두를 허용하여 객체 참조를 한다면 오류가 많아질 것이다.
3. 한정적 타입 토큰
한정적 타입 매개변수나 한정적 와일드카드를 사용하여 표현가능한 타입을 제한하는 타입토큰
애너테이션 API는 한정적 타입 토큰을 적극적으로 사용한다.
public <T extends Annotation> T getAnnotation(Class<T> annotationType)
annotationType: 애너테이션 타입을 뜻하는 한정적 타입 토큰 대상 요소에 달려있는 애너테이션을 런타임에 읽어오는 기능을 한다. 이 메서드는 토큰으로 명시한 타입의 애너테이션이 대상 요소에 달려있으면 그 애너테이션을 반환하고, 없다면 null을 반환
즉, 애너테이션된 요소는 그 키가 애너테이션 타입인 타입 안전 이종 컨테이너인 것이다.
Class<?> 타입의 객체를 한정적 타입 토큰을 받는 메서드에 넘기고 싶을 때
asSubclass 메서드 : 호출된 인스턴스 자신의 Class 객체를 인수가 명시한 클래스로 형변환 한다.
if 성공 : 인수로 받은 클래스 객체를 반환 else : ClassCastException
가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. 그런데 내부로 감춰야 했을 이 배열을 그만 클라이언트에 노출하는 문제가 생겼다. 그 결과 varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 알기 어려운 컴파일 경고가 발생한다.
warning: [unchecked] Possible heap pollution from parameterized vararg type List<String>
매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. 다음 메서드를 통해 확인해보자.
이 메서드 에서는 형변환하는 곳이 보이지 않는데도 인수를 건네 호출하면 ClassCastException을 던진다. 마지막 줄에 컴파일러가 생성한 (보이지 않는)형변환이 숨어 있기 때문이다.
제네릭 배열을 프로그래머가 직접 생성하는 건 허용하지 않으면서 제네릭 varargs 매개변수를 받는 메서드를 선언할 수 있게 한 이유는 무엇일까? 그 답은 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 메서드가 실무에서 매우 유용하기 때문이다. 사실 자바 라이브러리도 이런 메서드를 여럿 제공하는데, Arrays.asList(T... a), Collections.addAll(Collection<? super T> c, T... elements), EnumSet.of(E first, E...rest)가 대표적이다.
그래서 사용자는 이 경고 들을 그냥 두거나 (더 흔하게는) 호출하는 곳마다 @SuppressWarnings("unchkecked") 에너테이션을 달아 경고를 숨겨야 했다. 그래서 자바 7에서는 @SafeVarargs 애너테이션이 추가되어 제네릭 가변인수 메서드 작성자가 클라이언트 측에서 발생하는 경고를 숨길 수 있게 되었다. @SafeVarargs 애너테이션은 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치다. 컴파일러는 이 약속을 믿고 그 메서드가 안전하지 않을 수 있다는 경고를 더 이상 하지 않는다.
그렇다면 메서드가 안전한지는 어떻게 확신할 수 있을까?
메서드가 이 배열에 아무것도 저장하지 않고(그 매개변수들을 덮어쓰지 않고) 그 배열의 참조가 밖으로 노출되지 않는다면(신뢰할 수 없는 코드가 배열에 접근할 수 없다면)타입 안전하다.
static <T> T[] toArray(T... args) { return args;}
이 메서드가 반환하는 배열의 타입은 이 메서드에 인수를 넘기는 컴파일 타임에 결정되는데, 그 시점에는 컴파일러에게 충분한 정보가 주어지지 않아 타입을 잘못 판단할 수 있다.
static <T> T[] pickTwo(T a, T b, T c) { switch(ThreadLocalRandom.current().nextInt(3)) { case 0: return toArray(a, b); case 0: return toArray(a, b); case 0: return toArray(a, b); } throw new AssertionError(); // 도달할 수 없다. }
아무런 문제가 없는 메서드이니 별다른 경고 없이 컴파일된다. 하지만 실행하려 들면 ClassCastException을 던진다. Object[]는 String[]의 하위 타입이 아니므로 이 형변환은 실패하기 때문이다.
즉, 다음 두 조건을 모두 만족하는 제네릭 varargs 메서드는 안전하다. 둘 중 하나라도 어겼다면 수정하라
다음은 제네릭 varargs 매개변수를 안전하게 사용하는 전형적인 예다. 다음의 flattern 메서드는 임의 개수의 리스트를 인수로 받아, 받은 순서대로 그 안의 모든 원소를 하나의 리스트로 옮겨 담아 반환한다.
@SafeVarargsstatic <T> List<T> flatten(List<? extends T>... lists)
{ List<T> result = new ArrayList<>(); for (List<? extends T> list : lists)
result.addAll(list); return result;}
정리
가변인수와 제네릭은 궁합이 좋지 않다. 가변인수 기능은 배열을 노출하여 추상화가 완벽하지 못하고, 배열과 제네릭의 타입 규칙이 서로 다르기 때문이다. 메서드에 제네릭 (혹은 매개변수화된) varags 매개변수를 사용하고자 한다면, 먼저 그 메서드가 타입 안전한지 확인한 다음 @SafeVarargs 애너테이션을 달아 사용하는 데 불편함이 없게끔 하자
JVM에 있는 .class 파일들을 Execution Engine의 Interpreter와 JIT Complier를 통해 해석된다
해석된 바이트 코드는 Runtime Data Areas 에 배치되어 실질적인 수행이 이루어진다
Class Loader(Loading,linking,initializing)
자바프로그램을 실행하면.java파일은 컴파일러를 통해.class(자바 바이트코드)파일로 변환된다.Class Loader는 해당 파일을 Runtime(실행)하는 시점에Runtime Data Areas에 로딩시킨다. 즉, 컴파일하는 시점이 아니라 클래스를 처음으로 참조하는 시점(클래스의 인스턴스를 만들 때)Class Loader를 통해 메모리(Runtime Data Areas)에 로드한다.
Runtime Data Areas
런타임 데이터 영역은 JVM이 OS 위에서 실행될 때 할당받는 메모리 영역이다.
- PC Register
현재 수행중인 JVM 명령의 주소가 저장되어있다. CPU의 Register와는 다름, 연산을 위해 필요한 피연산자를 임시로 저장하기 위한 용도로 사용한다.
- JVM Stack(메서드들 영역)..T메모리
Stack Frame을 저장하는 스택. JVM은 오직Stack Frame을 push하고 pop하는 작업만 함. 예외 발생 시printStackTrace()등의 메서드로 보여주는 Stack Trace의 각 라인은 하나의 스택 프레임을 표현
Stack Frame(스택프레임) : 메서드가 수행될 때마다 하나의 스택 프레임이 생성되어 해당 스레드의 JVM 스택에 추가되고 메서드가 종료되면 스택 프레임이 제거된다. 각 스택 프레임은Local Variable Array,Operand Stack, 현재 실행 중인 메서드가 속한 클래스의Runtime Constant Pool에 대한 레퍼런스를 갖는다. 지역 변수 배열, 피연산자 스택의 크기는 컴파일 시에 결정되기 때문에 스택 프레임의 크기도 메서드에 따라 크기가 고정된다.
Local Variable Array(지역변수 배열) : 0부터 시작하는 인덱스를 가진 배열. 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부터는 메서드에 전달된 파라미터들이 저장되며, 메서드 파라미터 이후에는 메서드의 지역 변수들이 저장된다.
Operand Stack(피연산자 스택) : 메서드의 실제 작업 공간. 각 메서드는피연산자 스택과지역 변수 배열사이에서 데이터를 교환하고, 다른 메서드 호출 결과를 push하거나 pop한다. 피연산자 스택 공간이 얼마나 필요한지는 컴파일할 때 결정할 수 있으므로, 피연산자 스택의 크기도 컴파일 시에 결정된다.
- Native Method Stack
자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다. 즉, JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 C 스택이나 C++ 스택이 생성된다.
- Heap(객체들이 있는곳)..T메모리
인스턴스(객체)를 저장하는 공간.GC(Garbage Collection)의 대상이다. 성능 이슈를 일으키는 공간
- Method Area(스테틱영역 클래스들)
JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한Runtime Constant Pool, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관한다. 이 영역에 있는 클래스 정보로Heap영역에 객체를 생성한다.즉, 매우 중요한 영역!
메서드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM(HotSpot JVM)에서는 흔히 Permanent Area, 혹은 Permanent Generation(PermGen)이라고 불린다. 메서드 영역에 대한 가비지 컬렉션은 JVM 벤더의 선택 사항이다.
Runtime Constant Pool(런타임 상수 풀) : 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역이다.Method Area에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다. 각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다. 즉, 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.
Execution Engine
Execution Engine은Class Loader를 통해 JVM 내의Runtime Data Areas에 배치된 바이트코드를 실행한다.Execution Engine은 자바 바이트코드를 명령어 단위로 읽어서 실행한다
그런데 자바 바이트코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하며, 그 방식은 다음 두 가지가 있다.
Interpreter: 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나씩 해석하고 실행하기 때문에 바이트코드 하나하나의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다는 단점을 가지고 있다. 흔히 얘기하는 인터프리터 언어의 단점을 그대로 가지는 것이다. 즉, 바이트코드라는 ‘언어’는 기본적으로 인터프리터 방식으로 동작한다.
JIT(Just-In-Time) Compiler: 인터프리터의 단점을 보완하기 위해 도입된 것이 JIT 컴파일러이다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.
JIT 컴파일러가 컴파일하는 과정은 바이트코드를 하나씩 인터프리팅하는 것보다 훨씬 오래 걸리므로, 만약 한 번만 실행되는 코드라면 컴파일하지 않고 인터프리팅하는 것이 훨씬 유리하다. 따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.
GC
Garbage Collection은 메모리 관리를 위한 방법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역[Heap]을 해제하는 기능. (new 연산자를 이용한 객체)
Java GC는 객체가 garbage인지 판단하기 위해 reachability 개념을 사용하여 객체의 유효한 참조가 있다면 ‘reachable’ 로 없으면 ‘unreachable’ 로 구별하고, unreachable 객체를 Garbage로 간주하여 GC를 실행한다. 한 객체가 다른 객체를 참조하며, 다른 객체는 또다른 객체를 참조할 경우에는 유효한 최초의 참조가 무엇인기 파악해야 되는데, 이를 객체 참조의 root set이라고 한다.
Heap영역에 있는 객체들에 대한 참조의 종류는 4가지이다.
Heap내의 다른 객체에 의한 참조
JVM Stack, Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
Native Stack, JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
Method Area의 static 변수에 의한 참조
이들 중Heap내의 다른 객체에 의한 참조를 제외한 나머지 3개(JVM Stack, Native Stack, Method Area)가 root set으로, reachability를 판가름하는 기준이 된다. 즉 root set으로부터 시작한 객체들은 reachable이며, root set과 무관한 객체들이 unreachable 객체로 GC의 대상이 된다.
------------------------
Jit Compiler
자주쓰이는 코드는 JVM이 machine code로 컴파일해서 속도를 높임.
바이트코드를 native code로 변환하는 작업은 JVM내에 별도의 쓰레드에서 이루어진다.
컴파일 작업이 끝나면 jvm은 바이트코드대신 컴파일된 버전을 사용함
Garbage Collector
실행되면 해당스레드외 다른스레들은 잠시 멈춘다.
가비지 컬렉션 과정 - Generational Garbage Collection
GC에 대해서 알아보기 전에 알아야 할 용어가 있다. 바로 'stop-the-world'이다. stop-the-world란, GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작한다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생한다. 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.
Java는 프로그램 코드에서 메모리를 명시적으로 지정하여 해제하지 않는다. 가끔 명시적으로 해제하려고 해당 객체를 null로 지정하거나 System.gc() 메서드를 호출하는 개발자가 있다. null로 지정하는 것은 큰 문제가 안 되지만, System.gc() 메서드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 끼치므로 System.gc() 메서드는 절대로 사용하면 안 된다(다행히도 NHN에서 System.gc() 메서드를 호출하는 개발자를 보진 못했다).
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다" // ArrayStoreException
실체화(reify)된다 : 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.
제네릭
불공변 (invariant) - 서로 다른 타입Type1,Type2가 있을 때List<Type1>은List<Type2>의 하위타입도 아니고 상위타입도 아니다
리스트에서는 컴파일할 때 타입 오류를 바로 알 수 있다
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다.
ol.add("타입이 달라 넣을 수 없다.")
소거(erasure)된다 : 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알 수 없다. 제네릭 지원 전과 제네릭 타입을 함게 사용할 수 있게 해주는 메커니즘이다.
2. 제네릭 배열은 사용불가하다.
배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다. new List<E>[],new List<String>[],new E[]: 컴파일시 제네릭 생성오류를 일으킨다.
이유 :타입안전하지 않기 때문이다 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다 : 제네릭 타입의 취지와 맞지 않다.
3. 실체화 불가 타입
E,List<E>,List<String>: 실체화 불가 타입
실체화 되지 않아 런타임에는 컴파일 타임보다 타입점보를 적게 가지는 타입
List<?>,Map<?,?>: 소거 메커니즘 때문에 매개변수화 타입 가운데 실체화 가능한 타입 - 비한정적 와일드카드 타입
4. 배열의 불편함
a. 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 불가능하다.
아이템 33의 타입 안전 이종 컨테이너를 이용하여 자신의 원소타입을 추론할 수있다 (우회)
b. 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메세지를 받게된다.
가변인수 메서드를 호출할 때마다. 가변인수를 담는 배열이 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이면 경고가 뜬다. @SafeVarargs로 해결한다.
5. 배열대신 리스트를 사용하자
장점 : 타입 안정성과 상호 운용성이 좋아진다.
단점 : 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있다.
public class Chooser<T>{
private final List<T> choiceList;
public Chooser(Collection<T> choices){
choiceList = new ArrayList<>(choices);
}
public T choose(){
Random rnd = ThreadLocalRandom.current();
return choiceList.get(rnd.netInt(choiceList.size()));
}
}
List를 사용하면 런타임에 ClassCastException을 만날 일은 없다.
public class Chooser<T>{
private final T[] choiceArray;
public Chooser(Collection<T> choices){
choicesArra = (T[]) choices.toArray;
}
public Object choose(){
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
T[] 로의 타입캐스팅 과정에서 경고가 뜬다. 제네릭에서는 원소의 타입정보가 소거되어 런타임에는 타임 정보를 알 수 없기 때문이다. item 27 비검사 경고를 제거하라는 말에 따라 위험요소를 제거할 수 있는 최선의 방법은 List로 구현하는 것이었다.