泛型

泛型的概念

jdk1.5 出现的安全机制,之前不指定类型时,任何类型都能丢入容器,取出集合中的元素时,取出的元素都是object类型,无法直接获知存入时元素的类型,所以在获取时,要转换成某一种类型时会不安全,因而产生了泛型机制。具体而言,泛型为:

  • 编译时,对存入的集合元素类型进行检查,保证集合内元素类型统一,将运行时期可能出现的ClassCastException(存入类型不能强制转化成取出类型时抛出)转到了编译时期(存入时)。
  • 运行时,对取出的集合元素自动强制类型转换为存入时的类型,即不再是Object类型。
  • 基本类型不对应一个类,因而不能用基本类型实例化类型参数,即<>中不能是基本类型,只能是类

泛型的意义

定义时面向的类型任意使用再确定具体类型。

泛型的擦除和补偿

  • 泛型的擦除
    生成的class文件不带泛型

    擦除原因:在编译时期,已经对集合中的元素进行了类型检查,达到了目的,所以不在需要在运行时仍然利用泛型,同时也为了让泛型产生之前“运行的类加载器”仍可以在泛型出现之后使用,从而不用再重写之前的类加载器。

  • 泛型的补偿
    运行时期,JVM在取出集合中元素时,自动通过调用对象的getClass()获取元素的类型并完成类型转换动作。不用使用者显示地强制转换了.

    Pair<Integer> p1 = new Pair<Integer>(1,100);
    System.out.println(Pair.class==p1.getClass()); //输出true
    

泛型类和泛型接口

泛型类和泛型接口的区别可以看做是类和接口的区别。因此,此处主要讲泛型类。

  • 定义: class 类名/接口名 <T,E,……>{}
  • 特点:
    • 泛型类和泛型接口中泛型的确定是在创建实例时,通过构造函数中的<>确定

    • 使用泛型类时,如果没有指定泛型对应的具体类时,默认对应的类型为Object

    • 由于泛型擦除,泛型类并不会生成单独的.class文件

        List<Number> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        System.out.println(list1.getClass() == list2.getClass());//输出true
      
    • instanceof是运行时判断,泛型已经被擦除,不能判断具体泛型,为了避免让程序员误解,不允许后面泛型类的<>里面是具体类型

      if(ex_num instanceof Generic<Number>){  } //报错,后面是Generic<Number>编译报错,前面的ex_num可以是泛型类生成的对象
      if(ex_num instanceof Generic){  }		  //正确
      if(ex_num instanceof Generic<?>){  }      //正确
      
    • 泛型类中的泛型对应的对象,由于不能确定具体类型为何,只能调用Object方法

      class A<T>{
          T t1;
          public void method(){	  
        	  t1.toString(); //t1只能调用Object中的方法
          }
      }
      
    • 在泛型中,泛型类型不同,就认为是互不相关的不同的类
      //参数类型是子类和父类关系,应用在泛型中时,这种关系不再存在
      public void test(List<Object> c){} //定义一个参数为List<Object>的函数
      List<String> strList = new ArrayList<>(); //创建一个List<String>类型的变量
      test(strList);  //编译报错,List<Object>和List<String>是无关的类,没有子父类关系
      

泛型方法

  • 定义格式:修饰符 <T, S,…,Z> 返回值类型 方法名 (形参列表)

    注意:只有在定义时,签名中有<>才是泛型方法,仅仅是使用了类中定义的泛型参数的方法并不是泛型方法。

  • 特点:

    • 泛型声明中的类型形参,独立于类而产生变化。

      函数声明中的类型形参和类中的类型形参被视作不同的泛型类型(即使两者名字一样),为避免误解,两者不要重复

    • 所在的类可以是泛型类,也可以不是泛型类。
    • 可以使用泛型类中定义的类型形参,使用的泛型类的类型形参仍然依赖于泛型类
    • 函数声明中的类型形参是在调用该函数时通过实参确定的,因此形参列表需要使用到函数声明中的泛型类型。形参列表没有用到函数声明中的类型形参的话,不报错,但是会导致类型形参因为不能确定具体类型而无法在函数内部使用。

      因为泛型函数的泛型类型是在调用该函数时才确定,因此泛型函数在被同一个对象调用多次时,每次对应的泛型类型可以不同。

    • 静态方法无法使用泛型类中的泛型参数,想要使用泛型,只能被声明为泛型方法。

      静态方法和对象无关,泛型类中的泛型参数是通过创建对象时调用构造函数实现的,因此,静态方法不能使用泛型类中的泛型参数。

      
      class A<E>{
      	E e;
      	/*关于构造函数*/
      	public A(){}  //正确
      	public A(E e){}  //正确,使用了类中类型参数的构造函数,非泛型方法
      	public <T> A(T e){}  //正确,构造函数也是泛型方法
      	
      	/*关于静态函数*/
      	public static <T> void method1(T t){}//正确,静态函数要使用泛型,必须定义为泛型方法
      	public static void method1(E e){}    //错误,静态方法不能使用类中定义的泛型
      	
      	/*关于泛型函数*/
      	public <E> void method2(E e){
      		this.e = e; //会报错,函数中的泛型E和类中的泛型E被视为不同泛型参数,两个E没有关系,相互独立
      	}
          public <T> void method3(T t1,E e1){ //可以使用泛型类中的泛型E,泛型E的类型依赖于泛型类
         	    E e2 = e1;
          	T t2 = t1;
          }
      	public <T> void method4(){
      	T t; //不报错,
      	System.out.println(t); //报错,函数无参数传入,无法确定T的具体类型,因而t无法和其他变量相互赋值		
      	}		
      	public <T> void method5(T t) { //正确,因为T的类型可以在调用时确定
          System.out.println(t);
          }
      
      	public static void main(String[] args){
      		A<String> a = new A<String>();
      		/*同一个对象,两次调用泛型函数,对应的泛型类型可以不同*/
      		a.method5(2);     //此时,E对应的是String类,T对应的是Integer类
      		a.method5("a");   //此时,E对应的是String类,T对应的也是String类
      	}
      }
      

