回归Java:泛型使用

简介: 回归Java:泛型使用


什么是泛型

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



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


为什么需要泛型

  • 首先,我们看下下面这段简短的代码:
  1. publicclassGenericTest {
  2.    publicstaticvoidmain(String[] args) {
  3.        Listlist=newArrayList();
  4.        list.add("qqyumidi");
  5.        list.add("corn");
  6.        list.add(100);
  7.        for (inti=0; i < list.size(); i++) {
  8.            Stringname= (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. S、U、V 等:多参数情况中的第 234 个类型
  7. ?  表示不确定的java类型(无限制通配符类型)


  • 泛型类

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

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

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


  • 泛型方法

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

  1. 泛型方法的格式:
  1. 类型变量放在修饰符后面和返回类型前面,
  2. 如:
  3. publicstatic <E> E getMax(T... in)

public static <T, U extends DLRspBase> U getContent(String method, T t, Class<U> u)

     throws InstantiationException, IllegalAccessException {}


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

图片.png

     

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

图片.png

 

  • 说明一下,定义泛型方法时,必须在返回值前边加一个<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. publicinterfaceMyInteface<T> {
  2. public T read(T t);
  3. }
  • Generic2.java
  1. publicclassGeneric2implementsMyInterface<String>{
  2. publicstaticvoidmain(String[] args) {
  3. Generic2g=newGeneric2();
  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/17001714:12
  5. */
  6. publicclassDemo {
  7.    publicstaticvoidmain(String[] args) {
  8.        D<A> d = newD<>();
  9.        Aa=newA();
  10.        d.test1(a);
  11.        Bb=newB();
  12.        d.test1(b);
  13.        Cc=newC();
  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. classAimplementsSerializable, Cloneable {
  20.    @Override
  21.    public String toString() {
  22.        return"A{}";
  23.    }
  24. }
  25. classBextendsA {
  26.    @Override
  27.    public String toString() {
  28.        return"B{}";
  29.    }
  30. }
  31. classCextendsA {
  32.    @Override
  33.    public String toString() {
  34.        return"C{}";
  35.    }
  36. }
  37. classD<T extendsA & Serializable & Cloneable> {
  38.    publicvoidtest1(T t) {
  39.        System.out.println(t);
  40.    }
  41.    public <T> voidtest2(T t) {
  42.        System.out.println(t);
  43.    }
  44. }
  • 输出:
  1. A{}
  2. B{}
  3. C{}
  4. test


通配符类型


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

子类型限定

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


超类型限定

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

无限定

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


通配符类型--总结:

  1. 如果频繁支持读取数据,不要求写数据,使用<? extendsT>。即生产者 使用 <? extendsT>
  2. 如果频繁支持写入数据,不特别要求读数据,使用<? super T>。即消费者 使用 <? super T>
  3. 如果都需要支持,使用<T>。



泛型擦除

Java的泛型在编译期间,所有的泛型信息都会被擦除掉。

  1. Classc1=newArrayList<Integer>().getClass();  
  2. Classc2=newArrayList<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. publicclassTest2 {
  9. staticclassParameterizedTypeReference<T> {
  10. protectedfinal Type type;
  11. publicParameterizedTypeReference() {
  12. TypesuperClass=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. publicstaticvoidmain(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(newParameterizedTypeReference<String>() { }.getType());
  29. // ParameterizedTypeReference 的匿名内部类,可以触发super(),
  30. //即 ParameterizedTypeReference()的构造器逻辑,正常运行
  31. }
  32. }

注意一个关键点:

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


泛型数组(可能导致类型不安全)

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



参考来源: http://mp.weixin.qq.com/s/v-h9w6TP99uYFihJ8slklQ

参考来源: http://www.cnblogs.com/lwbqqyumidi/p/3837629.html


目录
相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
78 2
|
24天前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
20 0
[Java]泛型
|
1月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 1
|
1月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
27 5
|
1月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
18 1
|
2月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
37 9
Java——包装类和泛型
|
1月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
20 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
1月前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
16 0
|
2月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。