泛型
问题引出
1、要求:定义一个表示坐标的类(Point),该类要保存以下几种坐标:
· 整数:x = 10、y = 20;
· 小数:x = 10.2、y = 20.3;
· 字符串:x = 东经20度、y = 北纬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 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 Demo {
public static void main(String[] args) {
// 1. 设置数据
Point pA = new Point();
pA.setX(10);
pA.setY(20);
// 2. 取出数据
int x = (Integer) pA.getX();
int y = (Integer) pA.getY();
System.out.println("(" + x + " , " + y + ")");
}
}
上述代码利用Object数据类型解决了问题,但依然可能有一定问题。
范例:造成异常的代码:
public class Demo {
public static void main(String[] args) {
// 1. 设置数据
Point pA = new Point();
pA.setX("东经10度");
pA.setY(10);
// 2. 取出数据
String x = (String) pA.getX();
String y = (String) pA.getY();
System.out.println("(" + x + " , " + y + ")");
}
}
上述异常产生的原因是因为设置时存放的是int(Integer),而取出时是String。两个没有任何关系的类对象之间发生强制转换,会出现java.lang.ClassCastException
错误。
2、向上转型是为了统一参数,向下转型是为了调用子类定义的特殊功能。向下转型是一种不安全的操作,那么这种操作应该在代码运行前排查出来。
从JDK1.5增加了泛型,泛型的核心作用在于:类在定义的时候,可以使用一个标记,此标记动态表示类中属性或方法参数的类型,使用时设置具体类型。
// T在Point类定义上只表示一个标记,使用时需要为其设置具体的类型
class Point<T> { // 定义坐标,Type = T
private T x; // 该属性类型未知,由Point动态设置
private T y; // 该属性类型未知,由Point动态设置
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
在使用Point类时,才设置标记的类型,即设置类中属性的类型。
范例:设置为String
public class Demo {
public static void main(String[] args) {
// 1. 设置数据
Point<String> pA = new Point();
pA.setX("东经10度");
pA.setY("北纬20度");
// 此时Point类的类型为String,不需要向下转型
// 2. 取出数据
String x = pA.getX();
String y = pA.getY();
System.out.println("(" + x + " , " + y + ")");
}
}
3、使用泛型后,类中属性的类型都是动态设置的,这样避免了向下转型的问题。但是泛型只能用于类,即不能用于基本数据类型,只能是引用类型(例如,不能用<int>
,只能用<Integer>
)
public class Demo {
public static void main(String[] args) {
// 1. 设置数据
Point<Integer> pA = new Point();
pA.setX(10);
pA.setY(20);
// 利用包装类的自动装箱和自动拆箱
// 2. 取出数据
int x = pA.getX();
int y = pA.getY();
System.out.println("(" + x + " , " + y + ")");
}
}
4、对于泛型有两点说明:
· 使用泛型类或接口时,没有设置接口的具体类型会出现编译警告,为了保证程序不出错,将默认使用Object表示。
public class Demo {
public static void main(String[] args) {
// 1. 设置数据
Point pA = new Point(); // 将使用Object描述泛型
pA.setX(10);
pA.setY(20);
// 2. 取出数据,需要转型
int x = (Integer) pA.getX();
int y = (Integer) pA.getY();
System.out.println("(" + x + " , " + y + ")");
}
}
· JDK1.7开始,可以简化泛型声明
Point<Integer> pA = new Point();
即实例化时只要在前面声明一个泛型的具体类型。
通配符
范例:观察下述程序
class Message<T> {
private T msg;
public void setMsg(T msg) {
this.msg = msg;
}
public T getMsg() {
return msg;
}
}
public class Demo {
public static void main(String[] args) {
Message<String> m = new Message<String>();
m.setMsg("Hello");
fun(m); // 引用传递
}
public static void fun(Message<String> temp) {
System.out.println(temp.getMsg());
}
}
上述代码为Message类设置String型的泛型,但是此时设置其他类型时,fun()中的Message<String>
就不能使用了,并且fun()不能针对不同的泛型进行重载,因为方法重载只认得参数类型,无法辨别泛型的不同。
解决方法一:不设置方法参数的泛型
public class Demo {
public static void main(String[] args) {
Message<Integer> mA = new Message<Integer>();
Message<String> mB = new Message<String>();
mA.setMsg(100);
mB.setMsg("Hello");
fun(mA); // 引用传递
fun(mB);
}
public static void fun(Message temp) {
System.out.println(temp.getMsg());
}
}
此时fun()存在警告,因为不设置具体泛型,就会存在警告。并且存在下述问题:
public class Demo {
public static void main(String[] args) {
Message<Integer> mA = new Message<Integer>();
mA.setMsg(100);
fun(mA); // 引用传递
}
public static void fun(Message temp) { // 不设置泛型,默认为Object型
temp.setMsg("Hello"); // 设置Striing型
System.out.println(temp.getMsg());
}
}
1、上述代码说明,需要一种方式可以接收任意的泛型类型,但是不可以修改,只能取出。就可以使用?
来描述
public class Demo {
public static void main(String[] args) throws Exception {
Message<Integer> num = new Message<>();
num.setMsg(100);
fun(num);
}
public static void fun (Message<?> tmp) {
//tmp.setMsg("String"); 报错,无法应用
System.out.println(tmp.getMsg());
}
}
- 在
?
通配符基础上还有两个子通配符:
· ?extends 类:设置泛型上限,可以在声明上和方法参数上使用;
|- ?extends Number:意味着可以设置Number或者Number的子类(Integer,Double等)
· ?super 类:设置泛型下限,方法参数使用;
|-?super String:意味着只能设置String或者它的父类Object.
范例:设置泛型上限
class Message<T extends Number> {
private T msg;
public void setMsg(T msg) {
this.msg = msg;
}
public T getMsg() {
return msg;
}
}
public class Demo {
public static void main(String[] args) {
Message<Integer> mA = new Message<Integer>();
mA.setMsg(100);
fun(mA); // 引用传递
}
public static void fun(Message<? extends Number> temp) {
System.out.println(temp.getMsg());
}
}
范例:将Integer改为String
Message<String> mA = new Message<String>();
此时设置为非Number或其子类,那就会出现语法错误。
范例:设置泛型下限
class Message<T> {
private T msg;
public void setMsg(T msg) {
this.msg = msg;
}
public T getMsg() {
return msg;
}
}
public class Demo {
public static void main(String[] args) {
Message<String> mA = new Message<String>();
mA.setMsg("Hello");
fun(mA); // 引用传递
}
public static void fun(Message<? super String> temp) {
System.out.println(temp.getMsg());
}
}
泛型接口
1、泛型可以在接口上声明,这样的接口称为泛型接口。
范例:定义泛型接口
// 如果是接口在前面加“I”,例如:IMessage;
// 如果是抽象类前面加Abstract,例如:AbstractMessage
// 如果是普通类直接写,例如:Message
interface IMessage<T> { // 设置泛型接口
public void print(T t);
}
2、接口必须定义其相应的子类,定义子类有两种形式:
形式一:在子类继续设置泛型
// 子类也继续使用泛型,接口使用和子类一样的泛型标记
class Message<T> implements IMessage<T> {
public void print(T t) {
System.out.println(t);
}
}
public class Demo {
public static void main(String[] args) {
IMessage<String> msg = new Message<String>();
msg.print("Hello");
}
}
形式二:在子类不设置泛型,但为接口明确定义一个泛型
class Message implements IMessage<String> {
public void print(String t) {
System.out.println(t);
}
}
public class Demo {
public static void main(String[] args) {
IMessage<String> msg = new Message();
msg.print("Hello");
}
}
泛型方法
泛型方法也可以定义在普通类中。
范例:泛型方法定义
public class Demo {
public static void main(String[] args) {
String str = fun("Hello");
System.out.println(str.length());
}
// T的类型由传入的参数类型决定
public static <T> T fun(T t) {
return t;
}
}
总结:
1、泛型解决的是向下转型所带来的安全隐患,其核心是在声明类或接口时不设置参数或属性的类型;
2、“?”可以接收任意的泛型类型,只能取出,不能修改泛型