java泛型机制(基础知识总结篇)

  • 泛型概述

  • 泛型使用的必要性

  • 泛型类

  • 泛型接口

  • 泛型对象引用传递的解决方案

  • 泛型方法

  • 泛型的简单应用
    ---本文中将介绍泛型的基础知识以及简单应用,后面还计划学泛型的擦除机制以及在集合和反射机制中的应用

泛型的概述

泛型,字面上理解就是广泛的数据类型。其实就是将数据类型进行参数化,可以达到进一步的代码复用和使得程序更安全的目的

  • 泛型的基本定义
    泛型的特点:在声明一个变量或者属性的时候不设置其数据类型,而是在指定结构使用的时候进行动态的配置。使用泛型解决了强制类型转化的设计缺陷

泛型的必要性

在生活中信息的发送是并不可少的,但信息表达的方式也是不尽相同的。怎样实现代码复用得接收不同类型的消息显得很重要

不使用泛型的设计

//用于接收不同类型的消息
class Massage{
	private Object content;//用Object接收所以类型的消息
	public Massage(Object content){
		setContent(content);
	}
	public Object getContent(){
		return content;
	}
	public void setContent(Object content){
		this.content=content;
	}
	public void sentMassage(){
		System.out.println(content);
	}
}


public class MassageTest{
	public static void main(String[]args){
		Massage ma=new Massage("你今天有什么计划吗?");
		Massage me=new Massage(98.3);
		ma.sentMassage();
		me.sentMassage();
		//String str=(String)me.getContent();//此处ClassCastException(失误操作)
		//str.split();
		
		
	}
}
output:
你今天有什么计划吗?
98.3
//Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
        at MassageTest.main(MassageTest.java:25)

该程序用Object类作为所以类的父类进行接收所以的类型的数据,可以实现相应的功能,没有一点问题

但一般我们得到相应的数据之后,一般会做相应的一些处理,如程序中的注释部分,其中一般会涉及向下转型(用于调用子类特有的方法)向下转型如果我们操作失误,将可能会发生ClassCastException异常更重要的是这种异常为运行时异常,在编译期间不会暴露,而在我们的运行期间才会暴露,这给我们的程序带来了极大的安全隐患

而泛型的使用可以从根源上解决这个问题

为了解决这个问题,我们可以将这个Massage类设计成泛型类 由此我们引入泛型类

泛型类

泛型类在进行对象的实例化的时候,指定该泛型类所携带的泛型的具体类型
格式:
类名称<具体类> 对象名称= new 类名称<具体类>();
在jdk1.7时对该格式进行了简化:
类名称<具体类> 对象名称= new 类名称<>();(好像也称为钻石表达式)

泛型类实现后面同样的代码

//用于接收不同类型的消息
class Massage<T>{//标识为一个泛型类,T该泛型的类型
	private T content;//此处类型由外部指定
	public Massage(T content){//由外部指定
		setContent(content);
	}
	public T getContent(){//由外部指定
		return content;
	}
	public void setContent(T content){//由外部指定
		this.content=content;
	}
	public void sentMassage(){
		System.out.println(content);
	}
}


public class MassageTest{
	public static void main(String[]args){
		Massage<String>ma=new Massage<String>("你今天有什么计划吗?");
		Massage<Double>me=new Massage<Double>(98.3);
		ma.sentMassage();
		me.sentMassage();
	    String str=me.getContent();//此处失误
		str.split("");
		
		
	}
}
编译后:
MassageTest.java:25: 错误: 不兼容的类型: Double无法转换为String
            String str=me.getContent();//此处失误
                                    ^

相信聪明的你已经发现不同了,用泛型类进行实现相同的功能,和普通类实现相比更安全,都是相同的错误,但泛型实现在编译期间就可以泛型,而普通类进行实现需要在运行期间才能够发现(如果开发要求比较严格,这种有警告信息的代码一般不允许发布)

