【第二十二章】泛型
一.泛型问题引出:
泛型从JDK1.5之后追加到Java语言里面的,其主要目的是为了解决ClassCastException的问题,在进行对象的向下转型时,永远都存在有安全隐患。
而Java希望可以通过泛型可以慢慢解决掉此类问题。
泛型的问题引出:
现在假设说定义一个描述X与Y坐标的处理类,并且在这个类之中允许开发者保存三类数据。
· 整型数据:x=10、y=20;
· 浮点型数据:x = 10.1、y=20.9;
· 字符串型数据:x = 东经120度、北纬30度。
于是在设计Point类的时候就要去考虑具体的X和Y属性的类型,这个类型要求可以保存以上三种数据类型,最原始的做法就是利用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 this.x; } public Object getY(){ return this.y; } } public class tsy { public static void main(String args[]) { Point point = new Point(); point.setX("北纬100"); //向下转型 int x = (Integer)point.getX(); System.out.println(x); } }
此程序编译是不会出错,但是运行时会报出ClassCastException问题,所以本程序的设计是存在安全隐患的。而这个安全隐患存在的依据在于使用了Object类型,因为Object可以涵盖的范围太广了,而对于这样的错误如果可以直接出现在编译的过程中,
那我们就可以避免运行时的尴尬。
二.泛型的基本定义
如果想要避免项目中出现“ClassCastException”最好的做法是可以回避掉对象的强制转换,所以在JDK1.5之后提供有泛型的技术
而泛型的本质在于,类中的属性或方法的参数与返回值可以由对象实例化的时候动态决定。
那么此时就需要在类定义的时候明确定义占位符(泛型标记)
范例:观察泛型定义
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 T getX(){ return this.x; } public T getY(){ return this.y; } }
此时Point中的X和Y属性的数据类型并不确定,而是由外部决定。
提示:关于默认的泛型类型:
|-由于泛型是属于JDK1.5之后的产物,但是在这之前已经有不少内置的程序类或者是接口广泛的应用到项目开发之中,于是为了保证这些类追加了泛型后,原始的程序依然可以使用,所以如果不设置泛型类型时,自动将使用Object作为类型,以保证程序的正常进行,但是在编译时会出现以下警告信息:
注: tsy.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
2.泛型:
泛型定义完成后可以在实例化对象的时候进行泛型类型的设置,一旦设置,里面的X和Y的属性类型就与当前对象之间绑定了。
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 T getX(){ return this.x; } public T getY(){ return this.y; } } public class tsy { public static void main(String args[]) { Point<Integer> point = new Point<Integer>(); point.setX(10); int x = point.getX(); System.out.println(x); } }
泛型的使用注意点:
1.泛型之中只允许设置引用类型,如果现在要操作基本类型必须使用包装类(不能用基本的数据类型);
2.从JDK1.7开始,泛型对象实例化可以简化为“Point<Integer> point = new Point<>();“,后面可以省略。
使用泛型可以解决大部分的类对象的强制转换处理,这样的程序才是一个合理的设计。
三.泛型通配符
虽然泛型帮助开发者解决了一系列的对象的强制转换所带来的安全,但是从另外一个角度来讲,泛型也带来了一些新的问题:引用传递处理。
class Message<T> { public T Content; public void setContent(T Content){ this.Content = Content; } public T getContent(){ return this.Content; } } public class tsy { public static void main(String args[]) { Message<String> msg = new Message<>(); msg.setContent("www.mldn.com"); fun(msg); } public static void fun(Message<String> temp){ System.out.println(temp.getContent()); } }
此时最大的问题也就出现了,而问题的关键在于fun()方法上,如果真的去使用泛型,不可能只是一种类型,也就是说fun()可以接受任意种泛型类型的Message,但是这时候该程序只能接受Message<String>类型,
我们需要找到一种方式,即是泛型类型,而且不能修改里面的数据(允许获取),那么就需要通过通配符“?”来解决
范例:使用通配符
class Message<T> { public T Content; public void setContent(T Content){ this.Content = Content; } public T getContent(){ return this.Content; } } public class tsy { public static void main(String args[]) { Message<String> msg = new Message<>(); msg.setContent("www.mldn.com"); fun(msg); } public static void fun(Message<?> temp){ System.out.println(temp.getContent()); } }
此时fun()方法使用了通配符,所以可以接受所有类型。
在“ ?”这个通配符的基础之上还提供有俩类小的通配符:
·?extends 类:设置泛型上限;
|-例如:"? extends Number":表示该泛型类型只允许设置Number或者Number的子类
·? super 类: 设置泛型的下限:
|-例如:"? super String":只能够使用String或其父类。
范例:观察泛型上限配置:
class Message<T extends Number> { public T Content; public void setContent(T Content){ this.Content = Content; } public T getContent(){ return this.Content; } } public class tsy { public static void main(String args[]) { Message<Double> msg = new Message<>(); msg.setContent(5.0); fun(msg); } public static void fun(Message<? extends Number> temp){ System.out.println(temp.getContent()); } }
对于通配符而言是一个重要的概念。
四.泛型接口:
泛型除了可以在类定义之外,也可以在接口使用
范例:定义一个泛型接口
interface IMessage<T> { public String echo(T t); }
对于泛型接口的子类而言,现在就有俩种实现方式。
1.实现方式一:在子类之中继续设置泛型定义
interface IMessage<T> { public String echo(T t); } class MessageImpl<S> implements IMessage<S>{ public String echo(S t){ return "【ECHO】"+t; } } public class tsy { public static void main(String args[]) { IMessage<String> msg = new MessageImpl<String>(); System.out.println(msg.echo("Working")); } }
2.实现方式二:在子类实现父接口的时候直接定义出具体泛型类型
interface IMessage<T> { public String echo(T t); } class MessageImpl implements IMessage<String>{ public String echo(String t){ return "【ECHO】"+t; } } public class tsy { public static void main(String args[]) { IMessage<String> msg = new MessageImpl(); System.out.println(msg.echo("Working")); } }
如果从概念和实现上来讲并不复杂,但是在日后会遇见大量出现有泛型的接口,这个时候一定要清楚俩种实现原则。
五.泛型方法:
在之前的程序类里面实际上已经可以发现在泛型类之中如果将泛型标记卸载了方法上,那么这样的方法就被成为泛型方法,泛型方法不一定非要出现在泛型类之中,即:如果一个类没有定义泛型,也可以使用泛型方法。
public class tsy { public static void main(String args[]) { Integer num[]= fun(1,2,3); //传入了整数 for (int temp:num ){ System.out.println(temp+"、"); } } public static <T> T[] fun(T...args){ return args; } }
在后期进行项目开发的时候,这种泛型方法很常见,以之前的工厂设计为例。
如果此时一个项目有上千个接口,到时候满眼望去都是绝望的身影(上千个功能相同的工厂)。