안녕하세요! 오늘 수업에서는 계속해서 제네릭을 공부하겠습니다. 이것은 큰 주제이지만 갈 곳이 없습니다. 이것은 언어에서 매우 중요한 부분입니다 :) 제네릭에 대한 Oracle 문서를 연구하거나 인터넷에서 가이드를 읽을 때 다음 용어를 접하게 될 것입니다. 수정 불가능 유형 및 수정 가능 유형 . '재확인 가능'이란 어떤 단어인가요? 영어로 모든 것이 좋다고 하더라도, 영어를 접했을 가능성은 거의 없습니다. 번역해 봅시다!
*Google 감사합니다. 많은 도움이 되었습니다 -_-*
재구성 가능 유형은 런타임 시 정보를 완전히 사용할 수 있는 유형입니다. 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>(), // здесь все нормально
"Leonardo da Vinci",
"Vasco de Gama"
);
// а здесь мы получаем предупреждение
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
이 메서드는 목록 과 여러 개체를 addAll()
입력으로 사용 하고 이러한 모든 개체를 목록에 추가합니다. 메소드에서 우리는 메소드를 두 번 호출합니다 . 처음으로 두 개의 일반 줄을 추가합니다. 여기는 모든 것이 괜찮습니다. 두 번째로 두 개의 개체를 추가합니다 . 그리고 여기서 갑자기 경고가 나타납니다. List<E>
E
main()
addAll()
List
List
Pair<String, String>
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")
}
);
}
array 에서는 모든 것이 괜찮습니다 String
. 그러나 배열의 경우 Pair<String, String>
- 아니요. 사실 Pair<String, String>
이는 수정 불가능한 유형입니다. 컴파일하는 동안 매개변수 유형(<String, String>)에 대한 모든 정보가 삭제됩니다. 재구성 불가능한 유형에서 배열을 생성하는 것은 Java에서 허용되지 않습니다 . pair<String, String> 배열을 수동으로 생성하려고 하면 이를 확인할 수 있습니다.
public static void main(String[] args) {
// ошибка компиляции! 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> makeHeapPollution() {
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 = makeHeapPollution();
System.out.println(stringsWithHeapPollution.get(0));
}
}
콘솔 출력:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
간단히 말하면, 힙 오염은 유형 1의 객체가 힙에 있어야 하지만 유형 안전 오류로 인해 А
유형의 객체가 힙에 있게 되는 상황입니다. B
우리의 예에서는 이런 일이 발생합니다. 먼저 Raw 변수를 생성 numbers
하고 이를 일반 컬렉션에 할당했습니다 ArrayList<Number>
. 그 후에 우리는 거기에 숫자를 추가했습니다 1
.
List<String> strings = numbers;
이 줄에서 컴파일러는 " 검사되지 않은 할당... "이라는 경고를 발행하여 가능한 오류에 대해 경고하려고 했지만 우리는 이를 무시했습니다. 결과적으로 유형의 List<String>
일반 컬렉션을 가리키는 유형 의 일반 변수가 있습니다 ArrayList<Number>
. 이러한 상황은 분명히 문제를 일으킬 수 있습니다! 이것이 일어나는 일입니다. 새 변수를 사용하여 컬렉션에 문자열을 추가합니다. 힙이 오염되었습니다. 먼저 숫자를 추가한 다음 입력된 컬렉션에 문자열을 추가했습니다. 컴파일러는 우리에게 경고했지만 우리는 그 경고를 무시하고 ClassCastException
프로그램이 실행되는 동안에만 결과를 받았습니다. 그것 은 그것 과 무슨 관련 이 있나요 varargs
? 제네릭과 함께 사용하면 varargs
쉽게 힙 오염이 발생할 수 있습니다. 간단한 예는 다음과 같습니다.
import java.util.Arrays;
import java.util.List;
public class Main {
static void makeHeapPollution(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");
makeHeapPollution(cars1, cars2);
}
}
여기서 무슨 일이 일어나고 있는 걸까요? 유형 삭제로 인해 매개변수 시트(편의상 "목록" 대신 "시트"라고 부르겠습니다)는 다음과 같습니다.
List<String>...stringsLists
- 알 수 없는 유형의 시트 배열로 변환됩니다 List[]
(컴파일 결과 varargs가 일반 배열로 변환된다는 점을 잊지 마십시오). 이 때문에 메소드의 첫 번째 줄에서 변수에 쉽게 할당할 수 있습니다 Object[] array
. 유형이 시트에서 지워졌습니다! 이제 우리는 Object[]
무엇이든 추가할 수 있는 유형의 변수를 갖게 되었습니다. Java의 모든 객체는 Object
! 지금은 스트링 시트 배열만 있습니다. 그러나 유형을 사용 varargs
하고 삭제함으로써 우리는 쉽게 숫자 시트를 추가할 수 있습니다. 결과적으로 우리는 서로 다른 유형의 객체를 혼합하여 힙을 오염시킵니다. ClassCastException
배열에서 문자열을 읽으려고 할 때 결과는 동일한 예외가 됩니다 . 콘솔 출력:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
겉으로는 단순해 보이는 메커니즘을 사용함으로써 발생할 수 있는 예상치 못한 결과입니다. varargs
:) 그리고 오늘 강의는 이것으로 끝납니다. 몇 가지 문제를 해결하는 것을 잊지 마세요. 시간과 에너지가 남아 있다면 추가 문헌을 공부하세요. " Effective Java "는 자체적으로 읽혀지지 않습니다! :) 또 봐요!
GO TO FULL VERSION