注意:

  • 泛型的类型只能用类,基本数据类型需要使用对应的包装类,有自动拆箱和装箱的加持,也并不复杂
  • 当泛型指定的类型和传入的类型不一致时,程序将会在编译期间报错
  • 当在实例化对象的时候没有指定泛型的类型 即Massage ma=new Massage()时,为了保证程序不出错,java将会把泛型类型默认为Object,但在编译期间将会出现安全警告,但不影响运行
//用于接收不同类型的消息
class Massage<T>{//标识为一个泛型类,T该泛型的类型
	private T content;//此处类型由外部指定
	public Massage(T content){//由外部指定
		setContent(content);
	}
	public T getContent(){//由外部指定
		return content;
	}
	public void setContent(T content){//由外部指定
		this.content=content;
	}
	public void sentMassage(){
		System.out.println(content);
	}
}


public class MassageTest{
	public static void main(String[]args){
		Massage ma=new Massage("你今天有什么计划吗?");//没有指定泛型类型
		Massage me=new Massage (98.3);//没有指定泛型类型
		ma.sentMassage();
		me.sentMassage();
	    
		
		
	}
}
编译时:
C:\Users\SWT\Desktop\java语言疑问测试代码>javac MassageTest.java
注: MassageTest.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

output:
你今天有什么计划吗?
98.3

多元泛型

上面的程序我们都只运用了一个泛型类型,其实我们可以设置多元泛型
举例说明:

//验证多元泛型的使用
class Tools<K,V>{
	
	private K key;
	private V value;
	public Tools(K key,V value){
		setKey(key);
		setValue(value);
	}
	public K getKey(){
		return this.key;
	}
	
	public void setKey(K key){
		this.key=key;
	}
	public V getValue(){
		return this.value;
	}
	public void setValue(V value){
		this.value=value;
	}
}

public class ToolsTest{
	public static void main(String[]args){
		Tools<String,Double>to=new Tools<String,Double>("汤姆猫",34.5);
        System.out.println("名称:"+to.getKey());
        System.out.println("价值:"+to.getValue());		
	}
}
output:
名称:汤姆猫
价值:34.5

泛型接口

必知:实现的接口的泛型类型需要和其子类的泛型类型相同

我们不仅可以定义泛型类,还可以定义泛型接口,泛型接口有2种实现方法

  • 方式一:接口的泛型类型和泛型类一起在实例化时指定
//泛型接口的实现
interface Info<T>{//该步和泛型类意义相同
public T getVar();
	
}

class Infolmpl<T> implements Info<T>{
	private T var;
	public Infolmpl(T var){
		this.setVar(var);
	}
	public void setVar(T var){
		this.var=var;
		
	}
	public T getVar(){
		return this.var;
	}
}


public class FInterfaceTest{
	public static void main(String[]args){
		Info<String> in=new Infolmpl<String>("天才在左,疯子在右");//用子类实例化接口
		System.out.println(in.getVar());
		
		
	}
}
output:
天才在左,疯子在右
  • 方式二:在接口中指定具体的泛型类型(这种方式可能会更常用)
//泛型接口的实现
interface Info<T>{//该步和泛型类意义相同
public T getVar();
	
}

class Infolmpl<T> implements Info<String>{//该处直接指定泛型类型
//指定泛型类型后,后面的定义就用String直接表示就好了
	private String var;
	public Infolmpl(String var){
		this.setVar(var);
	}
	public void setVar(String var){
		this.var=var;
		
	}
	public String getVar(){
		return this.var;
	}
}


public class FInterfaceTest{
	public static void main(String[]args){
		Info<String> in=new Infolmpl<String>("天才在左,疯子在右");//用子类实例化接口
		System.out.println(in.getVar());
		
		
	}
}
output:
天才在左,疯子在右

泛型对象引用传递的解决方案

