1. Introduction
1.绪论
We may wish to use arrays as part of classes or functions that support generics, but due to the way Java handles generics, this can be difficult.
我们可能希望将数组作为支持泛型的类或函数的一部分,但由于Java处理泛型的方式,这可能是困难的。
In this tutorial, we’ll discuss the challenges of using generics with arrays. Then we’ll create an example of a generic array.
在本教程中,我们将讨论在数组中使用泛型的挑战。然后我们将创建一个泛型数组的例子。
Finally, we’ll see how the Java API has solved a similar problem.
最后,我们将看到Java API是如何解决类似问题的。
2. Considerations When Using Generic Arrays
2.使用通用数组时的考虑因素
An important difference between arrays and generics is how they enforce type checking. Specifically, arrays store and check type information at runtime. Generics, however, check for type errors at compile-time and don’t have type information at runtime.
数组和泛型之间的一个重要区别是它们如何执行类型检查。具体而言,数组在运行时存储并检查类型信息。然而,泛型在编译时检查类型错误,在运行时没有类型信息。
Java’s syntax suggests we might be able to create a new generic array:
Java的语法表明我们也许可以创建一个新的通用数组。
T[] elements = new T[size];
But if we attempted this, we’d get a compile error.
但如果我们尝试这样做,我们会得到一个编译错误。
To understand why, let’s consider the following:
为了了解原因,让我们考虑以下几点。
public <T> T[] getArray(int size) {
T[] genericArray = new T[size]; // suppose this is allowed
return genericArray;
}
As an unbound generic type T resolves to Object, our method at runtime will be:
由于非绑定的通用类型T可解析为Object,我们的方法在运行时将是。
public Object[] getArray(int size) {
Object[] genericArray = new Object[size];
return genericArray;
}
If we call our method and store the result in a String array:
如果我们调用我们的方法并将结果存储在一个String数组中。
String[] myArray = getArray(5);
The code will compile fine, but fail at runtime with a ClassCastException. This is because we just assigned an Object[] to a String[] reference. Specifically, an implicit cast by the compiler will fail to convert Object[] to our required type String[].
这段代码编译正常,但在运行时却出现了ClassCastException。这是因为我们刚刚将一个Object[]分配给一个String[]引用。具体来说,编译器的隐式转换将无法将Object[]转换为我们需要的类型String[]。
Although we can’t initialize generic arrays directly, it’s still possible to achieve the equivalent operation if the precise type of information is provided by the calling code.
虽然我们不能直接初始化泛型数组,但如果调用代码提供了精确的信息类型,还是有可能实现同等的操作。
3. Creating a Generic Array
3.创建一个通用阵列
For our example, let’s consider a bounded stack data structure, MyStack, where the capacity is fixed to a certain size. As we’d like the stack to work with any type, a reasonable implementation choice would be a generic array.
对于我们的例子,让我们考虑一个有界的堆栈数据结构,MyStack,其中容量被固定为某个大小。由于我们希望堆栈能与任何类型的数据一起工作,合理的实现选择是一个通用数组。
First, we’ll create a field to store the elements of our stack, which is a generic array of type E:
首先,我们将创建一个字段来存储我们的堆栈的元素,这是一个类型为E的通用数组。
private E[] elements;
Then we’ll add a constructor:
然后我们将添加一个构造函数。
public MyStack(Class<E> clazz, int capacity) {
elements = (E[]) Array.newInstance(clazz, capacity);
}
Notice how we use java.lang.reflect.Array#newInstance to initialize our generic array, which requires two parameters. The first parameter specifies the type of object inside the new array. The second parameter specifies how much space to create for the array. As the result of Array#newInstance is of type Object, we need to cast it to E[] to create our generic array.
请注意我们是如何使用 java.lang.reflect.Array#newInstance来初始化我们的通用数组的,它需要两个参数。第一个参数指定了新数组内对象的类型。第二个参数指定了要为数组创建多少空间。由于Array#newInstance的结果是Object类型,我们需要将其转换为E[]来创建我们的通用数组。
We should also note the convention of naming a type parameter clazz, rather than class, which is a reserved word in Java.
我们还应该注意到将类型参数命名为clazz,而不是class,的惯例,后者在Java中是一个保留字。
4. Considering ArrayList
4.考虑到ArrayList
4.1. Using ArrayList in Place of an Array
4.1.使用ArrayList来代替数组
It’s often easier to use a generic ArrayList in place of a generic array. Let’s see how we can change MyStack to use an ArrayList.
使用一个通用的ArrayList来代替一个通用的数组通常会更容易。让我们看看如何改变MyStack以使用ArrayList。
First, we’ll create a field to store our elements:
首先,我们将创建一个字段来存储我们的元素。
private List<E> elements;
Then, in our stack constructor, we can initialize the ArrayList with an initial capacity:
然后,在我们的堆栈构造函数中,我们可以用一个初始容量来初始化ArrayList。
elements = new ArrayList<>(capacity);
It makes our class simpler, as we don’t have to use reflection. Also, we aren’t required to pass in a class literal when creating our stack. As we can set the initial capacity of an ArrayList, we can get the same benefits as an array.
它使我们的类更简单,因为我们不必使用反射。另外,我们在创建堆栈时不需要传递类的字面意义。由于我们可以设置ArrayList的初始容量,我们可以获得与数组相同的好处。
Therefore, we only need to construct arrays of generics in rare situations or when we’re interfacing with some external library that requires an array.
因此,我们只需要在极少数情况下或者在与一些需要数组的外部库对接时,才需要构建泛型的数组。
4.2. ArrayList Implementation
4.2.ArrayList实现
Interestingly, ArrayList itself is implemented using generic arrays. Let’s peek inside ArrayList to see how.
有趣的是,ArrayList本身是使用泛型数组实现的。让我们窥探一下ArrayList的内部,看看是如何实现的。
First, let’s see the list elements field:
首先,让我们看看列表元素领域。
transient Object[] elementData;
Notice ArrayList uses Object as the element type. As our generic type isn’t known until runtime, Object is used as the superclass of any type.
注意ArrayList使用Object作为元素类型。由于我们的通用类型在运行时才会知道,Object被用作任何类型的超类。
It’s worth noting that nearly all the operations in ArrayList can use this generic array, as they don’t need to provide a strongly typed array to the outside world (except for one method, toArray).
值得注意的是,ArrayList中几乎所有的操作都可以使用这个通用数组,因为它们不需要向外界提供一个强类型的数组(除了一个方法,toArray)。。
5. Building an Array From a Collection
5.从一个集合建立一个数组
5.1. LinkedList Example
5.1.链接表实例
Let’s look at using generic arrays in the Java Collections API, where we’ll build a new array from a collection.
让我们看看在Java集合API中使用通用数组,我们将从一个集合中建立一个新的数组。
First, we’ll create a new LinkedList with a type argument String, and add items to it:
首先,我们将创建一个新的LinkedList,类型参数为String,,并向其中添加项目。
List<String> items = new LinkedList();
items.add("first item");
items.add("second item");
Then we’ll build an array of the items we just added:
然后我们将建立一个我们刚刚添加的项目的数组。
String[] itemsAsArray = items.toArray(new String[0]);
To build our array, the List.toArray method requires an input array. It uses this array purely to get the type information to create a return array of the right type.
为了建立我们的数组,List.toArray方法需要一个输入数组。它使用这个数组纯粹是为了获得类型信息,以创建一个正确类型的返回数组。
In our example above, we used new String[0] as our input array to build the resulting String array.
在我们上面的例子中,我们使用new String[0]作为我们的输入数组来构建结果String数组。
5.2. LinkedList.toArray Implementation
5.2.LinkedList.toArray实现
Let’s take a peek inside LinkedList.toArray to see how it’s implemented in the Java JDK.
让我们来看看LinkedList.toArray内部,看看它在Java JDK中是如何实现的。
First, we’ll look at the method signature:
首先,我们要看一下方法的签名。
public <T> T[] toArray(T[] a)
Then we’ll see how a new array is created when required:
然后我们将看到在需要时如何创建一个新的数组。
a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
Notice how it makes use of Array#newInstance to build a new array, like in our previous stack example. We can also see that parameter a is used to provide a type to Array#newInstance. Finally, the result from Array#newInstance is cast to T[] to create a generic array.
注意它是如何利用Array#newInstance来建立一个新的数组,就像我们之前的堆栈例子一样。我们还可以看到,参数a被用来为Array#newInstance提供一个类型。最后,来自Array#newInstance的结果被转换为T[]来创建一个通用数组。
6. Creating Arrays From Streams
6.从流中创建数组
The Java Streams API allows us to create arrays from the items in the stream. There are a couple of pitfalls to watch out for to ensure we produce an array of the correct type.
Java Streams API允许我们从流中的项目创建数组。有几个需要注意的陷阱,以确保我们产生一个正确类型的数组。
6.1. Using toArray
6.1.使用toArray
We can easily convert the items from a Java 8 Stream into an array:
我们可以很容易地将Java 8 Stream中的项目转换成一个数组。
Object[] strings = Stream.of("A", "AAA", "B", "AAB", "C")
.filter(string -> string.startsWith("A"))
.toArray();
assertThat(strings).containsExactly("A", "AAA", "AAB");
We should note, however, that the basic toArray function provides us with an array of Object, rather than an array of String:
然而,我们应该注意,基本的toArray函数为我们提供了一个Object数组,而不是String数组。
assertThat(strings).isNotInstanceOf(String[].class);
As we saw earlier, the precise type of each array is different. As the type in a Stream is generics, there’s no way for the library to infer the type at runtime.
正如我们之前看到的,每个数组的精确类型是不同的。由于Stream中的类型是泛型的,库没有办法在运行时推断类型。
6.2. Using the toArray Overload to Get a Typed Array
6.2.使用toArray重载来获取一个类型的数组
Where the common collection class methods use reflection to construct an array of a specific type, the Java Streams library uses a functional approach. We can pass in a lambda, or method reference, which creates an array of the correct size and type when the Stream is ready to populate it:
普通的集合类方法使用反射来构建特定类型的数组,而Java Streams库使用的是函数式方法。我们可以传入一个lambda或方法引用,当Stream准备好填充它时,它会创建一个大小和类型都正确的数组。
String[] strings = Stream.of("A", "AAA", "B", "AAB", "C")
.filter(string -> string.startsWith("A"))
.toArray(String[]::new);
assertThat(strings).containsExactly("A", "AAA", "AAB");
assertThat(strings).isInstanceOf(String[].class);
The method we pass is an IntFunction, which takes an integer as input and returns a new array of that size. This is exactly what the constructor of String[] does, so we can use the method reference String[]::new.
我们传递的方法是一个IntFunction,,它接收一个整数作为输入并返回一个该大小的新数组。这正是String[]的构造函数所做的,所以我们可以使用方法参考String[]::new。
6.3. Generics With Their Own Type Parameter
6.3.拥有自己的类型参数的泛型函数
Now let’s imagine we want to convert the values in our stream into an object which itself has a type parameter, say List or Optional. Perhaps we have an API we want to call that takes Optional<String>[] as its input.
现在让我们想象一下,我们想把流中的值转换成一个对象,这个对象本身有一个类型参数,比如List或Optional。也许我们有一个想要调用的API,它需要Optional<String>[]作为其输入。
It’s valid to declare this sort of array:
声明这种数组是有效的。
Optional<String>[] strings = null;
We can also easily take our Stream<String> and convert it to Stream<Optional<String>> by using the map method:
我们也可以通过使用map方法,轻松地将我们的Stream<String>转换为Stream<Optional<String>。
Stream<Optional<String>> stream = Stream.of("A", "AAA", "B", "AAB", "C")
.filter(string -> string.startsWith("A"))
.map(Optional::of);
However, we’d again get a compiler error if we tried to construct our array:
然而,如果我们试图构造我们的数组,我们又会得到一个编译器错误。
// compiler error
Optional<String>[] strings = new Optional<String>[1];
Luckily, there’s a difference between this example and our previous examples. Where String[] isn’t a subclass of Object[], Optional[] is actually an identical runtime type to Optional<String>[]. In other words, this is a problem we can solve by type casting:
幸运的是,这个例子和我们之前的例子有一个区别。String[]不是Object[]的子类,Optional[]实际上是与Optional<String> []相同的运行时类型。换句话说,这是一个我们可以通过类型转换来解决的问题。
Stream<Optional<String>> stream = Stream.of("A", "AAA", "B", "AAB", "C")
.filter(string -> string.startsWith("A"))
.map(Optional::of);
Optional<String>[] strings = stream
.toArray(Optional[]::new);
This code compiles and works, but gives us an unchecked assignment warning. We need to add a SuppressWarnings to our method to fix this:
这段代码可以编译并工作,但给了我们一个unchecked assignment警告。我们需要给我们的方法添加一个SuppressWarnings来解决这个问题。
@SuppressWarnings("unchecked")
6.4. Using a Helper Function
6.4.使用一个辅助函数
If we want to avoid adding the SuppressWarnings to multiple places in our code, and wish to document the way our generic array is created from the raw type, we can write a helper function:
如果我们想避免在代码中的多个地方添加SuppressWarnings,并希望记录我们的通用数组从原始类型创建的方式,我们可以写一个辅助函数。
@SuppressWarnings("unchecked")
static <T, R extends T> IntFunction<R[]> genericArray(IntFunction<T[]> arrayCreator) {
return size -> (R[]) arrayCreator.apply(size);
}
This function converts the function to make an array of the raw type into a function that promises to make an array of the specific type we need:
这个函数将制作原始类型数组的函数转换为承诺制作我们需要的特定类型数组的函数。
Optional<String>[] strings = Stream.of("A", "AAA", "B", "AAB", "C")
.filter(string -> string.startsWith("A"))
.map(Optional::of)
.toArray(genericArray(Optional[]::new));
The unchecked assignment warning doesn’t need to be suppressed here.
这里不需要压制未勾选的分配警告。
We should note, however, that this function can be called to perform type casts to higher types. For example, if our stream contained objects of type List<String>, we might incorrectly call genericArray to produce an array of ArrayList<String>:
然而,我们应该注意,这个函数可以被调用来执行类型转换到更高类型。例如,如果我们的流包含List<String>类型的对象,我们可能会错误地调用genericArray来产生一个ArrayList<String>的阵列。
ArrayList<String>[] lists = Stream.of(singletonList("A"))
.toArray(genericArray(List[]::new));
This would compile, but would throw a ClassCastException, as ArrayList[] isn’t a subclass of List[]. The compiler produces an unchecked assignment warning for this though, so it’s easy to spot.
这将会被编译,但会抛出一个ClassCastException,因为ArrayList[]不是List[]的子类。编译器对此产生一个未检查的赋值警告,所以很容易发现。
7. Conclusion
7.结语
In this article, we examined the differences between arrays and generics. Then we looked at an example of creating a generic array, demonstrating how using an ArrayList may be easier than using a generic array. We also discussed the use of a generic array in the Collections API.
在这篇文章中,我们研究了数组和泛型之间的区别。然后我们看了一个创建泛型数组的例子,证明了使用ArrayList可能比使用泛型数组更容易。我们还讨论了在集合API中使用泛型数组的问题。
Finally, we learned how to produce arrays from the Streams API, and how to handle creating arrays of types that use a type parameter.
最后,我们学习了如何从Streams API产生数组,以及如何处理创建使用类型参数的数组。
As always, the example code is available over on GitHub.
一如既往,示例代码可在GitHub上获得,。