一、泛型的引出
假设要求定义一个表示坐标的操作类(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) throws Exception {
//设置整型
Point point1 = new Point() ;
point1.setX(10) ;
point1.setY(20) ;
int x1 = (Integer) point1.getX() ;
int y1 = (Integer) point1.getY() ;
System.out.println("X的坐标是:" + x1 + ",Y的坐标是:" + y1);
//设置小数
Point point2 = new Point() ;
point2.setX(10.1) ;
point2.setY(20.3) ;
double x2 = (Double) point2.getX() ;
double y2 = (Double) point2.getY() ;
System.out.println("X的坐标是:" + x2 + ",Y的坐标是:" + y2);
//设置字符串
Point point3 = new Point() ;
point3.setX("东经100度") ;
point3.setY("北纬20度") ;
String x3 = (String) point3.getX() ;
String y3 = (String) point3.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[] args) throws Exception {
Point point = new Point() ;
point.setX(10) ; // 此处设置成int型(Integer型)
point.setY("北纬20度") ;
String x = (String) point.getX() ;
String y = (String) point.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类声明对象的时候没有设置泛型,那么程序在编译的过程之中会出现警告信息。使用泛型可以很好的解决数据类型的统一问题。
注意:
JDK 1.5和JDK 1.7在定义泛型的时候是稍微有些区别的,
JDK 1.5的语法,在声明对象和实例化对象的时候必须都同时设置好泛型类型:
Point<String> point = new Point<String>() ;
JDK 1.7的时候简化了,实例化对象时的泛型类型就通过声明时的泛型类型来定义:
Point<String> point = new Point<>() ;
三、泛型通配符
泛型的出现的确可以解决了数据类型的统一问题以及避免了向下转型操作,但同时又会带来新的问题,下面先通过一段程序来观察一下会产生什么问题?
为了简化操作,下面定义一个简单的泛型类:
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<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<Integer>
对象的引用,因为这个方法只能够接收Message<String>
对象的引用,那么可以将print()方法重载吗?,换成Message<Integer>
:
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()); // 只是输出
}
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.setInfo(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>(); // Integer是Number的子类
msg.setInfo("Hello World .");
print(msg); // 引用传递
}
// 只能是String的泛型或者是Object的泛型使用
public static void print(Message<? super String> temp) {
System.out.println(temp.getInfo()); // 只是输出
}
}
运行结果:
Hello World .