//验证泛型对象的引用传递
class Massages<T>{
	private T content;
	public Massages(T content){
		this.setContent(content);
	}
	public T getContent(){
		return this.content;
	}
	public void setContent(T content){
		this.content=content;
	}
	public void sentMassages(){
		System.out.println(this.content);
	}
	
}
public class MassagesTest{
	public static void info(Massages<String> ma){//进行引用传递
		ma.sentMassages();
	}
	public static void main(String[]ages){
		Massages<String> me=new Massages<>("同学你又在摸鱼吗?");
		info(me);
	}
}
output:
同学你又在摸鱼吗?

此时可以发现可以成功传递了

但如果我们换成下面一句代码,发现传不过来了

Massages<Integer> me=new Massages<>(12);

MassagesTest.java:24: 错误: 不兼容的类型: Massages无法转换为Massages

可以得出一个我自以为是的推论:

编译器将同一个类的不同泛型的对象的数据类型看成了不同的数据类型
就是Massages和Massages它将会认为了不同的数据类型

下面是验证与推导的过程
**-------------------------------------------------------------------------------------------------------------

为了解决此时泛型对象的引用传递我们想到了以下的3种解决方案

1.我们根据传入泛型的类型,设计多种传参函数进行重载,完成对该类的不同泛型对象的引用传递
2.我们知道Object类是所有类的父类,如果我们将传参函数的泛型设置成Object,就可以接收不同的对象了
3.Massage和Massages编译器认为其类型不同,但本质上将都同属于Massage类,所以可以直接用Massages类进行接收,不进行泛型标识
方案一:

//验证泛型对象的引用传递
class Massages<T>{
	private T content;
	public Massages(T content){
		this.setContent(content);
	}
	public T getContent(){
		return this.content;
	}
	public void setContent(T content){
		this.content=content;
	}
	public void sentMassages(){
		System.out.println(this.content);
	}
	
}
public class MassagesTest{
	//将传参函数进行重载
	public static void info(Massages<String> ma){
		ma.sentMassages();
	}
	public static void info(Massage<Integer>ma){
		ma.sentMassages();
	}
	public static void main(String[]ages){
		Massages<Integer> me=new Massages<>(12);
		Massages<String> ma=new Massages<>("条条大路通罗马");
		info(me);
		info(ma);
	}
}
MassagesTest.java:29: 错误: 对于info(Massages<Integer>), 找不到合适的 方法
                info(me);
                ^
    方法 MassagesTest.info(Massages<String>)不适用
      (参数不匹配; Massages<Integer>无法转换为Massages<String>)
    方法 MassagesTest.info(Massage<Integer>)不适用
      (参数不匹配; Massages<Integer>无法转换为Massage<Integer>)
2 个错误

但好像失败了,编译器不认为这样的2个函数是重载

  • 方案二
//验证泛型对象的引用传递
class Massages<T>{
	private T content;
	public Massages(T content){
		this.setContent(content);
	}
	public T getContent(){
		return this.content;
	}
	public void setContent(T content){
		this.content=content;
	}
	public void sentMassages(){
		System.out.println(this.content);
	}
	
}
public class MassagesTest{
	//将传参函数进行重载
	public static void info(Massages<Object> ma){
	
		ma.sentMassages();
	}
	
	public static void main(String[]ages){
		Massages<Integer> me=new Massages<>(12);
		Massages<String> ma=new Massages<>("条条大路通罗马");
		
		info(me);
		info(ma);
		
	}
}
C:\Users\SWT\Desktop\java语言疑问测试代码>javac MassagesTest.java
MassagesTest.java:29: 错误: 不兼容的类型: Massages<Integer>无法转换为Massages<Object>
                info(me);
                     ^
MassagesTest.java:30: 错误: 不兼容的类型: Massages<String>无法转换为Massages<Object>
                info(ma);

向上转型是子类转型父类,但他们两个本质上都是属于Massages类

  • 方案三
