目录
学习笔记
泛型是从jdk1.5之后追加到Java语言里面的,其主要目的是为了解决ClassCastException的问题,在进行对象的向下转型时存在安全隐患,而Java希望通过泛型能够慢慢解决此类问题。
泛型的问题引出
现在假设说定义一个描述x与y的处理类,并且在这个类之中允许开发者保留三类数据:
- 整型数据:x=10、y=20;
- 浮点型数据:x=10.1、y=20.9;
- 字符串型的数据:x=东经120度、y=北纬30度;
于是在设计Point类就需要考虑具体的x和y属性的类型,这个类型要求可以保存以上三种数据,很明显使用Object类进行定义的,因为存在如下的转型关系:
整型数据:基本数据类型,被包装为Integer类对象,然后自动向上转型为Object类;
浮点型数据:基本数据类型,被包装为double类对象,然后自动向上转型为Object类;
字符串数据:String类对象,自动向上转型为Object类;
范例:定义Point类
class point{
private Object x;
private Object y;
public void setX(){
this.x = x ;
}
public void setY(){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
而后下面进行内容设置
范例:进行内容的操作
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 JavaDemo{
public static void main(String[] args){
Point point = new Point() ;
// 第一步:根据需求进行内容设置
point.setX(10) ; //自动装箱
point.setY(20) ; //自动装箱
// 第二步:从里面获取数据
int x = (Integer) point.getX() ; // 拆箱
int y = (Integer) point.getY() ;
System.out.println("x:" + x + "、y:" + y) ;
}
}
本程序能解决问题主要的原因是,Object可以接收所有的数据类型,正因为如此这里的代码可能出现严重的安全隐患。
范例:观察错误
public class JavaDemo{
public static void main(String[] args){
Point point = new Point() ;
// 第一步:根据需求进行内容设置
point.setX(10) ; //自动装箱
point.setY("北纬20度") ; //自动装箱
// 第二步:从里面获取数据
int x = (Integer) point.getX() ; // 拆箱
int y = (Integer) point.getY() ;
System.out.println("x:" + x + "、y:" + y) ;
}
}
此时如果从编译的角度讲不会任何的提示错误产生的,而程序执行会出现ClassCastExpection异常,本程序的设计存在安全隐患。原因时使用了Object类型,Object接收的范围太广了,而对于这样的错误如果直接出现在编译的过程中,那么就可以避免运行时的出错。
泛型定义
如果为了避免项目之中出现“ClassCastException”最好的做法是可以直接回避掉对象的强制转换,所以在JDK1.5之后提供有泛型的技术,而泛型的本质在于,类中的属性或方法的参数与返回值的类型,可以由对象的实例化决定。
那么此时就需要在类定义时明确的定义占位符(泛型标记)。
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 this.x ;
}
public T getY(){
return this.y ;
}
}
此时Point类中的x与y的属性并没有确定,而是由外部来决定。
提示:关于默认的泛型类型
- 由于泛型是JDK1.5后的产物,但是之前已经由不少内置的程序类或者接口广泛的应用在了项目开发之中,于是为了保证这些类和接口追加了泛型之后,原始的程序类依然可以使用,所以如果不设置泛型类型时,自动将使用Object作为类型,以保证程序的正常运行,但是在编译的过程中会出现警告信息。
泛型定义完成后可以在实例化对象的时候进行泛型类型的设置,一旦设置后,x与y的属性就与当前对象直接绑定了,
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 this.x ;
}
public T getY(){
return this.y ;
}
}
public class JavaDemo{
public static void main(String[] args){
Point<Integer> point = new Point<Integer>() ;
// 第一步:根据需求进行内容设置
point.setX(10) ; //自动装箱
point.setY(20) ; //自动装箱
// 第二步:从里面获取数据
int x = point.getX() ; // 拆箱
int y = point.getY() ;
System.out.println("x:" + x + "、y:" + y) ;
}
}
现在的程序代码之中,由于Point类设置的泛型类型为Integer,这样所有的对应此类型的属性、变量、方法返回值都将全部替换为Integer(只局限于此对象之中)。这样在进行处理的时候,如果发现设置的内容有错误,则在程序编译的时候会有错误提示,同时也避免了对象向下转型处理(可以避免安全隐患)。
泛型的使用注意点:
- 泛型之中只允许设置引用类型,如果现在要操作基本类型,则必须使用包装类,比如:使用Integer,不用int;
- 从jdk1.7开始,泛型对象实例化可以简化为“Point<Integer> point = new Point<>() ;”
使用泛型可以解决大部分类对象的强制转换处理,这样的程序才是合理的设计。
泛型通配符
虽然泛型帮助解决了一系列的强制转换所带来了的安全隐患,但是泛型也带来了一些问题:引用传递处理。
范例:观察问题的产生
class Message <T> {
private T content ;
public void setContent(T content){
this.content = content ;
}
public T getContent(){
return this.content ;
}
}
public class JavaDemo{
public static void main(String[] args){
Message<String> msg = new Message<>() ;
msg.setContent("操作系统") ;
fun(msg) ;
}
public static void fun(Message<String> temp){
System.out.println(temp.getContent()) ;
}
}
但是这个时候问题出现了,出现在方法fun()上,如果真去使用泛型不可能只是一种类型,fun方法可以接收任意种泛型类型的message对象。但是这个时候只能够接收Message<String>类型,这个时候有人提出不设置泛型,代码如下:
范例:不设置泛型
public class JavaDemo{
public static void main(String[] args){
Message<String> msgA = new Message<>() ;
msgA.setContent("操作系统") ;
fun(msgA) ;
Message<Integer> msgB = new Message<>() ;
msgB.setContent(110) ;
fun(msgB) ;
}
public static void fun(Message temp){
System.out.println(temp.getContent()) ;
}
}
public class JavaDemo{
public static void main(String[] args){
Message<String> msgA = new Message<>() ;
msgA.setContent("操作系统") ;
fun(msgA) ;
Message<Integer> msgB = new Message<>() ;
msgB.setContent(110) ;
fun(msgB) ;
}
public static void fun(Message temp){
temp.setContent(1.1) ;
System.out.println(temp.getContent()) ;
}
}
这个时候发现如果不设置泛型,就会发现在方法之中就有可能对你的数据进行修改,所以此时需要找一种方案:可以接收所有的泛型类型并且不能够修改里面的数据(允许获取),那么就需要通过通配符“?”来解决。
public class JavaDemo{
public static void main(String[] args){
Message<String> msgA = new Message<>() ;
msgA.setContent("操作系统") ;
fun(msgA) ;
Message<Integer> msgB = new Message<>() ;
msgB.setContent(110) ;
fun(msgB) ;
}
public static void fun(Message<?> temp){
System.out.println(temp.getContent()) ;
}
}
此时在fun()方法里由于采用了Message和通配符“?”的处理,所以可以接收所有的类型,并且不允许修改,只允许获取数据。
在“?”通配符的基础之上还提供两类小的通配符:
? extends 类 指的是设置泛型的上限;
|- 例如:定义:“? extends Number ”: 表示该泛型类型允许设置Number或Number的子类
? super 类 指的是设置泛型的下限;
|- 例如:定义:“? super String”:表示只能够使用String或String的父类
范例:观察泛型的上限设置
class Message <T extends Number> {
private T content ;
public void setContent(T content){
this.content = content ;
}
public T getContent(){
return this.content ;
}
}
public class JavaDemo{
public static void main(String[] args){
Message<Integer> msgB = new Message<>() ;
msgB.setContent(110) ;
fun(msgB) ; // 引用传递
}
public static void fun(Message<? extends Number> temp){
System.out.println(temp.getContent()) ;
}
}
范例:设置泛型下限
class Message <T> {
private T content ;
public void setContent(T content){
this.content = content ;
}
public T getContent(){
return this.content ;
}
}
public class JavaDemo{
public static void main(String[] args){
Message<String> msgB = new Message<>() ;
msgB.setContent("测试") ;
fun(msgB) ; // 引用传递
}
public static void fun(Message<? super String> temp){
System.out.println(temp.getContent()) ;
}
}
注意下限只需在fun中使用。
对于通配符而言是一个重要的概念,并且要求你一定要可以理解此概念的定义,在日后学习一些Java类库的时候,会见到大量的通配符的使用。
泛型接口
泛型除了在类上使用之外,也可以在接口中使用。
范例:定义一个泛型接口
interface IMessage<T>{
public abstract String echo(T t) ;
}
对于泛型接口的子类而言,就有两种实现方式。
实现方式一:在子类中继续设置泛型定义,
interface IMessage<T>{
public abstract String echo(T t) ;
}
class MessageImpl<S> implements IMessage<S>{
public String echo(S t){
return "【ECHO】" + t ;
}
}
public class JavaDemo{
public static void main(String[] args){
IMessage<String> msg = new MessageImpl<String>() ;
System.out.println(msg.echo("baidu.cn")) ;
}
}
实现方式二:在子类实现父接口的时候直接定义处具体的泛型类型
interface IMessage<T>{
public abstract String echo(T t) ;
}
class MessageImpl implements IMessage<String> {
public String echo(String t){
return "【ECHO】" + t ;
}
}
public class JavaDemo{
public static void main(String[] args){
IMessage<String> msg = new MessageImpl() ;
System.out.println(msg.echo("baidu.cn")) ;
}
}
如果从概念和实现上讲并不复杂,但是在日后会大量出现有泛型的接口,这个时候一定要清楚两种实现原则。
这部分需要注意,格式定义。
泛型方法
在之前的程序类里面,实际上已经可以发现在泛型类之中如果将标记写在方法上的情况,那么这样的方法就被称为泛型方法,但是需要注意的是,泛型方法不一定需要出现在泛型类中。即如果一个类上没有定义泛型也可以使用泛型方法。
public class JavaDemo{
public static void main(String[] args){
Integer num [] = fun(1,2,3) ; // 传入了整数,则泛型类型就是Integer
for (int temp : num){
System.out.print(temp + "、") ;
}
}
public static <T> T[] fun(T ... args){
return args ;
}
}
在后期进行项目开发的时候,这种泛型方法很常见,以之前的工厂设计模式为例。
如果一个项目有上千个接口,就有问题
范例:利用泛型改进工厂
暂时空