泛型学习

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<Integer> 
    myIntList = new LinkedList<Integer>(); // 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<Integer>. 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<Integer>, 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>。我们将这个携带了类型参数(本例中是Integer)的List成为泛型接口。我们在创建一个list对象的时候也指定了类型参数。
现在,你可能认为我们所做的一切仅仅是为了移除the clutter around。即在第一行代码使用了一个Integer的类型参数替换了第三行代码处的强制转换。并非如此,这里有一个很大的差异。编译器现在可以在编译时检查程序的类型正确性。当我们将myIntList使用List<Integer>声明时,此时就描述了变量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 <E> {
      void add(E x);
      Iterator<E> iterator();
    }

    public interface Iterator<E> {
      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<Integer>. 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<Integer> stands for a version of List where E has been uniformly replaced by Integer:

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容