//验证泛型对象的引用传递
class Massages<T>{
	private T content;
	public Massages(T content){
		this.setContent(content);
	}
	public T getContent(){
		return this.content;
	}
	public void setContent(T content){
		this.content=content;
	}
	public void sentMassages(){
		System.out.println(this.content);
	}
	
}
public class MassagesTest{

	public static void info(Massages ma){
	   // ma.setContent(false);
		ma.sentMassages();
	}
	
	public static void main(String[]ages){
		Massages<Integer> me=new Massages<>(12);
		Massages<String> ma=new Massages<>("条条大路通罗马");
		Massages<Double> mm=new Massages<>(12.3);
		info(me);
		info(ma);
		info(mm);
		
	}
}
不加上注释:
output:
12
条条大路通罗马
12.3
加上注释:
编译期间:
注: MassagesTest.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。 
output:
false
false
false

可以发现我们基本已经实现了泛型对象传递的基本方法,但是由加上注释的内容后,我们可以发现我们的方法美中不足。我们传入的内容既然可以被任意修改(我们方法中没有设置泛型,此时默认被Object接收),由此引出这种方式不安全
-------------------------------------------------------------------------------------------------------------
以上是推导过程

由此我们可以推断:我们需要的这种对象引用传递的技术需要具备:
1.
可以传递该类的任意泛型对象**
2.利用该技术传递的对象不能被更改

为了具备以上2种条件,java推出了通配符?

通配符

我们修改成java通配符,然后再次验证上面代码

将传参函数更改成下面:

public static void info(Massages<?> ma){
	    ma.setContent(false);
		ma.sentMassages();
	}
	

编译:
MassagesTest.java:21: 错误: 不兼容的类型: boolean无法转换为CAP#1
可以看出如果想在传参的时候修改对象的内容,将会在编译期间报错

当然,我们有时候并不一定需要传入所以类型的对象,对此我们引入了2个子通配符(也称为上限和下限),用于限制传入对象的类型,可以避免犯错

  • 验证上限
//验证泛型对象的引用传递
class Massages<T extends Number >{
	private T content;
	public Massages(T content){
		this.setContent(content);
	}
	public T getContent(){
		return this.content;
	}
	public void setContent(T content){
		this.content=content;
	}
	public void sentMassages(){
		System.out.println(this.content);
	}
	
}
public class MassagesTest{

	public static void info(Massages<?extends Number> ma){
	 
		ma.sentMassages();
	}
	
	public static void main(String[]ages){
		Massages<Integer> me=new Massages<>(12);
		Massages<String> ma=new Massages<>("条条大路通罗马");
		Massages<Double> mm=new Massages<>(12.3);
		info(me);
		info(ma);
		info(mm);
		
	}
}


  • 验证下限(只能用于方法)
class Massages<T  >{
	private T content;
	public Massages(T content){
		this.setContent(content);
	}
	public T getContent(){
		return this.content;
	}
	public void setContent(T content){
		this.content=content;
	}
	public void sentMassages(){
		System.out.println(this.content);
	}
	
}
public class MassagesTest{

	public static void info(Massages<? super Integer> ma){//只能是Integer类和Integer类的父类
	 
		ma.sentMassages();
	}
	
	public static void main(String[]ages){
		Massages<Integer> me=new Massages<>(12);
		Massages<String> ma=new Massages<>("条条大路通罗马");
		Massages<Double> mm=new Massages<>(12.3);

		info(me);
		info(ma);
		info(mm);
		
	}
}



注意是在调用方法的时候才报错的

泛型方法

泛型方法是独立与泛型类的存在,刚刚泛型类里面的方法不是泛型方法

<<java核心技术>>:调用以泛型方法 String middle=ArrayAlg.getMiddle("John","public"),在大多数情况下泛型方法的调用可以省略,在大多数情况下编译器都可以推断出其泛型类型



