9.4泛型

利用泛型特征可以方便的避免对象强制转型带来的安全隐患(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()方法声明处必须单独定义泛型标记,此时的泛型类型将由传入的参数类型来决定。

发布了162 篇原创文章 · 获赞 9 · 访问量 3106

猜你喜欢

转载自blog.csdn.net/ll_j_21/article/details/104554555
9.4
今日推荐