使用类中泛型的方法

  • 定义: 声明中没有泛型,使用的泛型参数完全依赖于类的方法(即不能自己定义新的参数类型的方法)
    
    class A<E>{
    	E e;
    	
    	public void method1(E e) { //正确,泛型E的类型和具体的对象有关
        System.out.println(e);
        }
    
    	public void method2(T t){ //错误,不是泛型函数,不能使用类中未定义的泛型
    	}
    	
    	public static void main(String[] args){
    		A<String> a = new A<String>(); //对象是String类型
    		a.method1("a");   //只能传入String类型
    	}
    }
    

构造函数使用泛型

  • 定义时: 格式为类名(),而非类名<类型形参>()

  • 使用时: 格式为类名() 或者 类名<>() 或者 类名<类型实参>()
    注意:只有在调用构造函数时的菱形语法<>可以为空,其他时候<>内都不能为空

    菱形语法:创建泛型类对象时,后面构造函数中<>可以为空,其类型由前面的引用类型确定

    public class A<T>
    {
    	public A(){}  //正确
    	public A<T>{} //格式错误
    	public <E> A(E e){}  //构造函数也可以是泛型函数
    	
    	public static void main(String[] args){
    	
    		//调用构造函数public A(){}
    		A<String> a1 = new A<String>(); //正确
    		A<String> a2 = new A<>();       //正确,和上面等同。使用了菱形语法,即Java编译器可以推断出<>中缺失的信息
    		A a2 = new A<String>();         //错误,因为前面 "A a3" 相当于 "A<Object> a3",而A<Object>和A<String>互不兼容
    		A a4 = new A();                 //正确,但不提倡,因为此时类A的泛型对应的是Object,失去了设置泛型的作用	
    		A a5 = new A<>();               //正确,相当于 A<Object> a5 = new A<>();
    		
    		//调用构造函数public <E> A(E e){}
    		A<Integer> a5 = new A<Integer>(123);  //正确	
    		A<Integer> a6 = new A(123);           //正确,和上面等同		
    		A a8 = new A(123);                    //正确,和上面等同
    	}
    }
    

泛型的通配符:? T

  • ?
    • 可以接受任意类型的类型实参,因而可以表示各种泛型的父类
    • 可被取,不可存
    • 只能在<>中使用,不能脱离<>而表示任意类型。即不能是单独的?
    • 不能单独定义泛型中使用,<?>只能使用泛型时使用。除非是泛型限定时可以在定义中用。
    class A<?>{} //编译报错
    
    /*这时候通配符会捕获具体的String类型,但编译器不叫它String,而是起个临时的代号,
    比如”CAP#1“。所以以后再也不能往list里存任何元素,包括String。唯一能存的就是null*/
    List<?> c = new ArrayList<String>(); //表示c可以接受任何List泛型。
    c.add(new String()); 				 //发生编译错误
    c.add(null);        				 //正确,null是所有引用类型的实例
    
  • T
    • 只能接受泛型参数是<T>的类型形参
    • 可以脱离<>,被当做某种类型而操作
    • 既可以在定义泛型时使用,也可以在使用泛型时使用

泛型的限定

