Java泛型

一、什么是泛型

1.背景:

JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。

2.概念:

Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。

3.好处:

类型安全

消除了强制类型的转换

二、泛型类、接口

泛型类

  • 泛型类的定义语法

    • 代码演示

      • /**
         * 
         * @author:西瓜菌
         * @date:2022年2月11日下午3:48:04
         * @Description:泛型类的定义
         * @param <A> 泛型标识----类型形参
         * 		   A  创建对象的时候指定的具体的数据类型
         */
        class Genericity<A>{
        	private A key;
        
        	// 构造方法
        	public Genericity(A key) {
        		super();
        		this.key = key;
        	}
        	
        	// 读取器方法
        	public A getKey() {
        		return key;
        	}
        
        	// 修改器方法
        	public void setKey(A key) {
        		this.key = key;
        	}
        }
        
  • 常用的泛型标识符:T、E、K、V

    • 这些标识符可以随便取,如:SADASLFHAJ……,只是上面的标识符是约定俗成。
  • 使用语法(这里是 java1.8 之后的语法:钻石表达式)

    • image-20220211155352193

    • 代码演示

      • public static void main(String[] args) {
        		Genericity<String> gcity = new Genericity<>("jude");
            	// 泛型类在创建对象的时候,没有指定类型,将按照 Object 类型来操作
            	Genericity gcity2 = new Genericity("tom");
        }
        

从泛型类派生子类

  • 子类也是泛型类,子类和父类的泛型类型要一致

    • image-20220211160734077
  • 子类不是泛型类,父类要明确泛型的数据类型

    • image-20220211160815102

泛型类注意事项

  • 泛型类,如果没有指定具体的数据类型,此时,操作类型是 Object
  • 泛型的类型参数只能是类类型 ( 引用类型 ),不能是基本数据类型
  • 泛型类型在逻辑上(或者说编译时)可以看成是多个不同的类型,但实际上(运行时)都是相同类型( 类型擦除 )

泛型接口

  • 泛型接口的定义语法

    • image-20220211161328490

    • 代码演示

      /**
       * @author:西瓜菌
       * @date:2022年2月11日下午4:14:59
       * @Description:泛型接口的定义语法
       */
      public interface GenricityInter<T,E,D> {
      	T mothed1();
      	E mothed2(D d); // 返回值类型为 D
      }
      
  • 泛型接口的使用

    • 实现累不是泛型类,接口要明确数据类型

      // 接口要明确数据类型
      class CommomClass implements GenricityInter<Integer,String,Double>{
      
      	@Override
      	public Integer mothed1() {
      		// TODO Auto-generated method stub
      		return null;
      	}
      
      	@Override
      	public String mothed2(Double d) {
      		// TODO Auto-generated method stub
      		return null;
      	}
      	
      }
      
    • 实现类也是泛型类,实现类和接口的泛型类型要一致

      // 实现类和接口的泛型类型要一致,GenericityClass<T,E> 少一个类型参数报错 
      class GenericityClass<T,E,D> implements GenricityInter<T,E,D>{
      
      	@Override
      	public T mothed1() {
      		// TODO Auto-generated method stub
      		return null;
      	}
      
      	@Override
      	public E mothed2(D d) {
      		// TODO Auto-generated method stub
      		return null;
      	}
      	
      }
      

三、泛型方法

  • 泛型类,是在实例化类的时候指明泛型的具体类型

  • 泛型方法,是在调用方法的时候指明泛型的具体类型

  • 语法

    • image-20220211163713587

    • 代码演示

      	// 泛型方法
      	public <T,E> void demoMothed1() {
      		
      	}
      	
      	// <T> 中定义几个类型变量就可以使用几个类型变量,demoMothed2 中返回值,参数只能使用 T,demoMothed3 中可以		使用 E
      	public <T> T demoMothed2(T... t) {
      		return null;
      	}
      	
      	public <T,E> E demoMothed3(E... e) {
      		return null;
      	}
      
  • public 与返回值中间 非常重要,可以理解为声明此方法为泛型方法

  • 只有声明了 的方法才是泛型方法

  • 表明该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T

泛型方法与可变参数

  • 语法
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8ZfcNPX-1644593051711)(C:\Users\西瓜菌\AppData\Roaming\Typora\typora-user-images\image-20220211171323158.png)]
    • image-20220211171826567

泛型方法总结

  • 泛型方法能使方法独立于类而产生变化

    • 建议:在工作中尽量使用泛型方法,比泛型类更加灵活
  • 如果 static 方法要使用泛型能力,就必须使其成为泛型方法

    image-20220211170047840

  • 泛型方法调用

    • image-20220211171142791

四、类型通配符

