一、泛型的引出
假设要求定义一个表示坐标的操作类(Point),这个类可以表示三种类型的坐标:
(1)整数坐标:x=10、y=20;
(2)小数坐标:x=10.1、y=20.3;
(3)字符串坐标:x=“东晋100度”、y=“北纬20度”;
分析:
类中想保存以上数据,一定需要定义x和y两个属性,而这两个属性可以接受三种数据类型,这样的话,只能使用Object类来定义会比较合适,这样会发生如下几种转换关系:
(1)整数:int->自动装箱为Integer->向上转型为Object;
(2)小数:double->自动装箱为Double->向上转型为Object;
(3)字符串:字符串->向上转型为Object.
于是,代码定义如下:
public class Point{
private Object x;
private Object y;
public Object getX(){
return x;
}
public void setX(Object x){
this.x=x;
}
public Object getY(){
return y;
}
public void setY(Object y){
this.y=y;
}
}
下面开始设置不同的数据类型,以测试程序。
public class Test{
public static void main(String args[]){
//设置整数
Point p1=new Point();
p1.setX(10);
p1.setY(20);
int x1=(Integer)p1.getX();
int y1=(Integer)p1.getY();
System.out.println("X的坐标是:"+x1+",Y的坐标是:"+y1);
//设置小数
Point p2=new Point();
p2.setX(10.1);
p2.setY(20.3);
int x2=(Double)p2.getX();
int y2=(Double)p2.getY();
System.out.println("X的坐标是:"+x2+",Y的坐标是:"+y2);
//字符串
Point p3=new Point();
p3.setX("东经100度");
p3.setY("北纬20度");
String x3=(String)p3.getX();
String y3=(String)p3.getY();
System.out.println("X的坐标是:"+x3+",Y的坐标是:"+y3);
}
}
运行结果:
X的坐标是:10,Y的坐标是:20
X的坐标是:10.1,Y的坐标是:20.3
X的坐标是:东经100度,Y的坐标是:北纬20度
现在看起来功能是都实现了,但是程序是否有问题存在呢?
本程序解决问题的关键就在于Object类,所有的类型都可以向Object转换,但是成也Object,败也Object:
public class Test{
public static void main(String[] ars) throws Exception {
Point p=new Point();
p.setX(10);
p.setY("北纬20度");
String x=(String)p.getX();
String y=(String)p.getY();
System.out.println("X的坐标是:"+x+",Y的坐标是:"+y);
}
}
这时程序并没有任何语法错误,因为数字10被装箱成了Integer,可以使用Object接受,从技术上而言,本操作没问题,但是从实际上来讲,数据是有错误的,因为没有统一,所以在取得数据并且执行向下转型的过程之中会出现如下的错误提示信息:
Exception in thread "main" java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String
at Test.main(Test.java:7)
所以,以上的代码存在安全隐患,并且这一安全隐患并没有在程序编译的过程中检查出来。现在可以利用泛型来解决这种尴尬的问题。
二、泛型的定义和使用
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。类中操作的属性或方法的参数的类型不在定义的时候声明,而是在使用的时候动态设置。
上面Point类利用泛型可以做如下定义:
class Point<T>{//T:type
Private T x;
Private T y;
public void setX(T x){
this.x=x;
}
public void setY(T y){
this.y=y;
}
public T getX(){
return x;
}
public T getY(){
return y;
}
}
此时,point类中的x和y两个属性的类型暂时还不知道,等待程序运行的时候动态设置。
测试:
public class Test{
public static void main(String[] args) throws Exception{
Point<String> point=new Point<String>();
point.setX("东经100度");
point.setY("北纬20度");
String x=point.getX();
String y=point.getY();
System.out.println("X的坐标是:"+x+",Y的坐标是:"+y);
}
}
运行结果:
X的坐标是:东经100度,Y的坐标是:北纬20度
此时,没有向下的转型操作关系,程序就避免了安全性的问题。如果设置的类型不统一,在程序编译的过程中也是可以很好的解决了,直接会报出语法错误。
而当用户在使用Point类声明对象的时候没有设置泛型,那么程序在编译的过程之中会出现警告信息。使用泛型可以很好的解决数据类型的统一问题。
注意:
JDK1.5和JDK1.7在定义泛型的时候是稍微有些区别的。
JDK1.5的语法,在声明对象和实例化对象的时候必须都同时设置好泛型类型。
Point<String> poing=new Point<String>();
JDK1.7的时候简化了,实例化对象时的反省类型就通过声明时的泛型类型来定义:
Point<String> point=new Point<>();
三、泛型通配符
泛型的出现的确可以解决了数据类型的统一问题以及避免了向下的转型操作,但同时又会带来新的问题,下面通过一段程序来观察一下会产生什么问题?
class Message<T>{
private T info;
pulbic void setInfo(T info){
this.info=info;
}
public T getInfo(){
return info;
}
}
使用以上类对象执行引用传递:
public class Test{
public static void main(String[] args) throws Exception {
Message<String> msg=new Message<String>();
msg.setInfo("Hello World.");
print(msg);//引用传递
}
public static void print(Message<String> temp){
System.out.println(temp.getInfo());//只是输出
}
}
但是,如果现在定义的泛型类型不是String了呢?例如,换成了int(不能写基本类型,只能是包装类):
public class Test{
public static void main(String[] args) throws Exception{
Message<Integer> msg=new Message<Integer>();
msg.setInfo(100);
print(msg);//无法进行引用传递
}
public static void print(Message<String> temp){
System.out.println(temp.getInfo());//只是输出
}
}
发现这个时候的print()方法无法再接收Message对象的引用,因为这个方法只能够接收Message对象的引用,那么可以将print()方法重载,换成Message:
public class Test{
public static void main(String[] args) throws Exception{
Message<Integer> msg=new Message<Integer>();
msg.setInfo(100);
print(msg);//无法进行引用传递
}
public static void print(Message<Integer> temp){
System.out.println(temp.getInfo());
}
}
但是,这时候发现按照之前的方式根本无法进行方法的重载,因为方法重载的时候观察的不是泛型类型,而是类的名称,或者是说数据类型的,那么现在就可以发现,这个给出了泛型类之后,就相当于将一个类又划分成了若干个不同的小类型:
那么现在的问题是:方法接受的参数问题又严重了,而且比之前使用对象多态性解决问题时出现的麻烦更大了,至少那个时候利用重载可以接受一个类的所有子类对象,而现在连重载的机会都不给了。
这个时候,有人提出了,干脆在定义方法的时候就别写泛型类型了。
定义方法时不写上泛型类型:
public class Test{
public static void main(String[] args) throws Exception{
Message<Integer> msg=new Message<Integer>();
msg.setIfo(100);
print(msg);
}
public static void print(Message temp){
System.out.println(temp.getInfo());
}
}
虽然现在在print()方法的参数上出现了警告,但是现在的程序可算是正常了。但是新的问题又来了,问题就在于方法操作中,没有类型限制了:
public static void print(Message temp){
temp.setInfo(100);//设置Integer
System.out.println(temp.getInfo());
}
发现此时在print()方法之中操作的时候,由于没有设置泛型类型,那么所有的类型都统一变为了Object,也就可以修改了,而通过本程序也就发现了,必须找到一种方法:此方法可以接受任意的泛型类型的设置,并且不能够修改,只能够输出。为了解决这样的问题,可以使用通配符“?”表示:
public static void print(Message<?> temp){
System.out.println(temp.getInfo());
}
在通配符“?”上有两个子通配符:
(1)设置泛型的上限:
? extends 类
可以在声明上和方法参数上使用,例如:
? extends Number
表示:只能是Number或者是Number的子类Integer,Double等。
(2)设置泛型的下限:
? super 类
方法参数上使用,例如:
? super String
表示只能是String或者是String的父类(Object类)。
范例:设置泛型上限
class Message<T extends Number>{
private T info;
public void setInfo(T info){
this.info=info;
}
public T getInfo(){
return info;
}
}
public class Test{
public static void main(String[] args) throws Exception{
Message<Integer> msg=new Message<Integer>();//Integer是Number的子类
msg.setInfo(100);
print(msg);//引用传递
}
public static void print(Message<?> temp){
System.out.println(temp.getInfo());//只是输出
}
}
运行结果:
100
范例:设置泛型下限,在方法参数上使用
class Message<T>{
private T info;
public void setInfo(T info){
this.info=info;
}
public T getInfo(){
return info;
}
}
public class Test{
public static void main(String[] args) throws Exception {
Message<String> msg=new Message<String>();
msg.setInfo("Hello World.");
print(msg);
}
public static void print(Message<? super String> temp){
System.out.println(temp.getInfo());
}
}
运行结果:
Hello World.
四、泛型接口
使用泛型定义的接口可以称为泛型接口。例如
interface Message<T>{
public String getInfo(T msg);
}
泛型接口的实现,在Java中有两种方式:
(1)在子类上继续定义泛型,同时此泛型在接口上继续使用。例如:
interface Message<T>{
public String getInfo(T msg);
}
class MessageImpl<T> implements Message<T>{
public String getInfo(T msg){
return "info:"+msg;
}
}
public class Test{
public static void main(String[] args) throws Exception{
Message<String> msg=new MessageImpl<String>();
System.out.println(msg.getInfo("张三"));
}
}
运行结果:
张三
(2)在子类上设置具体类型:
interface Message<T>{
public String getInfo(T msg);
}
class MessageImpl implements Message<String>{
public String getInfo(String msg){
return "info:"+msg;
}
}
public class Test{
public static void main(String[] args) throws Exception{
Message<String> msg=new MessageImpl();
System.out.println(msg.getInfo());
}
}
运行结果:
info:张三
五、泛型方法
泛型除了可以定义在类上之外,也可以在方法中进行定义,而在方法上定义泛型的时候,这个方法不一定非要在泛型类中定义。
例1:
public class Test{
public static void main(String[] args) throws Exception{
String str=fun("hello world");
System.out.println(str.length());
}
//T的类型由传入的参数类型决定
public static<T> T fun(T t){
return t;
}
}
运行结果:
11
例2:
public class Test{
public static void main(String[] args) throws Exception{
Integer result[]=get(1,2,3);
for(int temp:result){//连自动拆箱都包含了
System.out.println(temp);
}
}
public static <T> T[] get(T...args){//T的类型由方法调用的时候来决定
return args;
}
}
运行结果:
1
2
3
六、泛型小结
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。