和类的继承一样,指定类型形参上限时,至多一个父类上限,可以多个类型接口上限,并且接口上限必须位于类上限之后。
泛型的限定,让本来由于泛型不同而没有关系的类,产生了关系

  • 设定类型通配符的限定
    由于存在 ,程序无法确定这个受限制的通配符的具体类型,所以带有限定的泛型同样不能被改变,只能被使用
    • <? extends 类型实参A & 接口实参1 & 接口实参2……>: 指定接收上限
      只能存不能取,因为不知道取出的元素是什么类型,也就无法取。比如 添加元素 addAll.因为取出都是按照上限类型来运算的,不会出现安全隐患。
    • <? super 类型实参A & 接口实参1 & 接口实参2……>: 指定接收下限。
      可以存,因为最小粒度确定了,可以取,但取出的元素由于不能确定类型只能当做Object。比如比较器。
      因为取出对象的时候要用父类进行接收。
  • 设定类型形参的限定
    • <T extends 实际类型A & 接口实参1 & 接口实参2……>:
    • <T super 实际类型A & 接口实参1 & 接口实参2……>:

泛型类的继承

非泛型类 继承 泛型类

使用泛型类时(除非是<?>作为一个可以接收任何泛型类型的引用的场景),必须确定其泛型类型,因此:

 public class A extends B	        //正确,其中B为带泛型的类,相当于public class A extends B<Object> 
 public class A extends B<String>   //正确,继承时指定父类的泛型参数,子类就不用再写泛型参数,如果写了,那就是子类自己新增加的
 public class A extends B<T>        //错误。父类类型参数如果写出就必须要被确定类型,即要么是实参,要么是可以通过子类类型确定的形参;否则不写

泛型类 继承 泛型类

  • 子类和父类泛型参数一样时,子类和父类泛型参数一定是形参(子类泛型一定是形参,父类泛型中类型和子类一样时,表示相同泛型,因而父类也是形参)
  • 子类和父类泛型参数不一样时,子类泛型参数是形参,父类泛型参数为必须实参(因为根据子类泛型参数推不出父类泛型参数)
    public class A<T> extends B<T>     //正确
    public class A<Integer> extends B<Integer> //注意,定义带有泛型的类或接口时,子类泛型参数总是形参,不是实参。这里父类和子类的参数类型Integer 不是java.lang.Integer。它只是一个泛型参数名称 ,恰好相同,跟T是没有区别的
    public class A<T> extends B<String>//正确,父类指定了类型,子类又增加了,这时子类的只是新增加的泛型参数,跟父类没有关系
    public class A<T,S> extends B<T>   //正确
    public class A<T,E extends T> extends B<T> //正确		
    

非泛型类的函数 覆盖 泛型类的函数

由于此时父类泛型参数已经明确,因而子类覆盖父类方法的时候,必须需要用具体的类型

	class Father<T> {
		T info;
		public Father(T info) {
			this.info = info;
		}
		public T get() {
			return info;
		}
		public void set(T info) {
			this.info = info;
		}
	}	 
	class Son extends Father<String> { 
	// 所有从父类继承来的方法的类型参数全部都确定为String了,因此在覆盖的时候都要使用具体的类型实参了!
		public Son(String info) {
			super(info);
		}	 
		@Override
		public String get() {
			return "haha";
		}	 
		@Override
		public String set(String info) {
			return "lala";
		}		
	}

常见问题

泛型和数组的区别

如果Fu是Zi的父类,则 Fu[] 依然是 Zi[] 的父类,但是G<Fu>和G<Zi>之间没有子父类关系(或者说互不兼容)(举例

泛型类 vs 泛型函数

泛型类泛型函数
泛型作用域整个类,不能被static成员使用函数内部,static函数只能是泛型函数
泛型类型确定创建引用或者对象时确定调用函数时确定

空 <?> <Object>区别

class A<T>{
	A a1;			//相当于A a1<Object>; 只不过此时不会像A a2<Object>一样进行安全检查
	A a2<?> ;		//表示可以接受任意类型,如A<String>,但是不能取
	A a2<Object>;   //只接受A<Object>类型的对象,不接受其他类型,如A<String>

参考文献

https://blog.csdn.net/s10461/article/details/53941091 —泛型详解和举例
https://blog.csdn.net/ShierJun/article/details/51253870 —泛型类的继承
https://blog.csdn.net/tounaobun/article/details/8587348 —静态方法的泛型
https://www.jianshu.com/p/fa66f06b701b —使用泛型时的注意事项
https://blog.csdn.net/Lirx_Tech/article/details/51570138 泛型讲解,易理解
https://www.zhihu.com/question/31429113 ? 和 T 的区别
https://www.zhihu.com/question/20400700/answer/117464182 泛型的限定
http://www.voidcn.com/article/p-fvtpxxtk-brb.html 通配符和Object

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值