什么是类型通配符?

  • 类型通配符一般是使用 ? 代替具体的类型实参
  • 所以,类型通配符是类型实参,而不是类型形参

类型变量的限定

  • 有时,类或方法需要对类型变量加以约束

  • 语法

    public static <T extends Comparable> void mothod() {
    		
    }
    
  • :表示 T 应该是限定类型 Comparable 的子类型

  • T 和 限定类型可以是类,也可以是接口。

  • 可以有多个限定

    • 一个类型变量或通配符可以有多个限定
    • 类型变量的限定和 java 继承语法差不多。一个类型变量或通配符可以有多个限定,但最多有一个限定是类,可以有多个接口超类型,如果有一个类作为限定,它必须是限定列表中的第一个限定
    • 限定类型用 & ,逗号用来分隔类型变量
    • 代码演示
    class Example{	
    	public static <T,E extends Example & Comparable & Serializable> void mothod() {
    		
    	}
    }
    
  • 例子

    • // 获得数组中最小的值
        	public static <T> T mothod(T[] arr) {
        		if(arr.length == 0 || arr == null) {
        			return null;
        		}
        		T min = arr[0];
        		for(int i = 1; i < arr.length; i++) {
        			if(min.compareTo(arr[i]) > 0) {
        				min = arr[i];
        			}
        		}
        		return min;
        	}
      
      • 注意变量 min 的类型为 T,就是说它可以是任何一个类的对象,那么怎么知道这个对象是否有 compareTo( ) 方法呢?如果这个对象没有 compareTo( ) 方法,那代码 min.compareTo(arr[i]) > 0 将会报错。
      • 可以通过给类型变量 T 设置一个限定 T extends Comparable 来实现这一点

类型通配符的上限界定符

  • 语法

    image-20220211182412984

  • 要求该泛型的类型,只能是实参类型,或实参类型的子类类型

  • 同时上限界定符不能修改值,只能获取值

  • 代码演示

    public class Case {
    	public static void main(String[] args) {
    		ArrayList<Animal> a = new ArrayList>();
    		ArrayList<Cat> c = new ArrayList<>();
    		ArrayList<SmallCat> sc = new ArrayList<>();
    		
    		// showAnimal(a); Animal 不是 Cat 的子类型,编译报错 
    		showAnimal(c);
    		showAnimal(sc);
    		
    	}
    	
    	/**
    	 * 泛型上限通配符,传递的集合类型,只能是 Cat 或 Cat 的子类型
    	 * @param list
    	 */
    	public static void showAnimal(ArrayList<? extends Cat> list) {
    		
    	}
    }
    
    class Animal<T>{
    	
    }
    
    class Cat<T> extends Animal<T>{
    	
    }
    
    class SmallCat<T> extends Cat<T>{
    	
    }
    
  • 图片代码

    image-20220211201948362

类型通配符的下限界定符

  • 语法

    image-20220211202101071

  • 要求该泛型的类型,只能是实参类型,或实参类型的父类型

  • 下限界定符可以添加元素。也可以获取值,但是获取的值只能赋给 Object 对象

  • 代码演示

    public class Case {
    	public static void main(String[] args) {
    		ArrayList<Animal> a = new ArrayList<>();
    		ArrayList<Cat> c = new ArrayList<>();
    		ArrayList<SmallCat> sc = new ArrayList<>();
    		
    		showAnimal(a);
    		showAnimal(c);
    		// showAnimal(sc); 报错 SmallCat 不是 Cat 的父类型
    		
    	}
    	
    	/**
    	 * 泛型上限通配符,传递的集合类型,只能是 Cat 或 Cat 的子类型
    	 * @param list
    	 */
    	public static void showAnimal(ArrayList<? super Cat> list) {
    		list.add(new Cat());
    		// 可以获取元素,但是只能赋给 Object 类型的对象
    		Animal am = list.get(0);
    		Object obj = list.get(0);
    	}
    }
    
  • 图片代码

    image-20220211202705740

无界限定符

  • 语法

    image-20220211203939237

  • getValue 的返回值只能赋给一个 Object

  • setValue 方法不能被调用(**注意:**可以调用 setValue(null))

  • 代码演示

    /**
     * 
     */
    package genericity;
    
    /**
     * @author:西瓜菌
     * @date:2022年2月11日下午8:29:51
     * @Description:
     */
    public class Case2 {
    	public static void main(String[] args) {
    		Test<String> test = new Test<>();
    		method(test);
    	}
    	
    	public static void method(Test<?> t) {
    		// t.setValue(new Object()); 报错无法调用 set()方法
    		t.setValue(null);
    		// String str = t.getValue(); 报错只有 Object 类型的对象才可以接受 get() 的返回值
    		Object obj = t.getValue();
    		System.out.println(str);
    	}
    }
    
    
    class Test<T>{
    	T value;
    
    	public T getValue() {
    		return value;
    	}
    
    	public void setValue(T value) {
    		this.value = value;
    	}
    }
    
  • 图片演示

    image-20220211204536423

