泛型学习

oracle java 官方教程:https://docs.oracle.com/javase/tutorial/extra/generics/index.html

概述

Introduced in J2SE 5.0, this long-awaited enhancement to the type system allows a type or method to operate on objects of various types while providing compile-time type safety. It adds compile-time type safety to the Collections Framework and eliminates the drudgery of casting.

泛型是在J2SE5.0引入的,这个期待已久的类型体系增强允许一个类型或者方法对各种类型的对象进行操作同时提供了编译时的类型安全。它为集合框架提供了编译时的类型安全并且消除了繁琐的类型转换。

一、简介

JDK 5.0 introduces several new extensions to the Java programming language. One of these is the introduction of generics
This trail is an introduction to generics. You may be familiar with similar constructs from other languages, most notably C++ templates. If so, you'll see that there are both similarities and important differences. If you are unfamiliar with look-a-alike constructs from elsewhere, all the better; you can start fresh, without having to unlearn any misconceptions.
Generics allow you to abstract over types. The most common examples are container types, such as those in the Collections hierarchy.
Here is a typical usage of that sort:

JDK5引入了几个java的新拓展。其中一个就是泛型。
这里就是对于泛型的介绍。你可能熟悉其他语言的类似构造,尤其是C++ template。如果确实如此,你将会发现它们之间的相似之处和重要的差异。如果你没有在其他地方知道一些看起来相似的构造,那就更好了;你可以重新开始,不用去分清两者间相似而易混淆的概念。
泛型允许你抽象类型。一个最常见的例子就是容器类型,例如在Collections层次体系中的那些容器类型。
以下是对于它们的一个经典用法:

    List myIntList = new LinkedList(); // 1
    myIntList.add(new Integer(0)); // 2
    Integer x = (Integer) myIntList.iterator().next(); // 3        

The cast on line 3 is slightly annoying. Typically, the programmer knows what kind of data has been placed into a particular list. However, the cast is essential. The compiler can only guarantee that an Object will be returned by the iterator. To ensure the assignment to a variable of type Integer is type safe, the cast is required.
Of course, the cast not only introduces clutter. It also introduces the possibility of a run time error, since the programmer may be mistaken.
What if programmers could actually express their intent, and mark a list as being restricted to contain a particular data type? This is the core idea behind generics. Here is a version of the program fragment given above using generics:

在第三行的类型转换稍显繁琐。通常来说,程序员知道一个特定的list中应该放置了什么类型的数据。但是,类型转换是必须的。编译器只能保障迭代器返回一个Object。为了确保对于一个Integer类型变量的赋值是类型安全的,需要强制转换。
当然,强制转换不仅会带来clutter。同时还有可能会导致运行时错误,因为程序员是有可能犯错的。那么当程序员可以实时地表达他们的意图,并且标记一个list而限制它只能容纳一个特定的数据类型,应该怎样做呢?这是泛型背后的核心概念。以下是上面的代码片段的使用了泛型的版本:

    List 
    myIntList = new LinkedList(); // 1'
    myIntList.add(new Integer(0)); // 2'
    Integer x = myIntList.iterator().next(); // 3'

Notice the type declaration for the variable myIntList. It specifies that this is not just an arbitrary List, but a List of Integer, written List. We say that List is a generic interface that takes a type parameter--in this case, Integer. We also specify a type parameter when creating the list object.
Note, too, that the cast on line 3' is gone.
Now, you might think that all we've accomplished is to move the clutter around. Instead of a cast to Integer on line 3, we have Integer as a type parameter on line 1'. However, there is a very big difference here. The compiler can now check the type correctness of the program at compile-time. When we say that myIntList is declared with type List, this tells us something about the variable myIntList, which holds true wherever and whenever it is used, and the compiler will guarantee it. In contrast, the cast tells us something the programmer thinks is true at a single point in the code.
The net effect, especially in large programs, is improved readability and robustness.

注意变量myIntList的类型声明。它指定了该List不是一个简单的List,而是一个只能容纳Integer的List,写成List。我们将这个携带了类型参数(本例中是Integer)的List成为泛型接口。我们在创建一个list对象的时候也指定了类型参数。
现在,你可能认为我们所做的一切仅仅是为了移除the clutter around。即在第一行代码使用了一个Integer的类型参数替换了第三行代码处的强制转换。并非如此,这里有一个很大的差异。编译器现在可以在编译时检查程序的类型正确性。当我们将myIntList使用List声明时,此时就描述了变量myIntList,这使得它无论何时何处都会被正确地使用,编译器会保障该点。相反的,强制转换描述的是在某一代码片段中程序员的正确想法。
泛型带来的真正好处是提到了可读性和健壮性,尤其是在大型程序中。

小结

