안녕! 오늘 수업에서는 계속해서 제네릭에 대해 공부하겠습니다. 공교롭게도 이것은 큰 주제이지만 피할 수는 없습니다. 언어의 매우 중요한 부분 입니다 . 수정 가능한 유형 . 수정 가능한 유형은 런타임에 정보를 완전히 사용할 수 있는 유형입니다. Java에서 이러한 유형에는 프리미티브, 원시 유형 및 비제네릭 유형이 포함됩니다. 반대로 수정 불가능 유형은 정보가 지워지고 런타임에 액세스할 수 없게 되는 유형입니다. 공교롭게도 이들은 제네릭 —
List<String>
, List<Integer>
등 입니다.
그런데 varargs가 무엇인지 기억하십니까?
잊어버렸을 경우를 대비해 이것은 가변 길이 인수입니다. 메서드에 얼마나 많은 인수가 전달될지 모르는 상황에서 유용합니다. 예를 들어 메서드가 있는 계산기 클래스가 있는 경우입니다sum
. 메서드 sum()
는 2개, 3개, 5개 또는 원하는 만큼 받을 수 있습니다. sum()
가능한 모든 인수 수에 대해 메서드를 오버로드하는 것은 매우 이상할 것입니다 . 대신 다음과 같이 할 수 있습니다.
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
콘솔 출력:
15
11
이는 제네릭과 함께 varargs를 사용할 때 몇 가지 중요한 기능이 있음을 보여줍니다. 다음 코드를 살펴보겠습니다.
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // This is okay
"Leonardo da Vinci",
"Vasco de Gama"
);
// but here we get a warning
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
이 메서드는 a 와 임의 개수의 객체를 addAll()
입력으로 받은 다음 이러한 모든 객체를 목록에 추가합니다. 메서드 에서 메서드를 두 번 호출합니다 . 첫 번째 경우에는 두 개의 일반 문자열을 . 여기에 모든 것이 정돈되어 있습니다. 두 번째 경우에는 에 두 개의 객체를 추가합니다 . 그러나 여기서 우리는 예기치 않게 다음과 같은 경고를 받습니다. List<E>
E
main()
addAll()
List
Pair<String, String>
List
Unchecked generics array creation for varargs parameter
그게 무슨 뜻이야? 경고가 표시되는 이유는 무엇이며 에 대한 언급이 있는 이유는 무엇입니까 array
? array
결국, 우리 코드에는 ! 가 없습니다. 두 번째 경우부터 시작하겠습니다. 경고는 컴파일러가 가변 길이 인수(varargs)를 배열로 변환하기 때문에 배열을 언급합니다. 즉, 우리 addAll()
방법의 서명은 다음과 같습니다.
public static <E> void addAll(List<E> list, E... array)
실제로는 다음과 같습니다.
public static <E> void addAll(List<E> list, E[] array)
즉, main()
메서드에서 컴파일러는 코드를 다음과 같이 변환합니다.
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
배열 String
은 괜찮습니다. 그러나 Pair<String, String>
배열은 그렇지 않습니다. 문제는 그것이 Pair<String, String>
수정 불가능한 유형이라는 것입니다. 컴파일하는 동안 형식 인수(<String, String>)에 대한 모든 정보가 지워집니다. 재정의할 수 없는 유형의 배열을 만드는 것은 Java에서 허용되지 않습니다 . Pair<String, String> 배열을 수동으로 만들려고 하면 이것을 볼 수 있습니다.
public static void main(String[] args) {
// Compilation error Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
그 이유는 명백합니다. 유형 안전성입니다. 기억하시겠지만 배열을 만들 때 배열이 저장할 객체(또는 프리미티브)를 지정해야 합니다.
int array[] = new int[10];
이전 수업 중 하나에서 유형 삭제를 자세히 살펴보았습니다. Pair
이 경우 유형 삭제로 인해 개체가 쌍을 저장 하는 정보가 손실됩니다 <String, String>
. 배열을 만드는 것은 안전하지 않습니다. varargs 및 제네릭과 관련된 메서드를 사용할 때 유형 삭제 및 작동 방식에 대해 기억해야 합니다. 작성한 코드에 대해 절대적으로 확신하고 문제를 일으키지 않을 것임을 알고 있는 경우 주석을 사용하여 varargs 관련 경고를 끌 수 있습니다 . @SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
이 주석을 메서드에 추가하면 이전에 발생한 경고가 나타나지 않습니다. 제네릭과 함께 varargs를 사용할 때 발생할 수 있는 또 다른 문제는 힙 오염입니다. 힙 오염은 다음과 같은 상황에서 발생할 수 있습니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> polluteHeap() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = polluteHeap();
System.out.println(stringsWithHeapPollution.get(0));
}
}
콘솔 출력:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
간단히 말해서 힙 오염은 유형의 개체가 A
힙에 있어야 하지만 B
유형 안전과 관련된 오류로 인해 유형의 개체가 힙에 있는 경우입니다. 우리의 예에서 이것은 정확히 일어나는 일입니다. 먼저 원시 변수를 생성 하고 여기에 numbers
일반 컬렉션( )을 할당했습니다 . 그런 다음 컬렉션에 ArrayList<Number>
번호를 추가했습니다 .1
List<String> strings = numbers;
이 줄에서 컴파일러는 " Unchecked assignment... " 경고 를 발행하여 가능한 오류를 경고하려 했지만 무시했습니다. List<String>
type 의 제네릭 컬렉션을 가리키는 type 의 제네릭 변수로 끝납니다 ArrayList<Number>
. 분명히 이 상황은 문제를 일으킬 수 있습니다! 그리고 그렇게 됩니다. 새 변수를 사용하여 컬렉션에 문자열을 추가합니다. 이제 힙 오염이 발생했습니다. 숫자를 추가한 다음 매개변수화된 컬렉션에 문자열을 추가했습니다. 컴파일러가 우리에게 경고했지만 우리는 그 경고를 무시했습니다. 결과적으로 ClassCastException
프로그램이 실행되는 동안에만 얻을 수 있습니다. 그렇다면 이것이 varargs와 어떤 관련이 있습니까? 제네릭과 함께 varargs를 사용하면 쉽게 힙 오염이 발생할 수 있습니다. 다음은 간단한 예입니다.
import java.util.Arrays;
import java.util.List;
public class Main {
static void polluteHeap(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
polluteHeap(cars1, cars2);
}
}
무슨 일이야? 유형 삭제로 인해 가변 길이 인수
List<String>...stringsLists
목록의 배열이 됩니다 List[]
. 이 때문에 Object[] array
메서드의 첫 번째 줄에 있는 변수에 쉽게 할당할 수 있습니다. 목록에 있는 객체의 유형이 지워졌습니다! 그리고 이제 우리는 Object[]
자바의 모든 객체가 Object
! 처음에는 문자열 목록의 배열만 있습니다. 그러나 유형 삭제와 가변 인수 사용 덕분에 숫자 목록을 쉽게 추가할 수 있습니다. 결과적으로 서로 다른 유형의 객체를 혼합하여 힙을 오염시킵니다. ClassCastException
배열에서 문자열을 읽으려고 하면 결과가 또 달라집니다 . 콘솔 출력:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
간단한 메커니즘인 varargs를 사용하면 이러한 예기치 않은 결과가 발생할 수 있습니다. :) 이것으로 오늘 수업은 끝납니다. 몇 가지 과제를 해결하는 것을 잊지 마시고, 시간과 에너지가 있다면 추가 독서를 공부하십시오. " Effective Java "는 스스로 읽지 않습니다! :) 다음 시간까지!