回归Java:泛型使用

来源:互联网 发布:跨境电商软件供应商 编辑:程序博客网 时间:2024/05/21 19:43

什么是泛型

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

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

  5. 什么时候使用泛型类?
    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


参考来源: http://mp.weixin.qq.com/s/v-h9w6TP99uYFihJ8slklQ
参考来源: http://www.cnblogs.com/lwbqqyumidi/p/3837629.html
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 捷豹钥匙没电了怎么办 车钥匙没电 汽车报警怎么办 东西卡在门锁里怎么办 锁芯里面有东西怎么办 有东西卡在锁里怎么办 锁里面卡了牙签怎么办 塑料卡在锁里怎么办 门锁被牙签堵了怎么办 锁子里面卡东西怎么办 东西卡在锁里怎么办 开车门碰到旁边车门怎么办 美团退款后 物品怎么办 倒车影像是反的怎么办 荒野行动cp版玩着玩着关机怎么办 王者荣耀点击开始游戏就闪退怎么办 电脑遥控游玩ps4画面抖动怎么办 ps4特典不能用了怎么办 苹果x出现的分屏怎么办 电脑注册表文件丢失或损坏怎么办 cad绘图反应很慢怎么办 拍到货商家下架怎么办 电动车头太活了怎么办 快捷方式在根目录找不到了怎么办 神秘海域4卡bug了怎么办 地下城老是闪退怎么办 强制关机后电脑打不开了怎么办 文明5地中海的海军怎么办 文明5被贸易禁运怎么办 文明5海里的食物怎么办 文明5遗址没了怎么办 ⅰpad屏幕动不了怎么办 苹果6plus满了怎么办 cf的fps低怎么办win7 游戏倒闭冲的钱怎么办 一闭眼就做噩梦怎么办 吃鸡游戏上瘾了怎么办 使命召唤7很卡怎么办 w10升级系统卡死怎么办 答题卡写错位置怎么办 高考答错区域该怎么办 荒野行动画面中间有条横怎么办