Java语言基础特性—第一部分(中)
来源:互联网 发布:测试网络稳定性 编辑:程序博客网 时间:2024/06/10 04:12
参数化类型是一种泛型类型实例,泛型类型的类型参数被真实的类型参数(参数名称)替换。例如: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)
class
Container<E>
{
private
E[] elements;
private
int
index;
Container(
int
size)
{
elements = (E[])
new
Object[size];
index =
0
;
}
void
add(E element)
{
elements[index++] = element;
}
E get(
int
index)
{
return
elements[index];
}
int
size()
{
return
index;
}
}
public
class
GenDemo
{
public
static
void
main(String[] args)
{
Container<String> con =
new
Container<String>(
5
);
con.add(“North”);
con.add(“South”);
con.add(“East”);
con.add(“West”);
for
(
int
i =
0
; i < con.size(); i++)
System.out.println(con.get(i));
}
}
清单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
运行这个程序。你可以看到下面的输出:
North
South
East
West
类型参数界限
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)
import
java.math.BigDecimal;
import
java.util.Arrays;
abstract
class
Employee
{
private
BigDecimal hourlySalary;
private
String name;
Employee(String name, BigDecimal hourlySalary)
{
this
.name = name;
this
.hourlySalary = hourlySalary;
}
public
BigDecimal getHourlySalary()
{
return
hourlySalary;
}
public
String getName()
{
return
name;
}
public
String toString()
{
return
name+”: “+hourlySalary.toString();
}
}
class
Accountant
extends
Employee
implements
Comparable<Accountant>
{
Accountant(String name, BigDecimal hourlySalary)
{
super
(name, hourlySalary);
}
public
int
compareTo(Accountant acct)
{
return
getHourlySalary().compareTo(acct.getHourlySalary());
}
}
class
SortedEmployees<E
extends
Employee & Comparable<E>>
{
private
E[] employees;
private
int
index;
SortedEmployees(
int
size)
{
employees = (E[])
new
Employee[size];
int
index =
0
;
}
void
add(E emp)
{
employees[index++] = emp;
Arrays.sort(employees,
0
, index);
}
E get(
int
index)
{
return
employees[index];
}
int
size()
{
return
index;
}
}
public
class
GenDemo
{
public
static
void
main(String[] args)
{
SortedEmployees<Accountant> se =
new
SortedEmployees<Accountant>(
10
);
se.add(
new
Accountant(“John Doe”,
new
BigDecimal(“
35.40
”)));
se.add(
new
Accountant(“George Smith”,
new
BigDecimal(“
15.20
”)));
se.add(
new
Accountant(“Jane Jones”,
new
BigDecimal(“
25.60
”)));
for
(
int
i =
0
; i < se.size(); i++)
System.out.println(se.get(i));
}
}
清单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
)。你应该可以看到下面的输出:
George Smith: 15.20
Jane Jones: 25.60
John Doe: 35.40
那下界呢?
你不能指定为一个泛型类型参数指定一个下限限制,想要知道为什么的我推荐阅读Angelika Langer的Java泛型关于下限限制的FAQs,但她说“会比较难理解并且没有什么用”。
说说通配符
我们来看看,如果你想要打印出对象列表,不管这个对象是strings
,employees
,shapres
还是一些其他的类型。你首先要做的应该是类似清单7。
Listing 7. GenDemo.java (version 3)
import
java.util.ArrayList;
import
java.util.Iterator;
import
java.util.List;
public
class
GenDemo
{
public
static
void
main(String[] args)
{
List<String> directions =
new
ArrayList<String>();
directions.add(“north”);
directions.add(“south”);
directions.add(“east”);
directions.add(“west”);
printList(directions);
List<Integer> grades =
new
ArrayList<Integer>();
grades.add(
new
Integer(
98
));
grades.add(
new
Integer(
63
));
grades.add(
new
Integer(
87
));
printList(grades);
}
static
void
printList(List<Object> list)
{
Iterator<Object> iter = list.iterator();
while
(iter.hasNext())
System.out.println(iter.next());
}
}
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>
类型的子类。如果这成立,你可以写下面的代码:
List<String> directions =
new
ArrayList<String>();
List<Object> objects = directions;
objects.add(
new
Integer());
String s = objects.get(
0
);
这个代码段创建了一个基于array list的strings列表。之后把它转换为objects列表(这是不可行的,但现在我们先假设它是成立的)。接着添加一个会引起类型安全问题的integer
到objects列表中。问题就出在最后一行,因为保存的integer
不能被转换为string
,所以会抛出ClassCastException
。
没有泛型,在清单7中你唯一的避免这种类型安全问题的选择就是传一个类型为List<Object>
的对象给printList()
方法,但这用处并不大。有了泛型,你可以通过通配符来解决这个问题,如清单8所示。
Listing 8. GenDemo.java (version 4)
import
java.util.ArrayList;
import
java.util.Iterator;
import
java.util.List;
public
class
GenDemo
{
public
static
void
main(String[] args)
{
List<String> directions =
new
ArrayList<String>();
directions.add(“north”);
directions.add(“south”);
directions.add(“east”);
directions.add(“west”);
printList(directions);
List<Integer> grades =
new
ArrayList<Integer>();
grades.add(
new
Integer(
98
));
grades.add(
new
Integer(
63
));
grades.add(
new
Integer(
87
));
printList(grades);
}
static
void
printList(List<?> list)
{
Iterator<?> iter = list.iterator();
while
(iter.hasNext())
System.out.println(iter.next());
}
}
清单8中我使用了通配符(?标记)代替了在printList()
的参数list
和方法体中的Object
。因为这个符号代表任意类型,传List<String>
和List<Integer>
给这个方法都是合法的。
编译清单8(javac GenDemo.java)并运行程序(java GenDemo)。你应该可以看到下面的输出:
north
south
east
west
98
63
87
探索泛型方法
现在假设你想要复制一个objects列表中满足某些filter条件的元素到另外一个list。你也许会想到定义一个方法void copy(List<Object> src, List<Object> dst, Filter filter)
,但这个方法只能用于复制Objects列表,其他的根本不行。
如果你想要传入任意类型的list给源list和目标list,你需要使用通配符作为一个类型占位符。例如,看看下面的copy()
方法:
void
copy(List<?> src, List<?> dest, Filter filter)
{
for
(
int
i =
0
; i < src.size(); i++)
if
(filter.accept(src.get(i)))
dest.add(src.get(i));
}
这个方法的参数list是正确的,但有个问题。编译器报告dest.add(src.get(i));
触发了类型安全问题。?表明任何类型的对象都可以是list的对象类型,有可能源类型和目标类型并不兼容。
例如,如果源列表是Shape类型的List,而目标列表是String类型的List,copy方法是可以正常执行的,但当尝试去获取目标列表的元素时就会抛出ClassCaseException
。
你可以使用通配符的上界和下界来部分解决这个问题,如下:
void
copy(List<?
extends
String> src, List<?
super
String> dest,
Filter filter)
{
for
(
int
i =
0
; i < src.size(); i++)
if
(filter.accept(src.get(i)))
dest.add(src.get(i));
}
- Java语言基础特性—第一部分(中)
- Java语言基础特性—第一部分(上)
- Java语言基础特性—第一部分(下)
- Java语言基础特性——第二部分
- 第一部分:Java语言的基础组成
- Java基础部分-《第一部分》
- 第一部分 Go 语言基础
- JAVA基础——常用语句格式(第一部分)
- java语言特性基础
- java基础问题(第一部分)
- java基础部分总结第一部分
- java语言基础----(特性方面)
- java语言基础部分(1)——常用关键字
- JAVA面试精选【Java基础第一部分】
- JavaScript基础(第一部分)
- C语言——第一部分 C语言概述以及编程基础
- Java修炼 之 基础篇(一)Java语言特性
- Java修炼 之 基础篇(一)Java语言特性
- Ios设备介绍,android资源文件和区分手机和平板的标准,按钮的状态
- 设计模式——策略模式
- SVN服务器搭建和SVN客户端安装及其…
- SVN服务器搭建和SVN客户端安装及其…
- SVN服务器搭建和SVN客户端安装及其…
- Java语言基础特性—第一部分(中)
- POJ 3104 Drying 二分搜索
- Velocity中文乱码解决
- quartz在服务器启动时,自动更新的…
- System.getProperties()方法获取的…
- 如何获取spring源代码
- myeclipse或eclipse生成javadoc时…
- servlet请求默认参数
- myeclipse项目打包发布时的目录问题