泛型程序设计
从Java程序的设计语言1.0版本发布以来,变化最大的部分就是泛型。致使Java SE5.0中增加泛型机制的主要原因为了满足在1999年制定的最早的Java规范需求之一(JSR14).专家组花费了5年左右的时间来定义规范和测试实现。泛型正是我们需要的程序设计手段。使用泛型机制编写的代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。至少在表面上来看,泛型很像C++的模板。与Java一样,在C++中,模板也就是最先被添加到语言中支持强类型集合的。但是,多年以后人们发现模板还有其它的用武之地。
为什么要使用泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。例如,我们并不希望为聚集在任何类型的对象。这是一个泛型程序设计的实例。这样做,因为一个ArrayList类可以聚集任何类型的对象。这是一个泛型程序设计的例子。
实际上,在Java增加泛型类之前已经有一个ArrayList类。下面来研究泛型程序设计的机制是如何演变的,另外还会讲解这对于用户和实现者来说意味着什么。
泛型问题的引出
在Java语言中,为了方便接收参数类型的统一,提供了一个核心类Object,利用此类型对象可以接收所有类型的数据。但是由于其所描述的数据范围过大,所以在实际使用中就会出现传入数据类型错误,从而引发ClassCastExeption异常。例如:现在要设计一个可以描述坐标点的类point,对于坐标点允许保存三种数据。
- 整型数据
- 浮点型数据
- 字符串型数据
- 整型数据
- 浮点类型数据
- 字符串型数据
例子:
class Point
{
private Object x;
private Object y;
public void setX(Object x){
this.x=x;
}
public void setY(Object y){
this.y=y;
}
public void getX(){
return x;
}
public void getY(){
return y;
}
}
point类中的x与y属性都采用了Object作为存储类型,这样就可以接受任何任意的数据类型,于是此时就可能产生两种情况。
情况一:
public class Demo1{
public static void main(String args[]){
point.setX(100);
point.setY(200);
int x=(Integer)point.getX();
int y=(Integer)point.getY();
System.out.println("x的坐标:"+x+"、y的坐标:"+y);
}
}
程序运行的结果:x的坐标:100、y坐标:20
情况二:
public class Demo2{
public static void main(String args[]){
Point point=new Point();
point.setX(100);
point.setY("东偏南20度");
int x=(Integer)point.getX();
int y=(Integer)point.getY();
System.out.println("x的坐标:"+x+"、y的坐标:"+y);
}
}
这个程序在设置Point类坐标数据时采用了不同的数据类型,所以在获取原始数据信息时就会出现程序运行的异常,即这类错误并不会在编译的时候告诉开发者,而是在执行过程中才会产生安全隐患,而造成此问题的核心原因就是Object类型能够接受的数据范围过大。
泛型基本定义:
泛型可以在编译时检测出程序的安全隐患,使用泛型技术可以使程序更加健壮。
如果要想解决项目中的可能出现的ClassCastExeption安全隐患,最为核心的方案就是避免强制性的进行对象向下转型操作。所以泛型设计的核心思想在于:类中的属性或方法的参数与返回值的类型采用动态标记:在对象实例化的时候动态配置要使用的数据类型。
###例子:在类定义上使用泛型
public class Point<T>{
private T x;
private T y;
public void setX(T x){
this.x=x;
}
public void setY(T y){
this.y=y;
}
public void getX(){
return x;
}
public void getY(){
return y;
}
public class Demo3{
public static void main(Stribg args[]){
Point<Integer> point=new Point<Integer>();
point.setX(100);
point.setY(200);
int x=point.getX();
int y=point.getY();
}
}
}
程序运行结果:x的坐标:100、y的坐标:200
类型参数的好处:
在Java中增加泛型类之前,泛型程序设计是用继承实现的。ArrayList类只维护一个Object引用的的数组
public class ArryList
{
private Object[] elementData;
....
public Object get(int i){....}
public void add(Object o){......}
}
- 这种方法有两个问题。当获取一个值时候必须进行强制类型转换。
ArrayList files=new ArrayList();
.....
String filename=(String) file.get(0);
- 此外,这里没有错误类型检查。可以向数组列表中添加任何类的对象。
files.add(new File("...");
- 对于这个调用,编译和运行都不会出错。然而,在其他地方,如果将get的结果强制类型转换为String类型,就会产生一个错误。
泛型提供了一个更好的解决方案:类型参数(type parameters)。ArrayList类有一个类型参数用来指示元素的类型。
ArrayList<String> files=new ArrayList<String>();
这使得代码具有更好的可读性。人们一看就知道这个数组列表中包含的是String对象。
注释:
前面已经提到,在Java SE7及以后的版本中,构造函数中可以省略泛型类型:
ArrayList<String> fiels=new ArrayList<>();
-
省略的类型可以从变量的类型推断得出
-
编译器也可以更好的利用这个信息。当调用get的时候,不需要进行强制类型转换,编译器就知道返回值的类型为String,而不是Object:
String filename=files.get(0);
- 编译器还知道ArrayList中add方法有一个类型为String的参数。这将比使用Object类型的参数安全一些。现在,编译器可以进行检查,避免插入错误类型的对象。例如:
files.add(new File("..."));
是无法通过编译的。出现编译错误比类在运行时出现类的强制类型转换异常要好很多。
类型参数的美丽在于:使得程序具有更好的可读性和安全性。
泛型接口:
泛型除了定义在类上也可以定义在接口上,这样的结构成为泛型接口。
例子:定义泛型接口
interface IMessage<T>{
public String echo(T msg);
}
对于此事的IMessage泛型接口在进行子类定义时就有两种实现方式:在子类中继续声明泛型和子类中为父类设置为泛型。
例子:定义泛型接口子类
interface IMessage<T>{
public String echo(T msg);
}
class MessageImpl<S> implemnets IMessage<S>{
public String echo(S t){
return "[echo]"+t;
}
}
public class Demo4{
public static void main(String args[]){
IMessage<String> msg=new MessageImpl();
System.out.println(msg.echo("Java编程中心");
}
}
运行结果:[echo]Java编程中心
泛型方法
在一定的环境下,类与接口往往不需要进行泛型定义,然而对于该结构体中的方法又可能出现泛型要求。
例子:定义泛型方法
public class Demo5{
public static void main(String args[]){
Integer num[]=fun(1,2,3);
for(int temp:num){
System.out.println(msg.echo(temp+"、");
}
public static<T> T[]fun(T ...args){
return args;
}
}
}
##泛型总结:
- 1.泛型具有参数化的能力。可以定义使用泛型类型的类或方法,编译器会用具体类型来替换泛型类型。
- 2.泛型的主要优势是能够在编译时而不是运行时检查错误。
- 3.泛型类或方法允许指定这个类或方法可以带有的对象类型。如果试图使用带有不兼容对象的类或方法,编译器会检测出这个错误。
- 4.定义在类、接口或者静态方法中的泛型称为形式泛型类型,随后可以使用一个实际具体类型来替换它。替换泛型类型的过程称为实例化。
- 5.不使用类型参数的泛型类称为原始类型,例如ArrayList。使用原始类型是为了向后兼容Java较早的版本。
- 6.通配泛型类型有三种形式:?、?extends T和?super T,这里的T代表一个泛型类型。第一种形式?称为非受限通配,它和?extends Object是一样的。第二种形式?extends T称为受限通配,代表T或者T的一个子类型。第三种类型?super T称为下限通配,表示或者T的一个父类型。
- 7.使用称为类型消除的方法来实现泛型。编译器使用泛型类型信息来编译代码,但是随后消除它。因此,泛型信息在运行是不可用的。这个放啊发能够使泛型代码向后兼容使用原始;类型的遗留代码。
- 8.不能使用泛型类型参数来创建实例
- 9.不能使用泛型类型参数创建数组
- 10.不能再静态实例中使用类的泛型参数
- 11.在异常类中不能使用泛型类型参数。
文章参考有:《Java从入门到项目实战》、《Java核心技术卷》