Java语言基础特性—第一部分(中)
2014-06-23 11:26
232 查看
本文由 ImportNew - 陈
晓舜 翻译自 javaworld。欢迎加入Java小组。转载请参见文章末尾的要求。
参数化类型是一种泛型类型实例,泛型类型的类型参数被真实的类型参数(参数名称)替换。例如:
Java语言支持下面几种真正类型参数:
实体类型:传入一个类或其他引用类型名称作为类型参数。例如,List,Animal作为参数传给E。
实体参数化类型:传入一个实体参数化类型名称作为类型参数。例如,Set<List>,List作为参数传给E。
数组类型:传入一个数组作为类型参数。例如,Map<String, String[]>,String传入给K,String[]传入给V。
类型参数:直接把类型参数传入作为类型参数。例如,在类Container {Set elements;}中,E就作为参数传给了E。
通配符:传入问号符作为类型参数。例如,Class<?>,?号作为参数类型传给T。
每一个泛型类型都有原生类型的存在,即不包含形参类型列表的泛型类型,例如,Class就是Class的原生类型。跟其他泛型类型不一样,原生类型可以用于任何类型的对象。
定义一个泛型类型需要指定形参列表并在它的实现中贯穿使用这些参数。使用泛型则需要在初始化时传入真正的类型参数给形参。看一下清单5:
清单5展示了泛型的定义和保存合适参数类型的简单
编译清单5(
注意,尽管在这个例子中不会造成类型安全问题。在内部数组中不可能保存非E的对象。我会在将来的文章告诉你怎么去掉这个警告信息。
执行
Set是一个未绑定类型参数的例子,因为你可以传入任何实际的参数类型给E。例如,你可以指定
有时,你希望可以限制传入给类型参数的实际类型参数的类型。例如,你可以希望限制类型参数只接受
你可以通过指定上界来限制类型参数,这是一个传入实际类型参数的最高限制。通过预留关键字
例如,
你可以给类型参数指定多个上界。然后,第一个限定必须为一个类,其他的限定必须为接口。每一个限定是通过
清单6的Employee类抽象出了领时薪的雇员概念。
编译清单6(
你不能指定为一个泛型类型参数指定一个下限限制,想要知道为什么的我推荐阅读Angelika Langer的Java泛型关于下限限制的FAQs,但她说“会比较难理解并且没有什么用”。
我们来看看,如果你想要打印出对象列表,不管这个对象是
你看到的错误信息跟泛型的基本规则有关。
对于一个指定的类型y的子类x和一个原生类型的定义G,
为什么会有这样一条规则?还记得吗,泛型是为了在编译时捕获类型安全错误才被设计出来的,这可是很有用的:没有泛型时,你有可能会在半夜两点被叫起去工作,就是因为你的Java程序抛出了一个
作为展示,我们假设
这个代码段创建了一个基于array list的strings列表。之后把它转换为objects列表(这是不可行的,但现在我们先假设它是成立的)。接着添加一个会引起类型安全问题的
没有泛型,在清单7中你唯一的避免这种类型安全问题的选择就是传一个类型为
清单8中我使用了通配符(?标记)代替了在
编译清单8(javac GenDemo.java)并运行程序(java GenDemo)。你应该可以看到下面的输出:
现在假设你想要复制一个objects列表中满足某些filter条件的元素到另外一个list。你也许会想到定义一个方法
如果你想要传入任意类型的list给源list和目标list,你需要使用通配符作为一个类型占位符。例如,看看下面的
这个方法的参数list是正确的,但有个问题。编译器报告
例如,如果源列表是Shape类型的List,而目标列表是String类型的List,copy方法是可以正常执行的,但当尝试去获取目标列表的元素时就会抛出
你可以使用通配符的上界和下界来部分解决这个问题,如下:
晓舜 翻译自 javaworld。欢迎加入Java小组。转载请参见文章末尾的要求。
参数化类型是一种泛型类型实例,泛型类型的类型参数被真实的类型参数(参数名称)替换。例如:
Set<String>是参数化类型,其中真正类型参数String替换类型参数E。
Java语言支持下面几种真正类型参数:
实体类型:传入一个类或其他引用类型名称作为类型参数。例如,List,Animal作为参数传给E。
实体参数化类型:传入一个实体参数化类型名称作为类型参数。例如,Set<List>,List作为参数传给E。
数组类型:传入一个数组作为类型参数。例如,Map<String, String[]>,String传入给K,String[]传入给V。
类型参数:直接把类型参数传入作为类型参数。例如,在类Container {Set elements;}中,E就作为参数传给了E。
通配符:传入问号符作为类型参数。例如,Class<?>,?号作为参数类型传给T。
每一个泛型类型都有原生类型的存在,即不包含形参类型列表的泛型类型,例如,Class就是Class的原生类型。跟其他泛型类型不一样,原生类型可以用于任何类型的对象。
定义和使用泛型类型
定义一个泛型类型需要指定形参列表并在它的实现中贯穿使用这些参数。使用泛型则需要在初始化时传入真正的类型参数给形参。看一下清单5:
Listing 5. GenDemo.java (version 1)
清单5展示了泛型的定义和保存合适参数类型的简单Container类型的使用。为了使代码简单点,我省略了一些错误检查代码。
Container类通过指定形参类型列表把它自己定义为泛型。类型参数E用于指定保存被添加到内部数组的元素和取出元素时返回的类型。
Container(int size)构造函数通过
elements = (E[]) new Object[size];创建数组。如果你奇怪我为什么不指定
elements = new E[size];,因为做不到啊:如果我们那样定义,会导致
ClassCastException。
编译清单5(
javac GenDemo.java)。E[]转换会导致编译器输出转换未被检查的警告。这标示着从Object[]向下转型为E[]可能会导致类型安全问题,因为Object[]可以保存任何类型的对象。
注意,尽管在这个例子中不会造成类型安全问题。在内部数组中不可能保存非E的对象。我会在将来的文章告诉你怎么去掉这个警告信息。
执行
java GenDemo运行这个程序。你可以看到下面的输出:
类型参数界限
Set是一个未绑定类型参数的例子,因为你可以传入任何实际的参数类型给E。例如,你可以指定Set<Marble>,
Set<Employee>或
Set<String>。
有时,你希望可以限制传入给类型参数的实际类型参数的类型。例如,你可以希望限制类型参数只接受
Employee和它的子类。
你可以通过指定上界来限制类型参数,这是一个传入实际类型参数的最高限制。通过预留关键字
extends后跟上限类型名称来指定上限类型。
例如,
Employees<E extends Employee>类限制了传入给
Employees的类型必须为
Employee或子类(例如,
Accountant)。指定
new Employees<Accountant>是可以的,但
new Employees<String>就不行了。
你可以给类型参数指定多个上界。然后,第一个限定必须为一个类,其他的限定必须为接口。每一个限定是通过
&符来进行分割的。我们看一下清单6。
Listing 6. GenDemo.java (version 2)
清单6的Employee类抽象出了领时薪的雇员概念。Accountant是它的子类,并且实现
Comparable<Accountant>表明
Accountants可以根据自然顺序排序,在这个例子中是通过时薪。
java.lang.Comparable接口被定义为接收一个类型参数T的泛型类型。这个类提供了一个
int compareTo(T o)方法用于比较当前对象和传入参数(T类型),当当前对象小于,等于,大于指定对象时分别返回负整数,0,和正整数。
SortedEmployees类在内部数组中允许你保存继承Employee且实现
Comparable的实例。这个数组会在
Employee子对象被添加后根据时薪进行顺序排序(通过
java.util.Arrays的
void sort(Object[] a, int fromIndex, int toIndex)类方法)。
编译清单6(
javac GenDemo.java)并运行(
java GenDemo)。你应该可以看到下面的输出:
那下界呢?
你不能指定为一个泛型类型参数指定一个下限限制,想要知道为什么的我推荐阅读Angelika Langer的Java泛型关于下限限制的FAQs,但她说“会比较难理解并且没有什么用”。
说说通配符
我们来看看,如果你想要打印出对象列表,不管这个对象是strings,
employees,
shapres还是一些其他的类型。你首先要做的应该是类似清单7。
Listing 7. GenDemo.java (version 3)
strings和
integers的列表是
objects表明表的子类型,看起来很符合逻辑。但当你尝试去编译时,编译器会报错。明确地告诉你
string列表不能转换为
object列表,
integer列表也一样。
你看到的错误信息跟泛型的基本规则有关。
对于一个指定的类型y的子类x和一个原生类型的定义G,
G<x>不是
G<y>的子类型。根据这条规则,尽管
String和
java.lang.Integer是
java.lang.Object的子类型,
List<String>和
List<Integer>却不是
List<Object>的子类型。
为什么会有这样一条规则?还记得吗,泛型是为了在编译时捕获类型安全错误才被设计出来的,这可是很有用的:没有泛型时,你有可能会在半夜两点被叫起去工作,就是因为你的Java程序抛出了一个
ClassCastException然后崩溃了。
作为展示,我们假设
List<String>是
List<Object>类型的子类。如果这成立,你可以写下面的代码:
integer到objects列表中。问题就出在最后一行,因为保存的
integer不能被转换为
string,所以会抛出
ClassCastException。
没有泛型,在清单7中你唯一的避免这种类型安全问题的选择就是传一个类型为
List<Object>的对象给
printList()方法,但这用处并不大。有了泛型,你可以通过通配符来解决这个问题,如清单8所示。
Listing 8. GenDemo.java (version 4)
清单8中我使用了通配符(?标记)代替了在printList()的参数
list和方法体中的
Object。因为这个符号代表任意类型,传
List<String>和
List<Integer>给这个方法都是合法的。
编译清单8(javac GenDemo.java)并运行程序(java GenDemo)。你应该可以看到下面的输出:
探索泛型方法
现在假设你想要复制一个objects列表中满足某些filter条件的元素到另外一个list。你也许会想到定义一个方法void copy(List<Object> src, List<Object> dst, Filter filter),但这个方法只能用于复制Objects列表,其他的根本不行。
如果你想要传入任意类型的list给源list和目标list,你需要使用通配符作为一个类型占位符。例如,看看下面的
copy()方法:
dest.add(src.get(i));触发了类型安全问题。?表明任何类型的对象都可以是list的对象类型,有可能源类型和目标类型并不兼容。
例如,如果源列表是Shape类型的List,而目标列表是String类型的List,copy方法是可以正常执行的,但当尝试去获取目标列表的元素时就会抛出
ClassCaseException。
你可以使用通配符的上界和下界来部分解决这个问题,如下:
相关文章推荐
- Java语言基础特性—第一部分(上)
- Java语言基础特性—第一部分(下)
- Java语言基础特性——第二部分
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识
- Java基础 - 1.1-Java 语言特性及其基本语法
- java语言基础----(特性方面)
- javaSE_8系列博客——Java语言的特性(四)--注解--(1)--基础知识
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(4)-- 变量和数组
- Java语言十大基础特性分析
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(6)-- 表达式、语句、块
- Java语言基础——06.集合框架(3)util包中的工具类和新特性
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(2)-- 变量和常用数据类型
- 【JAVA语言程序设计基础篇】--图形用户界面基础--Swing GUI组件的公共特性
- java基础总结(一)-java语言的特性
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(7)-- 流程控制语句
- java语言基础及特性-02
- 第一部分:Java语言的基础组成
- java语言基础特性
- javaSE_8系列博客——Java语言的特性(二)--高级语言的基础知识(5)-- 运算符
- java语言基础(23)——面向对象三大特性(封装、继承、多态)