你好!在今天的课程中,我们将继续学习泛型。碰巧这是一个很大的主题,但无处可去 - 这是该语言极其重要的部分:) 当您研究有关泛型的 Oracle 文档或阅读 Internet 上的指南时,您会遇到这些术语不可具体化类型和可具体化类型。“可具体化”是一个什么样的词?即使英语一切都很好,你也不太可能遇到它。我们来尝试翻译一下吧!
*谢谢谷歌,你帮了很多忙-_-*
可具体化类型是一种其信息在运行时完全可用的类型。在 Java 语言中,这些类型包括基元、原始类型和非泛型类型。相反,不可具体化类型是其信息在运行时被删除且不可用的类型。这些只是泛型 - List<String>、List<Integer>等。
顺便问一下,您还记得可变参数是什么吗?
如果您忘记了,这些是可变长度参数。当我们不知道到底有多少参数可以传递给我们的方法时,它们会派上用场。例如,如果我们有一个计算器类并且它有一个方法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")
}
);
}
阵列一切都很好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 的对象应该位于堆上,但由于类型安全错误,А
类型 1 的对象最终出现在堆上的情况。B
在我们的示例中,这就是发生的情况。首先,我们创建了一个 Raw 变量numbers
并为其分配了一个通用集合ArrayList<Number>
。之后我们在那里添加了号码1
。
List<String> strings = numbers;
在这一行中,编译器试图通过发出警告“ Unchecked assignment... ”来警告我们可能出现的错误,但我们忽略了它。结果,我们有一个类型为 的泛型变量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