1. 泛型的引出
现在要求定义一个表示坐标的操作类(Point), 在这个类里面保存以下几种坐标:
- 保存数字:x = 10 , y = 20 ;
- 保存小数:x = 10.2 , y = 20.3;
- 保存字符串:x = 东经20度,北纬15度
现在这个Point类设计的关键就在于x与y这两个变量的类型,必须有一种类型可以保存这三种数据,那么就是Object类型: - int : int自动封装为Integer,Integer向上转型为Object;
- double : double自动装箱为Double,Double向上转型为Object;
- String : 直接向上转型为Object。
范例:初期设计如下:
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 Object getX() {
return x;
}
public Object getY() {
return y;
}
}
下面重复演示三个程序,分别使用各个不同的数据类型。
范例:在Point类里面保存整型数据
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX(10);
p.setY(20);
//第二部:取出数据
int x = (Integer)p.getX();
int y = (Integer)p.getY();
System.out.println("X坐标: " + x + ",Y坐标: " + y);
}
}
输出显示:
X坐标: 10,Y坐标: 20
范例:使用Double型数据
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX(10.2);
p.setY(20.3);
//第二部:取出数据
double x = (Double)p.getX();
double y = (Double)p.getY();
System.out.println("X坐标: " + x + ",Y坐标: " + y);
}
}
输出显示:
X坐标: 10.2,Y坐标: 20.3
范例:使用字符串
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX("东经100度");
p.setY("北纬20度");
//第二部:取出数据
String x = (String)p.getX();
String y = (String)p.getY();
System.out.println("X坐标: " + x + ",Y坐标: " + y);
}
}
输出显示:
X坐标: 东经100度,Y坐标: 北纬20度
以上的代码利用Object数据类型满足了需求,但是在设置数据的时候,不一定会按照开发的数据类型设置;比如:
范例:错误设置数据
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX("东经100度");
p.setY(10); //错误的设置数据
//第二部:取出数据
String x = (String)p.getX();
String y = (String)p.getY();
System.out.println("X坐标: " + x + ",Y坐标: " + y);
}
}
编译报错:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at GenericsDemo.main(GenericsDemo.java:26)
原因分析:因为在设置的时候存放的是int(Integer),而取出数据的类型是String,两者没有任何关系的类对象之间发生了强制转换,就产生了ClassCastException异常。
这就涉及到向上转型和向下转型,具体内容查看之前写的多态
从JDK1.5之后开始增加了泛型:类在定义的时候,可以使用一个标记,表示类中属性或方法参数的类型,在使用的时候才动态的设置类型。
范例:使用泛型标记
class Point {
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类的时候,才设置标记的内容,也就是设置了类中的属性类型
范例:设置为String
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX("东经100度");
p.setY("北纬20度");
//第二部:取出数据,由于接收的类型是String
//所以不需要向下强制转型
String x = p.getX();
String y = p.getY();
System.out.println("X坐标: " + x + ",Y坐标: " + y);
}
}
使用泛型后,所有类中属性的类型都是动态设置的,而所有这种泛型标记的方法数据类型也发生了改变,这样就避免了向下转型的问题,就解决了类对象转换的安全隐患。
对于泛型有两点说明:
- 如果在使用泛型类或者是接口的时候,没有设置泛型具体类型,那么就会出现编译警告,同时为了保证程序不出错,所有的泛型都将使用Object表示。
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point(); //将使用Object类型描述泛型
//利用的就是包装类的自动装箱功能
p.setX(10);
p.setY(20);
int x = (Integer)p.getX();
int y = (Integer)p.getY();
System.out.println("X坐标: " + x + ", Y坐标:" + y);
}
}
- 从JDK1.7开始可以简化声明泛型
public class GenericsDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point<>(); //JDK1.7之后实例化的泛型可以省略
p.setX(10);
p.setY(20);
int x = p.getX();
int y = p.getY();
System.out.println("X坐标: " + x + ", Y坐标:" + y);
}
}
2. 通配符
为了更好的理解通配符,先观察一个程序:
private T msg;
public void setMsg(T msg) {
this.msg = msg;
}
public T getMsg() {
return msg;
}
}
public class WildcardDemo {
public static void main(String[] args) {
Message m = new Message();
m.setMsg("Hello world!");
fun(m);
}
public static void fun(Message temp) {
System.out.println(temp.getMsg());
}
}
以上代码中,Message类设置了String泛型,如果现在设置其它的类型呢?例如:
public class WildcardDemo {
public static void main(String[] args) {
Message m = new Message<>();
m.setMsg(10);
fun(m);
}
public static void fun(Message temp) {
System.out.println(temp.getMsg());
}
}
报错显示:
WildcardDemo.java:14: 错误: 不兼容的类型: Message无法转换为Message
fun()方法里面接收的是"Message
所以现在需要可以接收一个类的任意泛型类型,但是不可以修改只能够取出,所以可以使用通配符: "?" 来描述。
public class WildcardDemo {
public static void main(String[] args) {
Message m = new Message<>();
m.setMsg(10);
fun(m);
}
//使用通配符 ?
public static void fun(Message> temp) {
System.out.println(temp.getMsg());
}
}
在"?" 通配符还有两个子通配符:
- ?extends 类 : 设置泛型上限,可以在声明时和方法参数上使用;
· ? extends Number : 表示可以设置Number或者是Number的子类(Integer、Double...) - ? super 类 :设置泛型下限,使用在方法参数上;
· ? super String :表示只能够设置String或者是它的父类Object。
范例:设置泛型的上限
class Message { //设置泛型上限
private T msg;
public void setMsg(T msg) {
this.msg = msg;
}
public T getMsg() {
return msg;
}
}
public class WildcardDemo {
public static void main(String[] args) {
Message m = new Message<>();
m.setMsg(10);
fun(m);
}
//使用通配符 ?
public static void fun(Message extends Number> temp) {
System.out.println(temp.getMsg());
}
}
如果设置了非Number或非子类的话,就会出现错误。
范例:设置泛型的下限
class Message {
private T msg;
public void setMsg(T msg) {
this.msg = msg;
}
public T getMsg() {
return msg;
}
}
public class WildcardDemo {
public static void main(String[] args) {
Message m = new Message<>();
m.setMsg("Hello World!");
fun(m);
}
//使用通配符 ?; 设置泛型下限
public static void fun(Message super String> temp) {
System.out.println(temp.getMsg());
}
}
3. 泛型接口
之前都是将泛型定义在一个类里面,泛型也可以在接口上声明,称为泛型接口。
范例:定义泛型接口
interface Message {
public void print(T t);
}
接口必须有子类,有两种形式定义泛型接口子类:
形式一:在子类继续设置泛型
//子类也继续使用泛型,并且父接口使用和子类同样的泛型标记
class MessageImpl implements IMessage {
public void print(T t) {
System.out.println(t);
}
}
public class GenericInterface {
public static void main(String[] args) {
IMessage msg = new MessageImpl();
msg.print("Hello world!");
}
}
形式二:在子类不设置泛型,而为父接口明确的定义一个泛型类型
//为父类明确的定义String类型的泛型
class MessageImpl implements IMessage {
@Override
public void print(String t) {
System.out.println(t);
}
}
public class GenericInterface {
public static void main(String[] args) {
IMessage msg = new MessageImpl();
msg.print("Hello world!");
}
}
4. 总结
- 泛型解决的是向下转型会出现的安全隐患,其核心的组成就是在成名类或者接口的时候不设置参数或属性的类型;
- " ? " 可以接收任意的泛型类型,只能取出,不能够修改。