以上是泛型方法重要的应用之一,结合反射机制创建泛型类的实例化对象,用这种方法不用每次都new一次,比较灵活

利用泛型方法返回泛型类的实例

//验证泛型方法的使用
class Info<T extends Number>{//限制泛型类型

	private T var;
	public T getVar(){
		return this.var;
	}
	public void setVar(T var){
		this.var=var;
	}	
	
}

public class InfoTest{
	public static<T extends Number> Info<T> fun(T param){//相当于告诉你对象属性的值,让你实例化它
		Info<T>tem=new Info<T>();
		tem.setVar(param);
		return tem;
		
	}
	public static void main(String[]args){
		
		Info<Double>in=fun(23.4);
		System.out.println(in.getVar());
	}
}
	
output:
23.4

泛型的简单应用

在之前的举例中,泛型通常表示的是相同信息的不同表示,有一个特点是此时的泛型标识都是java中内置的类
但在实际的应用中,我们的信息并没有这么简单,所以通常我们需要自己去设计信息类

最终需要回到现实中,实现相同的数据但是分支不同,如:都是信息,但是分为,基本信息 联系方式等。的统一表示

应用概述:
一般一个人可以定义一个信息的属性,但是一个人可能有各种类型的信息,例如:联系方式和基本信息。我们可以自己定义信息类,然后通过泛型来表示

  • 都让信息类实现于同一个接口,用于判断合理的信息类
interface Info{
	
}


//信息类Contact
class Contact implements Info{
	private String address;//地址
	private String telphone;//电话
	private String qq;//qq账号
	public Contact(String address,String telphone,String qq ){
		this.setAddress(address);
		this.setTelphone(telphone);
		this.setQq(qq);
	}
	public String getAddress(){
		return this.address;
	}
	public String getTelphone(){
		return this.telphone;
	}
	public String getQq(){
		return this.qq;
	}
	public void setAddress(String address){
		this.address=address;
	}
	public void setTelphone(String telphone){
		this.telphone=telphone;
	}
	public void setQq(String qq){
		this.qq=qq;
	}
	public String toString(){//覆写toString方法实现信息的输出
	return "地址:"+this.address+" 电话号码:"+this.telphone+" qq账号:"+this.qq;
	}
}	
	//信息类Introduction
	class Introduction implements Info{
		private String name;
		private int age;
		private String sex;
		public Introduction(String name,int age,String sex){
			this.setName(name);
			this.setAge(age);
			this.setSex(sex);
		}
		public String getName(){
			return this.name;
		}
		public int getAge(){
			return this.age;
		}
		public String getSex(){
			return this.sex;
		}
		public void setName(String name){
			this.name=name;
		}
		public void setAge(int age){
			this.age=age;
		}
		public void setSex(String sex){
			this.sex=sex;
			
		}
		
	}
	
class Person<T extends Info>{//控制其产生的泛型类型为Info的子类

	private T infor;
	public Person (T infor){
		this.setInfor(infor);
	}	
	public T getInfor(){
		return infor;
	}
	public void setInfor(T infor){
		this.infor=infor;
	}
	public String toString(){
		return this.infor.toString();//看信息是哪个类型调用的就是那个信息类的toString
	}
}

测试2种信息的输出

//测试类

public class PersonTest{
	public static void main(String[]args){
		Person<Contact>pr=new Person<Contact>(new Contact("湖北省","343434332","34455544"));
		System.out.println(pr.toString());
	}
}
output:
地址:湖北省 电话号码:343434332 qq账号:34455544


//测试类

public class PersonTest{
	public static void main(String[]args){
		Person<Introduction> pr=new Person<Introduction>(new Introduction("张三",18,"男"));
		System.out.println(pr.toString());
	}
}
output:
姓名:张三 性别:男 年龄:18

参考:
https://pdai.tech/
<<java开发实战经典>>

posted @ 2022-11-28 20:37  一往而深,  阅读(53)  评论(0编辑  收藏  举报