Java基础学习——泛型(generics)学习一
来源:互联网 发布:北京大学远程网络教育 编辑:程序博客网 时间:2024/06/06 00:30
概述
在JDK 5.0,Java语言引入了好几个新的功能,其中很重要的一个就是泛型(generics)。
本文就是对泛型的一个概述。你可以很熟悉其他语言中的类似结构,比如C++里的模板(templates)。如果这样,你将会看到两者之间有相似,同时也有很大的不同。如果你之前并不熟悉酷似的东西,那就更好了,你将会没有包袱,从而重头开始。
泛型(generics)是一种对类型(types)的抽象。最常见的例子便是容量类型,比如 Java提供的Collections。
下面是Java中典型的排序用法。
List myIntList = new LinkedList(); // 1myIntList.add(new Integer(0)); // 2Integer x = (Integer) myIntList.iterator().next(); // 3
第三行的强制类型转换是比较烦人的。通常,程序员都是知道List里是放的什么类型的数据,然后,强制类型转换却是必不可少的。编译器只能仅仅保证Object对象被迭代器返回(iterator)。为了Integer变量赋值操作的类型安全,强制类型转换是必须的。
当然,强制类型转换并不仅仅带来混乱,还有可能因为程序员的失误,带来运行时的错误(run time error)。
能不能有种机制,程序员能明确表达意图,使一个List仅仅只能包含一个指明的类型?那这便是泛型(generics)的核心思想。下面是上面那份代码的泛型版本。
List<Integer> myIntList = new LinkedList<Integer>(); // 1'myIntList.add(new Integer(0)); // 2'Integer x = myIntList.iterator().next(); // 3'
留意下变量myIntList的声明,它并不只是个List,而是一个被写作List<Integer>的Integer List。我们叫List是一个泛型接口(generic interface),拥有一个type参数,Integer。当我们新建一个List对象的时候,需要特别指定一个type参数。同时,我们可以看到,第三行的强制类型转换也消失了。
现在,你也行会认为我们已经将杂乱的麻烦移除了。我们用第一行的类型参数Integer替换了第三行的Integer强制类型转换。然而,这两者却又很大的不同。编译器现在可以在编译期进行类型安全检测了。当我们用List<Integer>类型定义myIntList时,便告诉了编译器一些约定,同时,编译器会保证这些。在一些大型程序里,泛型提供了更好的健壮性。
以上内容翻译 Java 官网 Generics Introduction
简单定义
下面是Jdk中java.util包内,List和Iterator的定义的一些简单择录。
public interface List <E> { void add(E x); Iterator<E> iterator();}public interface Iterator<E> { E next(); boolean hasNext();}
这些代码都是相似的,除了尖括号里的内容。尖括号里的内容其实就是List和Iterator的类型参数。
类型参数会用在泛型声明中所有需要真实类型地方(虽然有很多限制)。
在概述中,我们已经看到了泛型List的调用方法;在这个调用中,所有出现类型参数(通常也被叫做参数化的类型(parameterized type))的地方(在上面例子中的E)都会被真实类型替换(在上面例子中的Integer)。
你可能会想List<Integer>就是List的一个所有E都会Integer替换的特殊版本,像下面一样:
public interface IntegerList { void add(Integer x); Iterator<Integer> iterator();}
这种类比,虽然有些好处,但它同时也是一种误导。
说这种类比有好处,是因为参数化类型的List<Integer>的确看起来像是这种扩展。
说它误导,是因为泛型重没有做这种替换扩展。泛型不是源代码的副本,也不是二进制的,不是硬盘上副本,也不是内存里的副本。如果你是个C++程序员,你就会发现这与C++模板非常不同。
一个泛型类型的源码仅仅只会被编译一次,被转成一个Class文件,就像普通类和接口一样。
类型参数在方法或构造函数里的使用方式和普通参数一样。就像函数参数声明表示值参数(formal value parameters),描述的是运行的值一样,一个泛型声明表示一个类型参数(formal type parameters)。当一个方法被调用时,实际参数会取代形式参数,然后执行函数体。当一个泛型被调用时,这个实际类型会用来替换形式类型参数。
一些关于命名约定的注意事项。我们推荐使用简短的方式(如果可以使用单字母)。最好能避免使用小写字母,从而很方便和普通类和接口名称区分。像上面的例子一样,很多集合类型都用E代表元素。后面我们还会有更多的例子。
以上内容翻译自Defining Simple Generics
泛型和子类型(Generics and Subtyping)
让我们测试对泛型的理解。下面的代码片段是否合法呢?
List<String> ls = new ArrayList<String>(); // 1List<Object> lo = ls; // 2
第一行,毫无疑问是合法的。棘手的问题是第二行。这个也就是说:一个String 的list是否是一个Object的List。大部分的人可能脱口而出说,是的。
那我们继续看下面的这几行:
lo.add(new Object()); // 3String s = ls.get(0); // 4: Attempts to assign an Object to a String!
上面的例子,我们将lo当成ls的别名。通过别名lo访问了String的list ls,然后随便插入了一个Object对象进去。结果ls就再也不能只保存String的对象了,当我们想从ls里取出一些东西的时候,我们肯定会大吃一惊。
Java的编译器会阻止上面的事情发生。上面的第二行会引起一个编译期错误。
一般而言,如果Foo是Bar的一个子类型(subtype,(subclass or subinterface)),然后G是一个泛型定义,这不会导致G<Foo>是G<Bar>的一个子类型。这个也是泛型学习里很困难的事情,因为它违背我们深信不疑的直觉。
我们不能假设容器都是不变的。我们的直觉总是让我们感觉这些东西都是一成不变的。举个栗子,如果交管局(the department of motor vehicles,DMV)拥有一个人口普查局(the census bureau)里机动车驾驶员户口信息的list,这其实挺合理的。我们可能会认为List<Driver> 是一个List<Person>(is-a),假设Driver是Person的子类。但事实,只是把驾驶员信息做了一份拷贝。否则,如果人口普查局(the census bureau)新增一个非驾驶员的人口信息,将会污染DMV里的记录。
为了应对这种情况,我们需要更灵活地看待泛型的类型。到目前为止,我们看到的规则都是相当严格的。
以上内容翻译自 Java 官网 Generics and Subtyping
- Java基础学习——泛型(generics)学习一
- Java基础学习——泛型(generics)二
- java基础-generics 泛型
- 对照Java学习Swift--泛型(Generics)
- Java学习(1)-Java.Generics
- Java学习笔记(19)Generics
- Swift学习笔记22——泛型(Generics)
- 泛型(Generics Types)学习笔记
- 泛型(Generics Types)学习笔记
- java学习脚印: 泛型(Generics)认识之三
- java学习(泛型编程generics)
- 蜗牛—Java基础之学习(一)
- [java基础学习]02——Java基础语法(一)
- java基础学习一
- java基础学习(一)
- java基础学习(一)
- java基础学习(一)
- Java基础学习一
- VS2010+Python初探
- A10 负载均衡抓包方法
- C#控制台 通过tryparse,控制台输入给Int32类型变量赋值
- Android内存泄漏案例分析一
- 【JavaScript】div+css定位position详解
- Java基础学习——泛型(generics)学习一
- 2016/08/01 升级版ATM机取款过程
- poj 1068 Parencodings
- 第一行代码中遇到的小问题
- char short 类型的提升
- 字符流中第一个不重复的字符
- ubuntu 软件安装
- 求多个数的最小公倍数
- 图的最短路径算法