回归Java:泛型使用

什么是泛型

  
  
  1. 泛型(Generics )是把类型参数化,运用于类、接口、方法中,可以通过执行泛型类型调用分配一个类型,将用分配的具体类型替换泛型类型。
  2. 然后,所分配的类型将用于限制容器内使用的值,这样就无需进行类型转换,
  3. 还可以在编译时提供更强的类型检查。

  
  
  1. 泛型,即“参数化类型”。
  2. 一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
  3. 那么参数化类型怎么理解呢?
  4. 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,
  5. 此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

为什么需要泛型

  • 首先,我们看下下面这段简短的代码:

  
  
  1. public class GenericTest {
  2. public static void main(String[] args) {
  3. List list = new ArrayList();
  4. list.add("qqyumidi");
  5. list.add("corn");
  6. list.add(100);
  7. for (int i = 0; i < list.size(); i++) {
  8. String name = (String) list.get(i); // 1
  9. System.out.println("name:" + name);
  10. }
  11. }
  12. }

        定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

 在如上的编码过程中,我们发现主要存在两个问题:

   
   
  1. 1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,
  2. 改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
  3. 2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,
  4. 且很容易出现“java.lang.ClassCastException”异常
  5. 那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,
  6. 运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。


泛型有什么用

  
  
  1. 泛型主要有两个好处:
  2. 1)消除显示的强制类型转换,提高代码复用
  3. 2)提供更强的类型检查,避免运行时的ClassCastException


什么时候使用泛型
   
   
  1. 只要类中操作的引用数据类型不确定,就可以定义泛型类。通过使用泛型类,可以省去强制类型转换和类型转化异常的麻烦。



泛型的使用

  
  
  1. 类型参数(又称类型变量)用作占位符,指示在运行时为类分配类型
  2. 根据需要,可能有一个或多个类型参数,并且可以用于整个类。
  3. 根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。
  • 下面列出每个用例的标准类型参数:
   
   
  1. E:元素
  2. K:键
  3. N:数字
  4. T:类型
  5. V:值
  6. SUV 等:多参数情况中的第 234 个类型
  7. ? 表示不确定的java类型(无限制通配符类型)

  • 泛型类
    
    
  1. 当一个类要操作的引用数据类型不确定的时候,可以给该类定义一个形参。
  2. 用到这个类的时候,通过传递类型参数的形式,来确定要操作的具体的对象类型。
  3.         
            
    1. JDK1.5之前,为了提高代码的通用性,通常把类型定义为所有类的父类型:Object
    2. 这样做有两大弊端:
    3. 1. 在具体操作的时候要进行强制类型转换;
    4. 2. 这样还是指定了类型,还是不灵活,对具体类型的方法未知且不安全。

  4.  
  5. 泛型类的格式:在类名后面声明类型变量<E>,泛型类可以有多个类型变量, 如:
            
            
    1. public class MyClass<K, V>

  6. 什么时候使用泛型类?
            
            
    1. 只要类中操作的引用数据类型不确定,就可以定义泛型类。通过使用泛型类,可以省去强制类型转换和类型转化异常的麻烦。


  • 泛型方法
    
    
  1. 泛型方法也是为了提高代码的重用性和程序安全性。
  2. 编程原则:
            
            
    1. 尽量设计泛型方法解决问题,如果设计泛型方法可以取代泛型整个类,应该采用泛型方法。

  3. 泛型方法的格式:
            
            
    1. 类型变量放在修饰符后面和返回类型前面 
    2. 如:
    3. public static <E> E getMax(T... in)
    4. public static <T, U extends DLRspBase> U getContent(String method, T t, Class<U> u)
            throws InstantiationException, IllegalAccessException {}

  • 定义泛型方法语法格式如下:


      

  • 调用泛型方法语法格式如下:


 

  • 说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
  • Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。
  • 为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象
  •  泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。
  • 当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。
  • 为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。


  • 泛型接口
    
    
  1. 将泛型原理用于接口实现中,就是泛型接口。
  2. 泛型接口的格式:泛型接口格式类似于泛型类的格式,接口中的方法的格式类似于泛型方法的格式。
  • 泛型接口例子:
    • MyInterface.java
    
    
  1. public interface MyInteface<T> {
  2. public T read(T t);
  3. }
  • Generic2.java
     
     
  1. public class Generic2 implements MyInterface<String>{
  2. public static void main(String[] args) {
  3. Generic2 g = new Generic2();
  4. System.out.println(g.read("hahaha"));
  5. }
  6. @Override
  7. public String read(String str) {
  8. return str;
  9. }
  10. }


泛型的限定

类型变量的限定
  • 如果在方法前指定了<T>,那么就是说,方法的这个泛型类型变量和类定义时的泛型类型无关,这个特性让泛型方法可以定义在普通类中而不是泛型类中。
  • 我们都知道,泛型中可以限定类型变量必须实现某几个接口或者继承某个类,多个限定类型用&分隔,类必须放在限定列表中所有接口的前面
          
          
    1. import java.io.Serializable;
    2. /**
    3. * ICE
    4. * 2016/10/17 0017 14:12
    5. */
    6. public class Demo {
    7. public static void main(String[] args) {
    8. D<A> d = new D<>();
    9. A a = new A();
    10. d.test1(a);
    11. B b = new B();
    12. d.test1(b);
    13. C c = new C();
    14. d.test1(c);
    15. <span style="white-space:pre"> //test2泛型方法传入的类型是String,不是 D<A> d = new D<>();定义的A类型</span>
    16. d.test2("test");
    17. }
    18. }
    19. class A implements Serializable, Cloneable {
    20. @Override
    21. public String toString() {
    22. return "A{}";
    23. }
    24. }
    25. class B extends A {
    26. @Override
    27. public String toString() {
    28. return "B{}";
    29. }
    30. }
    31. class C extends A {
    32. @Override
    33. public String toString() {
    34. return "C{}";
    35. }
    36. }
    37. class D<T extends A & Serializable & Cloneable> {
    38. public void test1(T t) {
    39. System.out.println(t);
    40. }
    41. public <T> void test2(T t) {
    42. System.out.println(t);
    43. }
    44. }
    输出:
           
           
    1. A{}
    2. B{}
    3. C{}
    4. test

