利用泛型特征可以方便的避免对象强制转型带来的安全隐患(ClassCastException异常)
9.4.1泛型问题引出
在java语言中,为了方便接收参数类型的统一,提供了一个核心类Object,利用此类对象可以接收所有类型数据。但是由于所描述的数据范围过大,所以在实际使用中就会出现传入数据类型错误,从而引发异常,现在设计一个可以描述坐标点的类Point,对于坐标点允许保存三类数据,
-
整型数据:x=10,y=20。
-
浮点型数据:x=10.1,y=20.9.
-
字符串型数据:x=东经210度,y=北纬30度。
在设计point类的时候就需要考虑x和y属性的具体类型,这个类型要求可以保存以上三种数据类型,很明显最为原始的做法就是利用Object类来进行定义。存在以下转换关系。 -
整型数据:基本数据类型–>包装为Integer类对象–>自动向上转型为Object。
-
浮点型数据:基本数据类型–>包装为Double类对象–>自动向上转型为Object。
-
字符串型数据:String类对象–>自动向上转型为Object。
范例:定义Point坐标点类
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;
}
}
Point类中的x与y属性都采用了Object作为存储类型,这样就可以接收任意的数据类型,于是此时就可能产生两种情况。
情况1:使用者按照统一的数据类型设置坐标内容,并且利用向下转型获取坐标原始数据
public class Java214 {
public static void main(String[] args) {
Point ppo=new Point() ;
//第一步,根据需求进行内容的设置,所有数据都通过Object接收
ppo.setX(10); //自动装箱
ppo.setY(20);
//第二步 从里面获取数据由于返回的是Object类型,所以必须进行强制向下转型
int x=(Integer)ppo.getX();
int y=(Integer)ppo.getY();
System.out.println();
}
}
执行结果
x坐标:10、y坐标:20
情况2:使用者没有按照统一的数据类型设置坐标内容,读取数据时使用了错误的类型进行强制转换
package com.lxh.ninechapter;
public class java215 {
public static void main(String[] args) {
Point ppo=new Point() ;
//第一步,根据需求进行内容的设置,所有数据都通过Object接收
ppo.setX(10); //自动装箱
ppo.setY("beiwu");//与X数据类型不统一,但是由于符合标准语法,所以编译时无法发现问题
//第二步 从里面获取数据由于返回的是Object类型,所以必须进行强制向下转型
int x=(Integer)ppo.getX();
int y=(Integer)ppo.getY();
System.out.println();
}
}
执行结果
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at com.lxh.ninechapter.java215.main(java215.java:11)
9.4.2泛型的基本定义
.
如果想要解决项目中出现的ClassCastException问题,核心方案避免强制性的进行对象向下转型操作。所以泛型的设计核心在于:类中的属性或方法的参数与返回值的类型采用动态标记,在对象实例化的时候动态配置要使用的数据类型。
注意:泛型只允许设置引用数据类型。
相当于初类中定义范围很广的类,具体数据类型由来发者在使用的时候定义。
范例:在类定义上使用泛型
public class Point<T> {//T属于类型标记可以设置多个标记时
private T x;
private T y;
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;
}
}
import java.util.List;
public class Java216 {
public static void main(String[] args) {
//实例化Point对象,设置泛型标记T的目标数据类型,属性、方法参数、返回值类型的动态配置。
Point<Integer> oo=new Point<Integer>();
//第一步,根据需求进行内容的设置,所有数据都通过Object接收
oo.setX(10); //自动装箱
oo.setY(20);
//第二步 从里面获取数据由于返回的是Object类型,所以必须进行强制向下转型
int x=oo.getX(); //避免强制向下转型
int y=oo.getY();//避免强制转型
System.out.println("坐标X:"+x+"、坐标y"+y);
}
}
执行结果:
坐标X:10、坐标y20
在本程序中实例化Point类对象时所采用的的泛型类型设置为了Integer,这样一来当前对象中的x、y的属性类型就是Integer.对应的参数和返回值也都是Integer,这样不仅可以在编译的时候明确知道数据类型的错误,也避免了对象向下转型操作。
在泛型类使用中,jdk考虑到最初可开发者的使用习惯,允许开发者在实例化对象时不设置泛型类型,这样在编译时会出现相应的警告信息,同时为了保证不出错,未设置的泛型类型将使用Object作为默认类型。
范例:观察默认类型
public class Java217 {
public static void main(String[] args) {
//实例化Point对象,没有设置泛型类型,编译时出现警告,默认使用Object类型
Point oo=new Point();
//第一步,根据需求进行内容的设置,所有数据都通过Object接收
oo.setX(10); //自动装箱
oo.setY(20);
//第二步 从里面获取数据由于返回的是Object类型,所以必须进行强制向下转型
int x=(Integer)oo.getX(); //避免强制向下转型
int y=(Integer)oo.getY();//避免强制转型
System.out.println("坐标X:"+x+"、坐标y"+y);
}
}
编译时警告信息
注:Java217.java使用了未经检查或不安全的操作
注:有关详细信息,请使用-Xlint:unchecked重新编译。
9.4.3泛型通配符
不同泛型类型的对象之间彼此是无法进行引用传递的
所以在进行泛型类型的引用对象时,为了可以适应所有本类的实例化对象,则可以在接收时使用“?”作为泛型通配符使用,利用?表示的泛型类型只允许从对象中获取,而不允许修改数据。
范例:使用?接收数据
public class Message<T> { //定义泛型类对象
public T count;
public T getCount() {
return count;
}
public void setCount(T count) {
this.count = count;
}
}
public class Java218 {
public static void main(String[] args) {
Message<String> msg=new Message<String>();
msg.setCount("kkkkkk");
fun(msg);
}
private static void fun(Message<?> temp) { //输出信息,只允取不允许修改存放
//如果现在需要接收则会使用Object作为泛型类型,既String str=(String)temp.getCount()
System.out.println(temp.getCount());
}
}
执行结果
kkkkkk
本程序在fun()方法的参数上使用Message<?>接收Message类的引用对象,由于通配符?的作用,所以该方法可以匹配任意的泛型类型.
提问:如果不设置泛型类型或者设置泛型类型为Object可否解决上面问题
回答:泛型需要考虑操作类型的统一性
在泛型概念小括号里面的两个类型属于不同类型,如果用Object,则只能接受Object类型的。
如果在方法中不设置泛型类型,可以解决不同泛型类型的对象传递问题,但是同时也会有新的问题:允许随意修改数据。
范例:观察不设置泛型类型时的方法参数定义
public class Java1219 {
public static void main(String[] args) {
Message<String> msg=new Message<String>();
msg.setCount("kkkkkk");
fun(msg);
}
//不设置泛型类型,,表示可以接收任意的泛型类型对象
//默认泛型类型为Object,但是不同于Message<Object>
private static void fun(Message temp) {
//原始类型为String,现在设置Integer
temp.setCount(10);
System.out.println(temp.getCount());
}
}
执行结果
10
这样无法对修改做出控制,而使用通配符?的泛型只允许取,不允许修改。
泛型上限与下限
通配符“?”除了可以匹配任意的泛型类型外,也可以通过泛型上限和下限的配置实现更加严格的类范围定义:
【类和方法】设置泛型的上限(? extends 类):只能够使用当前类或当前类的子类设置泛型类型;
“? extends Number”:可以设置Number或Number子类(例如:Integer、Double);
【方法】设置泛型的下限(? super 类):只能够设置指定的类或指定类的父类;
“? super String”:只能够设置String或String的父类Object。
范例:设置泛型上限
public class Message<T extends Number> { //定义泛型类对象
public T count;
public T getCount() {
return count;
}
public void setCount(T count) {
this.count = count;
}
}
public class Java219 {
public static void main(String[] args) {
Message<Integer> msg=new Message<Integer>(); //Integer是Number的子类
msg.setCount(10);
fun(msg);
}
private static void fun(Message<? extends Number> t) {
System.out.println(t.getCount());
}
}
执行结果:10
使用了泛型上限的设置,这样可以实例化的Message对象只允许使用Number或其子类作为泛型类型
范例:设置泛型下限
public class Message<T> { //定义泛型类对象
public T count;
public T getCount() {
return count;
}
public void setCount(T count) {
this.count = count;
}
}
public class Java220 {
public static void main(String[] args) {
Message<String> msg=new Message<String>();
msg.setCount("520");
fun(msg);
}
private static void fun(Message<? super String> msg) {
System.out.println(msg.getCount());
}
}
执行结果:520
本程序使用了泛型下限,接收的Message对象的泛型类型只能是String或其父类。
9.4.4泛型接口
泛型除了可以定义在类上,也可以定义在类上,这样的结构称为泛型接口。
范例:定义泛型接口
public interface IMessage<T> {
public void echo(T msg);
}
对于此时的IMessage泛型接口在进行子类定义时就有两种实现方式:在子类中继续声明泛型和子类中为父类设置泛型类型。
范例:定义泛型接口子类,在子类中继续声明泛型类型
public interface IMessage<T> {
public void echo(T msg);
}
public class MessageImol<S> implements IMessage<S> { //子类继续声明泛型类型
@Override
public void echo(S t) { //方法覆写
System.out.println("ll"+t);
}
}
public class JavaA221 {
public static void main(String[] args) {
IMessage<String> msg=new MessageImol<String>();
msg.echo("520");
}
}
执行结果
ll520
本程序定义MessageImol子类时继续声明了一个泛型标记S,并且实例化MessageImol子类对象时设置的泛型类型也会传递到IMessafe接口中。
范例:定义子类,在子类中为IMessage设置泛型类型
public interface IMessage<T> {
public void echo(T msg);
}
public class MessageImpl implements IMessage<String>{
@Override
public void echo(String msg) { //设置泛型类型,类型为String
System.out.println("lxt"+msg);
}
}
public class JavaB221 {
public static void main(String[] args) {
IMessage<String> msg=new MessageImpl();//实例化子类不设置泛型
msg.echo("520");
}
}
执行结果
lxt520
本程序定义MessageImpl子类时没有定义泛型标记,而是为父类接口设置泛型类型为String,所以在覆写echo()方法时参数类型就是String
9.4.5泛型方法
对于泛型,除了可以定义在类上之外,也可以在方法上定义,而在方法上定义泛型的时候,这个方法不一定非要在泛型类中定义。
范例:定义泛型方法
public class Java222 {
public static void main(String[] args) {
Integer num[]=fun(1,2,3);//传入了整数,泛型类型就是Integer
for(int a:num) {
System.out.println(a+"、");
}
}
//定义泛型方法,由于类中没有设置泛型类型,所以需要定义一个泛型标记,泛型的类型就是传递的参数类型
public static <T> T[] fun(T...args) { //可变参数
return args;
}
}
执行结果
1、
2、
3、
由于此时是在一个没有泛型声明的类中定义了泛型方法,所以在fun()方法声明处必须单独定义泛型标记,此时的泛型类型将由传入的参数类型来决定。