那些年我们在Java泛型上躺过的枪---万恶的泛型擦除【享学Java】(下)

简介: 那些年我们在Java泛型上躺过的枪---万恶的泛型擦除【享学Java】(下)

附:关于Arrays.asList()使用陷阱、指南


Arrays.asList()在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。但是很多小伙伴对它有点滥用,它的使用还是存在一些坑的,这里借助泛型,稍微总结一下:


// @since  1.2
public class Arrays {
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
    private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
      ...
      // 来自父类  它自己并没有复写
      public void add(int index, E element) {
          throw new UnsupportedOperationException();
      }
      public E remove(int index) {
          throw new UnsupportedOperationException();
      }
      ...
    }
}


这是asList()方法的一个申明,可以看到它接收的也是一个可变参数,这么看来它和我们上面定义的doSomething(T... values)方法何其相似,因此此处我就不再用代码示例了,总结出下面几条结论即可,具体缘由我建议小伙伴一定要做到心知肚明:


  1. 传递的数组必须是对象数组,而不是基本类型。1. 当传入一个源生数据类型数组时,asList真正得到的参数就不是数组的元素了,而是数组对象本身
  2. 使用集合的修改方法:add()、remove()、clear()会抛出异常(因为它本质上还是个数组,不可变)


那么问题来了,如何正确的把数组Array转换为List呢???下面介绍几种常用方案:

方案一:自己for循环实现


代码略


方案二:最简便的方法(推荐)

List list = new ArrayList<>(Arrays.asList("a", "b", "c"))

方案三:使用 Java8 的Stream(推荐)


    public static void main(String[] args) {
        Integer[] myArray = {1, 2, 3};
        List<Integer> myList = Arrays.stream(myArray).collect(Collectors.toList());
        //基本类型也可以实现转换(依赖boxed的装箱操作) //boxed()是IntStream类的方法
        int[] myArray2 = {1, 2, 3};
        myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
        System.out.println(myList); //[1, 2, 3]
    }


方案四:使用 Apache Commons Collections


    public static void main(String[] args) {
        Integer[] myArray = {1, 2, 3};
        List<Integer> list = new ArrayList<>();
        CollectionUtils.addAll(list, myArray);
        System.out.println(list); // 
    }


它的addAll()是个重载方法,数组List都行。它内部其实就是把myArray遍历了一遍而已,因此int或者Integer数组都无所谓~


image.png



注意:java.util.Collections可以没有这个能力把Array转为List,但是它的singleton(T o)等方法也是非常好用的,但也是只读视图哦~


Java泛型经典面试题


下面两个代码片段有问题吗,为什么?
public static void main(String[] args) {
    //Part 1
    List<Object> obj = new ArrayList<Long>();
    obj.add("I love Android!");
    // Part 2
    Object[] objArray = new Long[1];
    objArray[0] = "I love Android!"; //编译通过,运行出错
}


答:上面 Part 1 编译出错,Part 2 编译 OK,运行出错。

原因:因为 List 和 ArrayList 没有继承关系,而 Java 的数组是在运行时类型检查的(这就是为何推荐使用集合,少用数组的原因之一)。


如何把 int 值放入 ArrayList list = new ArrayList(); 的 list 列表中?


本题的考点是泛型擦除。下面介绍两种方案:

// 通过泛型擦除兼容性实现如下:
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    List list1 = list; // 使用无泛型的List重新引用(擦除了泛型)
    list1.add(12);
    System.out.println(list1.get(0));
}
// 通过反射实现如下:
public static void main(String[] args) throws Exception {
    List<String> list = new ArrayList<>();
    Class clazz = list.getClass();
    Method m = clazz.getMethod("add", Object.class);
    m.invoke(list, 100);
}


泛型擦除到底擦除了哪些信息?


这个答案上面已经说过了


既然泛型类型在编译时就被擦除了,那类似 Gson 这种 json 解析框架是如何解析数据到泛型类型 Bean 结构的呢?


本题其实一箭双雕,即考察了对于 Gson 框架是否熟悉又考察了 Java 泛型与反射的关系及泛型的实质。