本节主要讲了泛型的诞生背景和作用:

  1. 提高可读性,减少代码冗余。
    在没有泛型之前,编译器在对源代码进行编译的时候对一些无法确定的类型视为Object进行操作,例如容器List中的元素,所以需要对编译器在编译时期无法确定的类型通过cast强制转换进行类型投射,这样才能保证类型安全。但是这样一来每个无法确定类型的地方,以List为例,每一次对List中元素的操作或者读取都要进行强制转换,这些代码是冗余的。当泛型出现后,在声明List之初就定义了它元素的类型,编译器会读懂这个定义,之后会由编译器来自动维护List中元素的类型,即原本cast这个由程序员来做的操作现在由编译器来做了。
  2. 提高健壮性。
    程序员是有可能犯错的,例如在一个大工程中定义了很多个List,在泛型没有出现的时候,每一次对List进行操作的时候都要进行cast,这里存在的问题是,程序员可能在某一刻进行cast的时候记错了当前这个List的元素类型了。而泛型出现之后,在程序员定义List的时候,此时他的想法是绝对正确的,在这个时候他就可以指定List的类型,之后由编译器帮他记住这个类型,程序是不会犯错的。

二、定义简单的泛型

Here is a small excerpt from the definitions of the interfaces List and Iterator in package java.util:

以下是在java.util包中定义接口ListIterator的一小片代码:

    public interface List  {
      void add(E x);
      Iterator iterator();
    }

    public interface Iterator {
      E next();
      boolean hasNext();
    }

This code should all be familiar, except for the stuff in angle brackets. Those are the declarations of the formal type parameters of the interfaces List and Iterator.
Type parameters can be used throughout the generic declaration, pretty much where you would use ordinary types (though there are some important restrictions; see the section The Fine Print.)
In the introduction, we saw invocations of the generic type declaration List, such as List. In the invocation (usually called a parameterized type), all occurrences of the formal type parameter (E in this case) are replaced by the actual type argument (in this case, Integer).
You might imagine that List stands for a version of List where E has been uniformly replaced by Integer:

这段代码除了尖括号中的内容应该都很熟悉。尖括号中的是对于接口ListIterator形式类型参数的定义。
类型参数可以在整个泛型声明范围中使用,在会使用普通类型的地方几乎都可以使用类型参数。(这里还有一些重要的约束;参考小结The Find Print)
在简介中,我们看到了对于List泛型类型的调用,例如List。在该调用(通常被称为参数化的类型)中,所有的形式类型参数(这里指的是E)将会被实际类型参数替换(这里指的是Integer)。你可能会想象成List代表List的一个版本,所有的E都被统一替换成Integer

public interface IntegerList {
    void add(Integer x);
    Iterator iterator();
}

This intuition can be helpful, but it's also misleading.
It is helpful, because the parameterized type List does indeed have methods that look just like this expansion.
It is misleading, because the declaration of a generic is never actually expanded in this way. There aren't multiple copies of the code--not in source, not in binary, not on disk and not in memory. If you are a C++ programmer, you'll understand that this is very different than a C++ template.
A generic type declaration is compiled once and for all, and turned into a single class file, just like an ordinary class or interface declaration.
Type parameters are analogous to the ordinary parameters used in methods or constructors. Much like a method has formal value parameters that describe the kinds of values it operates on, a generic declaration has formal type parameters. When a method is invoked, actual arguments are substituted for the formal parameters, and the method body is evaluated. When a generic declaration is invoked, the actual type arguments are substituted for the formal type parameters.
A note on naming conventions. We recommend that you use pithy (single character if possible) yet evocative names for formal type parameters. It's best to avoid lower case characters in those names, making it easy to distinguish formal type parameters from ordinary classes and interfaces. Many container types use E, for element, as in the examples above. We'll see some additional conventions in later examples.

这种直觉在一定程度上是有用的,但是同时也是一种误导。
它是有用的,因为参数化类型List确实有一些方法类似以上IntegerList的方法。
它是一种误导,因为实际上该泛型的声明从未以这种方式进行拓展。它们并不是代码的副本--无论是在源代码层面,还是字节码层面,或者是硬盘或者内存中都不是。如果你是一个C++编程者,你会明白这与C++ template非常不同。
一个泛型类型声明只会编译一次,然后转换成一个单独的.class文件,就像一个普通的类class或者接口interface声明。
类型Type参数类似于在方法或者构造器中使用的普通参数。很像一个方法拥有的描述了它要操作的拥有各个数值的形式参数,一个泛型声明拥有形式类型参数。当一个方法被调用,实际参数会替代形式参数,然后方法体被执行。当一个泛型类型被调用的时候,实际类型参数替代形式类型参数
关于命名规范的说明。我们建立你使用简练(尽量单个字符)而有寓意或者关联易唤起回忆的名字给形式类型参数命名。最好避免使用小写字符,使得形式类型参数容易与普通类class和接口interface区分开来。如上例所示,很多容器类型使用E(element)。我们将会在后面的示例中看到其他的规范。

你可能感兴趣的:(泛型学习)