通配符 类型

    
    
  1. 类型通配符一般是使用 ? 代替具体的类型实参
            
            
    1. 通配符“?”同样可以对类型进行限定。可以分为子类型限定、超类型限定和无限定。
    2. 通配符不是类型变量,因此不能在代码中使用"?"作为一种类型。
     
     
  1. <? extends T>:是指 上界通配符 Upper Bounds Wildcards
  2. <? super T>:是指 下界通配符 Lower Bounds Wildcards


子类型限定


       
       
  1. 表示类型的上界,类似泛型的类型变量限定,格式是:? extends X
  2. 作用:主要用来安全地访问数据,可以访问X及其子类型。
               
               
    1. 一个类型变量或通配符可以有多个限定,多个限定用“&”分隔开,且限定中最多有一个类,可以有多个接口;
    2. 如果有类限定,类限定必须放在限定列表的最前面。如:T extends MyClass1 & MyInterface1 & MyInterface2


超类型限定


        
        
  1. 表示类型的下界,格式是:? super X
  2. 特点:
  3. 1、限定为XX的超类型,直至Object类,因为不知道具体是哪个超类型,因此方法返回的类型只能赋给Object
  4. 2、因为X的子类型可以向上转型为X,所以作为方法的参数时,可以传递nullX以及X的子类型
  5. 作用:主要用来安全地写入数据,可以写入X及其子类型。


无限定

        
        
  1. 无限定不等于可以传任何值,相反,作为方法的参数时,只能传递null,作为方法的返回时,只能赋给Object

通配符类型--总结:
         
         
  1. 如果频繁支持读取数据,不要求写数据,使用<? extends T>。即生产者 使用 <? extends T>
  2. 如果频繁支持写入数据,不特别要求读数据,使用<? super T>。即消费者 使用 <? super T>
  3. 如果都需要支持,使用<T>。


泛型擦除
Java的泛型在编译期间,所有的泛型信息都会被擦除掉。
   
   
  1. Class c1 = new ArrayList<Integer>().getClass();
  2. Class c2 = new ArrayList<Long>().getClass();
  3. System.out.println(c1 == c2);

          这就是 Java 泛型的类型擦除造成的,因为不管是 ArrayList<Integer> 还是 ArrayList<Long>,在编译时都会被编译器擦除成了 ArrayList。Java 之所以要避免在创建泛型实例时而创建新的类,从而避免运行时的过度消耗。

   
   
  1. 究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,
  2. 在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,
  3. 也就是说,成功编译过后的class文件中是不包含任何泛型信息的。
  4. 泛型信息不会进入到运行时阶段。
  5. 对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

泛型类型信息

  • 那么,如果我们确实某些场景,如HTTP或RPC或jackson需要获取泛型进行序列化反序列化的时候,需要获取泛型类型信息。
  • 可以参照如下:
          
          
    1. import java.lang.reflect.ParameterizedType;
    2. import java.lang.reflect.Type;
    3. /**
    4. * 获取运行时的泛型类型信息
    5. *
    6. * @author Sven Augustus
    7. */
    8. public class Test2 {
    9. static class ParameterizedTypeReference<T> {
    10. protected final Type type;
    11. public ParameterizedTypeReference() {
    12. Type superClass = this.getClass().getGenericSuperclass();
    13. //if (superClass instanceof Class) {
    14. // throw new IllegalArgumentException(
    15. //"Internal error: TypeReference constructed without actual type information");
    16. // } else {
    17. this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    18. //}
    19. }
    20. public Type getType() {
    21. return type;
    22. }
    23. }
    24. public static void main(String[] args) {
    25. // System.out.println(new ParameterizedTypeReference<String>().getType());
    26. // java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
    27. // 此处会输出报错,因此ParameterizedTypeReference 应不能直接实例化,可以考虑加abstract
    28. System.out.println(new ParameterizedTypeReference<String>() { }.getType());
    29. // ParameterizedTypeReference 的匿名内部类,可以触发super(),
    30. //即 ParameterizedTypeReference()的构造器逻辑,正常运行
    31. }
    32. }

注意一个关键点:

    
    
  1. 可以通过定义类的方式(通常为匿名内部类,因为我们创建这个类只是为了获得泛型信息)在运行时获得泛型参数。


泛型数组 (可能导致类型不安全)
   
   
  1. 注意:Java中没有所谓的泛型数组一说
   
   
  1. List<String>[] lsa = new ArrayList<String>[10]; // error
    
    
  1. 如果可以的话,可能导致类型不安全。如:
  2. Object o = lsa;
  3. Object []oa = (Object[])o;
  4. List<Integer> li = new ArrayList<Integer>();
  5. li.add(new Integer(3));
  6. oa[1] = li;
  7. String s = lsa[1].get(0); // runtime error


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琦彦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值