由于在运行期间无法通过 getClass() 得知 T 的具体类型,所以 Gson 通过借助 TypeToken 类来解决这个问题,使用样例如下:

public static void main(String[] args) throws Exception {
    List<String> list = new ArrayList<String>();
    list.add("java");
    // 序列化
    Type type = new TypeToken<ArrayList<String>>() {}.getType();
    String gStr = new Gson().toJson(list, type);
    System.out.println(gStr); // ["java"]
    // 反序列化
    List<String> gList = new Gson().fromJson(gStr, type);
    System.out.println(gList); // [java]
}


可以看到 TypeToken 的使用非常简单,只用将需要获取类型的泛型类作为 TypeToken 的泛型参数构造一个匿名的子类就可以通过 getType() 方法获取到我们使用的泛型类的泛型参数类型。


这种做法的本质:利用了类上面的泛型是不会“擦除”的特性来做的,所以是new了一个TypeToken的一个匿名子类嘛~


下面程序的输出是什么?为什么?


public static void main(String[] args) throws Exception {
    ParameterizedType type = (ParameterizedType) Bar.class.getGenericSuperclass();
    System.out.println(type.getActualTypeArguments()[0]);
    ParameterizedType fieldType = (ParameterizedType) Foo.class.getField("children").getGenericType();
    System.out.println(fieldType.getActualTypeArguments()[0]);
    ParameterizedType paramType = (ParameterizedType) Foo.class.getMethod("foo", List.class).getGenericParameterTypes()[0];
    System.out.println(paramType.getActualTypeArguments()[0]);
    System.out.println(Foo.class.getTypeParameters()[0].getBounds()[0]);
}
class Foo<T extends CharSequence> {
    public List<Bar> children = new ArrayList<>();
    public List<StringBuilder> foo(List<String> foo) {
        return null;
    }
    public void bar(List<? extends String> param) {
        //empty
    }
}
class Bar extends Foo<String> {
}


答:其运行结果如下。

class java.lang.String

class Demo$Bar

class java.lang.String

interface java.lang.CharSequence


从这个例子可以看出:


  1. 表面结论:泛型类型的每一个类型参数都被保留了,而且在运行期可以通过反射机制获取到
  2. 深层结论:泛型的擦除机制实际上擦除的是除结构化信息外的所有东西(结构化信息指与类结构相关的信息,而不是与程序执行流程有关的,即与类及其字段和方法的类型参数相关的元数据都会被保留下来通过反射获取到)


总结


Java的泛型一直是被吐槽的对象,它确实很渣不好用,容易出错。但是存在即合理,你不能改变它那请好好接受它。

编码的时候遵循一个原则:该写泛型的地方务必写上泛型,能使得你对泛型的理解更加的深刻。这是一个非常良好的编码习惯~


相关文章
|
2天前
|
安全 Java 程序员
Java 泛型
Java 泛型
8 0
|
7天前
|
Java
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识。入坑JAVA因它的面向对象特性、平台无关性、强大的标准库和活跃的社区支持。
30 2
|
12天前
|
安全 Java 编译器
【JAVA】泛型和Object的区别
【JAVA】泛型和Object的区别
|
15天前
|
存储 算法 Java
滚雪球学Java(20):Java泛型与枚举:提升代码灵活性与可读性
【4月更文挑战第9天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
26 1
滚雪球学Java(20):Java泛型与枚举:提升代码灵活性与可读性
|
15天前
|
Java
|
15天前
|
安全 Java 机器人
|
15天前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
18 0
|
2天前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第7天】在Java中,多线程编程是提高应用程序性能和响应能力的关键。本文将深入探讨Java并发编程的核心概念,包括线程安全、同步机制以及性能优化策略。我们将通过实例分析,了解如何避免常见的并发问题,如死锁、竞态条件和资源争用,并学习如何使用Java提供的并发工具来构建高效、可靠的多线程应用。
|
2天前
|
缓存 Java
Java并发编程:深入理解线程池
【5月更文挑战第7天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,以及如何使用Java的Executor框架来创建和管理线程池。此外,我们还将讨论线程池的优点和缺点,以及如何选择合适的线程池大小。最后,我们将通过一个示例来演示如何使用线程池来提高程序的性能。
|
3天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。