总结

看完了三种通配符的使用,我们来做个总结:

  • <? extends T> 子类型限定通配符 无法向其中设置值,但是可以进行正常的取出
  • <? super T> 父类型限定通配符 可以设置 T 类型及其子类型的对象,但是取出的时候只能赋值给 Object
  • <?> 无限定通配符 无法向其中设置值,取值的时候也只能赋值给 Object

从上面的总结可以看出,<? extends T> 通配符偏向于内容的获取,而 <? super T> 通配符更偏向于内容的存入。

**PECS 原则(Producer Extends Consumer Super)**很好的解释了这两种通配符的使用场景:

  • Producer Extends 说的是当你的情景是生产者类型,需要获取资源以供生产时,建议使用 extends 通配符,因为使用了 extends 通配符的类型更适合获取资源。
  • Consumer Super 说的是当你的场景是消费者类型,需要存入资源以供消费时,建议使用 super 通配符,因为使用 super 通配符的类型更适合存入资源。

当然,如果你既想设置值又想取出值,那么就不适合使用通配符了。

五、类型擦除

  • 泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为–类型擦除。

  • 无论何时定义一个泛型类型,都会自动提供一个原始类型。这个原始类型就是类型擦除后的类型,如下图的 Object,Number

  • 有限制类型擦除:类型变量会被擦除,并替换为其限定类型(有多个限定类型时,原始类型会用第一个限定来替换类型变量)

    image-20220211205857709

    • 代码演示,上界限定符只能限定最后一个类型变量,网上查资料也没找到。如果有人知道可以通过文章结尾的联系方式联系告知于我,万分感谢。

      image-20220211215006809

  • 无限制类型擦除:没有给定限定就替换为 Object。

    image-20220211205954278\

    • 代码演示

      image-20220211214056867

  • 擦除方法中类型定义的参数

    image-20220211210401677

    • 代码演示

      image-20220211220519785

  • 桥接方法

    image-20220211221729261

    • 代码演示:只定义了一个方法,却获取到了两个方法

      image-20220211221748529

六、泛型与数组

  • 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象

    • 代码演示

      ArrayList<String> strList = new ArrayList();
      		strList.add("as");
      		// ArrayList<String> list = new ArrayList<String>[]; 报错 不能直接创建带泛型的数组
      		// 可以
      		ArrayList<String>[] list = new ArrayList[5];
      		list[0] = strList;
      
  • 可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组

    • 代码演示

      public class Case6 {
      	public static void main(String[] args) {
      		Fruit<String> fr = new Fruit(String.class,3);
      	}
      }
      class Fruit<T> {
          private T[] array;
      
          public Fruit(Class<T> clz, int length){
              //通过Array.newInstance创建泛型数组
              array = (T[])Array.newInstance(clz, length);
              System.out.println(array.length);
          }
      }
      
      // 输出数组长度 3
      

七、泛型和反射

反射常用的泛型类

  • Class

  • Constructor

  • 代码演示

    /**
     * 
     */
    package genericity;
    
    import java.lang.reflect.Constructor;
    
    /**
     * @author:西瓜菌
     * @date:2022年2月11日下午11:07:46
     * @Description:
     */
    public class Person<T> {
    	private T name;
    
    	public Person(T name) {
    		super();
    		this.name = name;
    	}
    
    	public T getName() {
    		return name;
    	}
    
    	public void setName(T name) {
    		this.name = name;
    	}
    	
    	
    }
    
    class personDemo {
    	public static void main(String[] args) throws Exception{
    		// 使用反射,会自动获取对象类型 idea
            // 获取字节码文件
    		Class<Person> personClass = Person.class;
            // 获取构造函数
    	    Constructor<Person> constructor = personClass.getConstructor();
            // 通过构造函数创建对象
    	    Person person = constructor.newInstance();
    	    
    	    //不使用反射,不会自动获取类型,还需要自己强转
    	    Class personClass2 = Person.class;
    	    Constructor<Person> constructor2 = personClass.getConstructor();
    	    Object person2 = constructor.newInstance();
    	    Person per = (Person)person2;
    	    
    	}
    }
    
    

八、声明

Java之泛型_Beyondczn的博客-CSDN博客_java之泛型

Java泛型中的通配符,, - 简书 (jianshu.com)

JavaSE强化教程泛型,由点到面的讲解了整个泛型体系。_哔哩哔哩_bilibili

java 核心技术卷一

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值