The Java Tutorials have been written for JDK 8.Java教程是为JDK 8编写的。Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available.本页中描述的示例和实践没有利用后续版本中引入的改进,并且可能使用不再可用的技术。See Java Language Changes for a summary of updated language features in Java SE 9 and subsequent releases.有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言更改。
See JDK Release Notes for information about new features, enhancements, and removed or deprecated options for all JDK releases.有关所有JDK版本的新功能、增强功能以及已删除或不推荐的选项的信息,请参阅JDK发行说明。
Until now, all our examples have assumed an idealized world, where everyone is using the latest version of the Java programming language, which supports generics.到目前为止,我们所有的例子都假设了一个理想化的世界,每个人都在使用支持泛型的最新版本的Java编程语言。
Alas, in reality this isn't the case. 唉,事实并非如此。Millions of lines of code have been written in earlier versions of the language, and they won't all be converted overnight.数百万行代码已经用该语言的早期版本编写,它们不会在一夜之间全部转换。
Later, in the Converting Legacy Code to Use Generics section, we will tackle the problem of converting your old code to use generics. 稍后,在将遗留代码转换为使用泛型部分,我们将解决将旧代码转换为使用泛型的问题。In this section, we'll focus on a simpler problem: how can legacy code and generic code interoperate? 在本节中,我们将关注一个更简单的问题:遗留代码和泛型代码如何互操作?This question has two parts: using legacy code from within generic code and using generic code within legacy code.这个问题有两个部分:在通用代码中使用遗留代码和在遗留代码中使用通用代码。
How can you use old code, while still enjoying the benefits of generics in your own code?如何使用旧代码,同时仍能在自己的代码中享受泛型的好处?
As an example, assume you want to use the package 例如,假设您想使用com.Example.widgets
. com.Example.widgets
包。The folks at Example.com market a system for inventory control, highlights of which are shown below:Example.com市场的分叉系列是一个库存控制系统,其亮点如下所示:
package com.Example.widgets; public interface Part {...} public class Inventory { /** * Adds a new Assembly to the inventory database. * The assembly is given the name name, and * consists of a set parts specified by parts. * All elements of the collection parts * must support the Part interface. **/ public static void addAssembly(String name, Collection parts) {...} public static Assembly getAssembly(String name) {...} } public interface Assembly { // Returns a collection of Parts Collection getParts(); }
Now, you'd like to add new code that uses the API above. 现在,您想添加使用上述API的新代码。It would be nice to ensure that you always called 最好确保始终使用正确的参数调用addAssembly()
with the proper arguments - that is, that the collection you pass in is indeed a Collection
of Part
. addAssembly()
,也就是说,传入的集合确实是Part
的Collection
。Of course, generics are tailor made for this:当然,仿制药是为这一点量身定制的:
package com.mycompany.inventory; import com.Example.widgets.*; public class Blade implements Part { ... } public class Guillotine implements Part { } public class Main { public static void main(String[] args) { Collection<Part> c = new ArrayList<Part>(); c.add(new Guillotine()) ; c.add(new Blade()); Inventory.addAssembly("thingee", c); Collection<Part> k = Inventory.getAssembly("thingee").getParts(); } }
When we call 当我们调用addAssembly
, it expects the second parameter to be of type Collection
. addAssembly
时,它希望第二个参数的类型为Collection
。The actual argument is of type 实际参数的类型为Collection<Part>
. Collection<Part>
。This works, but why? 这是可行的,但为什么呢?After all, most collections don't contain 毕竟,大多数集合不包含Part对象,因此一般来说,编译器无法知道类型集合所指的集合类型。Part
objects, and so in general, the compiler has no way of knowing what kind of collection the type Collection
refers to.
In proper generic code, 在正确的泛型代码中,Collection
would always be accompanied by a type parameter. Collection
总是伴随着一个类型参数。When a generic type like 如果在没有类型参数的情况下使用像Collection
is used without a type parameter, it's called a raw type.Collection
这样的泛型类型,则称之为原始类型。
Most people's first instinct is that 大多数人的第一反应是,Collection
really means Collection<Object>
. Collection
实际上意味着Collection<Object>
。However, as we saw earlier, it isn't safe to pass a 然而,正如我们前面看到的,在需要Collection<Part>
in a place where a Collection<Object>
is required. Collection<Part>
的地方传递Collection<Object>
是不安全的。It's more accurate to say that the type 更准确地说,类型Collection
denotes a collection of some unknown type, just like Collection<?>
.Collection
表示某种未知类型的集合,就像Collection<?>
。
But wait, that can't be right either! 但是等等,那也不可能是对的!Consider the call to 考虑调用getParts()
, which returns a Collection
. getParts()
,它返回一个Collection
。This is then assigned to 然后将其分配给k
, which is a Collection<Part>
. k
,这是一个Collection<Part>
。If the result of the call is a 如果调用的结果是Collection<?>
, the assignment would be an error.Collection<?>
,这项任务将是一个错误。
In reality, the assignment is legal, but it generates an unchecked warning. 事实上,这种分配是合法的,但它会产生未经检查的警告。The warning is needed, because the fact is that the compiler can't guarantee its correctness. 警告是必要的,因为编译器不能保证它的正确性。We have no way of checking the legacy code in 我们无法检查getAssembly()
to ensure that indeed the collection being returned is a collection of Part
s. getAssembly()
中的遗留代码,以确保返回的集合确实是Part
集合。The type used in the code is 代码中使用的类型是Collection
, and one could legally insert all kinds of objects into such a collection.Collection
,可以合法地将各种对象插入这样的集合中。
So, shouldn't this be an error? 那么,这不是一个错误吗?Theoretically speaking, yes; but practically speaking, if generic code is going to call legacy code, this has to be allowed. 从理论上讲,是的;但实际上,如果泛型代码要调用遗留代码,这是必须允许的。It's up to you, the programmer, to satisfy yourself that in this case, the assignment is safe because the contract of 由程序员自己决定,在这种情况下,赋值是安全的,因为getAssembly()
says it returns a collection of Part
s, even though the type signature doesn't show this.getAssembly()
的契约表示它返回一个零件集合,即使类型签名没有显示这一点。
So raw types are very much like wildcard types, but they are not typechecked as stringently. 因此,原始类型非常类似于通配符类型,但它们没有经过严格的类型检查。This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code.这是一个经过深思熟虑的设计决策,允许泛型与预先存在的遗留代码进行互操作。
Calling legacy code from generic code is inherently dangerous; once you mix generic code with non-generic legacy code, all the safety guarantees that the generic type system usually provides are void. 从泛型代码调用遗留代码本身就是危险的;一旦将泛型代码与非泛型遗留代码混合,泛型类型系统通常提供的所有安全保证都是无效的。However, you are still better off than you were without using generics at all. 然而,与完全不使用泛型相比,您仍然过得更好。At least you know the code on your end is consistent.至少你知道你这边的代码是一致的。
At the moment there's a lot more non-generic code out there then there is generic code, and there will inevitably be situations where they have to mix.目前,非通用代码比通用代码多得多,而且不可避免地会出现它们必须混合的情况。
If you find that you must intermix legacy and generic code, pay close attention to the unchecked warnings. 如果您发现必须混合使用遗留代码和通用代码,请密切注意未检查的警告。Think carefully how you can justify the safety of the code that gives rise to the warning.仔细考虑如何证明引起警告的代码的安全性。
What happens if you still made a mistake, and the code that caused a warning is indeed not type safe? 如果您仍然犯了错误,并且导致警告的代码确实不是类型安全的,会发生什么?Let's take a look at such a situation. 让我们来看看这种情况。In the process, we'll get some insight into the workings of the compiler.在这个过程中,我们将对编译器的工作原理有一些了解。
public String loophole(Integer x) { List<String> ys = new LinkedList<String>(); List xs = ys; xs.add(x); // Compile-time unchecked warning return ys.iterator().next(); }
Here, we've aliased a list of strings and a plain old list. 这里,我们给一个字符串列表和一个普通的旧列表加了别名。We insert an 我们在列表中插入一个Integer
into the list, and attempt to extract a String
. Integer
,并尝试提取一个String
。This is clearly wrong. 这显然是错误的。If we ignore the warning and try to execute this code, it will fail exactly at the point where we try to use the wrong type. 如果我们忽略警告并尝试执行此代码,它将在尝试使用错误类型时完全失败。At run time, this code behaves like:在运行时,此代码的行为如下:
public String loophole(Integer x) { List ys = new LinkedList; List xs = ys; xs.add(x); return(String) ys.iterator().next(); // run time error }
When we extract an element from the list, and attempt to treat it as a string by casting it to 当我们从列表中提取一个元素,并试图通过将其转换为字符串来将其视为String
, we will get a ClassCastException
. String
时,我们将得到一个ClassCastException
。The exact same thing happens with the generic version of 同样的情况也发生在loophole()
.loophole()
的通用版本中。
The reason for this is, that generics are implemented by the Java compiler as a front-end conversion called erasure. 原因是,泛型由Java编译器作为前端转换实现,称为擦除。You can (almost) think of it as a source-to-source translation, whereby the generic version of 您可以(几乎)将其视为源代码到源代码的转换,从而将loophole()
is converted to the non-generic version.loophole()
的通用版本转换为非通用版本。
As a result, the type safety and integrity of the Java virtual machine are never at risk, even in the presence of unchecked warnings.因此,即使存在未经检查的警告,Java虚拟机的类型安全性和完整性也不会受到威胁。
Basically, erasure gets rid of (or erases) all generic type information. 基本上,擦除会删除(或擦除)所有泛型类型信息。All the type information betweeen angle brackets is thrown out, so, for example, a parameterized type like 尖括号之间的所有类型信息都被抛出,因此,例如,像List<String>
is converted into List
. List<String>
这样的参数化类型被转换为List
。All remaining uses of type variables are replaced by the upper bound of the type variable (usually 类型变量的所有剩余用法都被类型变量的上限(通常是Object
). Object
)替换。And, whenever the resulting code isn't type-correct, a cast to the appropriate type is inserted, as in the last line of 而且,每当生成的代码的类型不正确时,就会插入一个适当类型的转换,就像在最后一行loophole
.loophole
中一样。
The full details of erasure are beyond the scope of this tutorial, but the simple description we just gave isn't far from the truth. 擦除的全部细节超出了本教程的范围,但我们刚才给出的简单描述与事实并不遥远。It's good to know a bit about this, especially if you want to do more sophisticated things like converting existing APIs to use generics (see the Converting Legacy Code to Use Generics section), or just want to understand why things are the way they are. 了解一些这方面的知识是很好的,尤其是如果您想做更复杂的事情,比如将现有API转换为使用泛型(请参阅将遗留代码转换为使用泛型部分),或者只是想了解为什么事情是这样的。
Now let's consider the inverse case. 现在让我们考虑逆情形。Imagine that Example.com chose to convert their API to use generics, but that some of their clients haven't yet. 想象一下Example.com选择将其API转换为使用泛型,但他们的一些客户机尚未这样做。So now the code looks like:现在代码看起来像:
package com.Example.widgets; public interface Part { ... } public class Inventory { /** * Adds a new Assembly to the inventory database. * The assembly is given the name name, and * consists of a set parts specified by parts. * All elements of the collection parts * must support the Part interface. **/ public static void addAssembly(String name, Collection<Part> parts) {...} public static Assembly getAssembly(String name) {...} } public interface Assembly { // Returns a collection of Parts Collection<Part> getParts(); }
and the client code looks like:客户端代码如下所示:
package com.mycompany.inventory; import com.Example.widgets.*; public class Blade implements Part { ... } public class Guillotine implements Part { } public class Main { public static void main(String[] args) { Collection c = new ArrayList(); c.add(new Guillotine()) ; c.add(new Blade()); // 1: unchecked warning Inventory.addAssembly("thingee", c); Collection k = Inventory.getAssembly("thingee").getParts(); } }
The client code was written before generics were introduced, but it uses the package 客户机代码是在引入泛型之前编写的,但它使用com.Example.widgets
and the collection library, both of which are using generic types. com.Example.widgets
包和集合库,两者都使用泛型类型。All the uses of generic type declarations in the client code are raw types.客户机代码中泛型类型声明的所有用法都是原始类型。
Line 1 generates an unchecked warning, because a raw 第1行生成了一个未检查的警告,因为在预期的Collection
is being passed in where a Collection
of Part
s is expected, and the compiler cannot ensure that the raw Collection
really is a Collection
of Part
s.Part
的Collection
中传递了一个原始Collection
,而编译器无法确保原始Collection
确实是一个Part
的Collection
。
As an alternative, you can compile the client code using the source 1.4 flag, ensuring that no warnings are generated. 作为替代方案,您可以使用source 1.4标志编译客户机代码,确保不会生成任何警告。However, in that case you won't be able to use any of the new language features introduced in JDK 5.0.但是,在这种情况下,您将无法使用JDK 5.0中引入的任何新语言功能。