1.介绍
我们可能希望使用数组作为支持的类或函数的一部分泛型。由于Java处理泛型的方式,这可能很困难。
在本教程中,我们将理解在数组中使用泛型的挑战。然后,我们将创建一个泛型数组的示例。
我们还将看看Java API在哪里解决了类似的问题。
2.使用泛型数组时的注意事项
数组和泛型之间的一个重要区别是它们如何执行类型检查。具体来说,数组在运行时存储和检查类型信息。但是,泛型在编译时检查类型错误在运行时不要有类型信息。
Java的语法建议我们可以创建一个新的泛型数组:
T[]元素= new T[size];
但是,如果我们尝试这样做,就会得到一个编译错误。
为了理解其中的原因,让我们考虑以下几点:
public T[] getArray(int size) {T[] genericArray = new T[size];//假设允许返回genericArray;}
作为非绑定泛型类型T解析为对象,我们在运行时的方法是:
public Object[] getArray(int size) {Object[] genericArray = new Object[size];返回genericArray;}
然后,如果我们调用方法并将结果存储在字符串数组:
String[] myArray = getArray(5);
代码将正常编译,但在运行时使用ClassCastException。这是因为我们刚刚分配了an对象[]到一个String []参考。具体来说,编译器的隐式强制转换将无法转换对象[]到我们需要的类型String []。
虽然不能直接初始化泛型数组,但如果调用代码提供了精确的信息类型,仍然可以实现等价的操作。
3.创建通用数组
在我们的例子中,让我们考虑一个有界堆栈数据结构MyStack,容量固定在一定的大小。此外,由于我们希望堆栈可以用于任何类型,因此一个合理的实现选择是泛型数组。
首先,让我们创建一个字段来存储堆栈的元素,这是一个泛型数组类型E:
私人E[]元素;
其次,让我们添加一个构造函数:
public MyStack(Class clazz, int capacity) {elements = (E[]) Array. / /创建数组newInstance (clazz、容量);}
注意我们使用java.lang.reflect.Array # newInstance初始化我们的泛型数组,这需要两个参数。第一个参数指定新数组中对象的类型。第二个参数指定要为数组创建多少空间。由于…的结果数组# newInstance的类型是对象,我们需要将它转换为E []创建我们的泛型数组。
我们还应该注意命名类型参数的约定clazz而不是类,这在Java中是一个保留字。
4.考虑ArrayList
4.1。使用ArrayListin Place of an Array
使用泛型通常更容易ArrayList代替泛型数组。让我们看看如何改变MyStack使用一个ArrayList。
首先,让我们创建一个字段来存储元素:
私人< E >元素列表;
其次,在堆栈构造函数中,可以初始化ArrayList具有初始能力的:
ArrayList<>(容量);
它使我们的类更简单,因为我们不需要使用反射。另外,在创建堆栈时也不需要传入类字面量。最后,我们可以设置an的初始容量ArrayList,我们可以得到与数组相同的好处。
因此,我们只需要在很少的情况下,或者在与需要数组的外部库进行接口时,构造泛型数组。
4.2。ArrayList实现
有趣的是,ArrayList它本身是使用泛型数组实现的。让我们看里面ArrayList来看看。
首先,让我们看看列表元素字段:
瞬态对象[]elementData;
请注意ArrayList使用对象作为元素类型。因为泛型类型直到运行时才知道,对象用作任何类型的超类。
值得注意的是,几乎所有的操作ArrayList可以使用这个泛型数组,因为它们不需要向外部世界提供强类型数组,除了一个方法-toArray!
5.从集合构建数组
5.1。LinkedList的例子
让我们看看如何在Java Collections API中使用泛型数组,我们将在其中从集合构建一个新数组。
首先,让我们创建一个newLinkedList带有类型参数字符串并添加项目:
List items = new LinkedList();物品。一个dd("first item"); items.add("second item");
其次,让我们为刚刚添加的条目构建一个数组:
String[] itemsAsArray = items。toArray(新的字符串[0]);
要构建数组,则列表。toArray方法需要输入数组。它纯粹使用这个数组来获取类型信息,以创建正确类型的返回数组。
在上面的例子中,我们使用了新的字符串[0]作为我们的输入数组来构建结果字符串数组中。
5.2。LinkedList.toArray实现
让我们来看看里面LinkedList.toArray,查看它是如何在Java JDK中实现的。
首先,让我们看一下方法签名:
public T[] toArray(T[] a)
其次,让我们看看如何在需要时创建一个新数组:
a = (T[])java.lang.reflect.Array.newInstance(a. getclass ().getComponentType(), size);
注意它是如何利用数组# newInstance构建一个新的数组,就像我们前面的堆栈示例一样。另外,请注意how参数一个用于提供类型到# newInstance数组。最后,结果来自数组# newInstance是演员T []创建泛型数组。
6.从流创建数组
的Java流API允许我们从流中的项创建数组。要生成正确类型的数组,需要注意两个陷阱。
6.1。使用toArray
我们可以很容易地从Java 8转换项目流成一个数组:
对象[]字符串=流。(“A”、“AAA”、“B”,“艺术展”、“C”).filter(字符串- > string.startsWith (A)) .toArray ();为了(字符串)。containsExactly(“A”、“AAA级”、“艺术展”);
然而,我们应该注意到,基本的toArray函数为我们提供一个数组对象,而不是一组字符串:
为了(字符串).isNotInstanceOf (String [] . class);
正如我们前面所看到的,每个数组的精确类型是不同的。作为a流是泛型,没有办法的库吗在运行时推断类型。
6.2。使用toArray重载获取类型化数组
公共集合类方法使用反射构造特定类型的数组,而Java Streams库使用函数方法。对象时,可以传入一个lambda或方法引用,该引用将创建具有正确大小和类型的数组流准备填充它:
String[] String =流。(“A”、“AAA”、“B”,“艺术展”、“C”).filter(字符串- > string.startsWith (A)) .toArray (string[]::新);为了(字符串)。containsExactly(“A”、“AAA级”、“艺术展”);为了(字符串).isInstanceOf (String [] . class);
我们传递的方法是IntFunction它接受一个整数作为输入,并返回该大小的新数组。这就是构造函数String []有,所以我们可以用方法引用- - - - - -String[]::新。
6.3。具有自己类型参数的泛型
现在让我们假设我们想要将流中的值转换为一个对象,该对象本身具有类型参数列表或可选。也许我们有一个需要调用的API可选<字符串> []作为它的输入。
声明这类数组是有效的:
可选[] String = null;
我们也可以很容易地拿我们的流<字符串>然后转换成流<可选<字符串> >通过使用地图方法:
Stream<可选> Stream =流。(“A”、“AAA”、“B”,“艺术展”、“C”).filter(字符串- > string.startsWith (A)) . map(可选::的);
然而,如果试图构造数组,又会得到编译器错误:
[] String = new String>[1]; //编译错误
幸运的是,这个示例和我们前面的示例有一个区别。在哪里String []不是一个子类吗对象[],可选的[]实际上是相同的运行时类型可选<字符串> []。换句话说,这是一个可以通过类型转换来解决的问题:
Stream<可选> Stream =流。(“A”、“AAA”、“B”,“艺术展”、“C”).filter(字符串- > string.startsWith (A)) . map(可选::的);可选[] String = stream .toArray(可选[]::new);
此代码编译并工作,但给我们一个检查作业警告。我们需要加aSuppressWarnings我们的方法来解决这个问题:
@SuppressWarnings (“unchecked”)
6.4。使用Helper函数
如果我们想避免添加SuppressWarnings对于代码中的多个位置,并希望记录从原始类型创建泛型数组的方式,我们可以编写一个帮助函数:
@SuppressWarnings("unchecked") static IntFunction genericArray(IntFunction arrayCreator) {return size -> (R[]) arrayCreator.apply(size);}
这个函数将生成原始类型数组的函数转换为承诺生成我们所需要的特定类型数组的函数:
可选[] String = Stream。(“A”、“AAA”、“B”,“艺术展”、“C”).filter(字符串- > string.startsWith (A)) . map()的可选:.toArray (genericArray(可选[]::新));
此处不需要抑制未选中的赋值警告。
但是,我们应该注意,可以调用此函数来执行更高类型的类型转换。例如,如果我们的流包含类型对象<字符串>列表,我们可能会错误地调用genericArray生成数组ArrayList <字符串>:
ArrayList[] lists = Stream.of(singletonList("A")) .toArray(genericArray(List[]::new));
这将编译,但将抛出ClassCastException作为ArrayList []不是一个子类吗[]列表。但是编译器会为此生成一个未检查的赋值警告,所以很容易发现。
7.结论
在本文中,我们首先研究了数组和泛型之间的区别,然后是创建泛型数组的示例。然后,我们演示了如何使用ArrayList可能比使用泛型数组更容易。我们还研究了Collections API中泛型数组的使用。
最后,我们了解了如何从Streams API生成数组,以及如何处理创建使用类型参数的类型数组。
与往常一样,示例代码是可用的在GitHub。