zoukankan      html  css  js  c++  java
  • 史上最全java范型讲解

    什么是java的泛型?

    Java泛型是在JDK 5.0中引入的,引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

    泛型基础

    让我们想象一个场景,我们想要在Java中创建一个列表来存储Integer;我们很容易写:

    List list = new LinkedList();
    list.add(new Integer(1)); 
    Integer i = list.iterator().next();

    令人惊讶的是,编译器会抱怨最后一行。它不知道返回什么数据类型。编译器将需要显式强制转换:

    Integer i = (Integer) list.iterator.next();

    没有可以保证列表的返回类型为Integer。此时列表可以容纳任何对象。我们只知道我们通过检查上下文来检索列表。在查看类型时,它只能保证它是一个Object,因此需要进行显式强制转换以确保该类型是安全的。

    这种转换可能很烦人,我们知道此列表中的数据类型是Integer。演员表也使我们的代码混乱。如果程序员在显式转换中犯了错误,则可能导致与类型相关的运行时错误。

    如果程序员可以表达使用特定类型的意图,而编译器可以确保这种类型的正确性,则容易得多。这是泛型背后的核心思想。

    让我们将前面的代码片段的第一行修改为:

    List<Integer> list = new LinkedList<>();

    通过添加包含该类型的菱形运算符<>,我们仅将此列表的特殊化范围缩小为Integer类型,即,我们指定将在列表内保留的类型。编译器可以在编译时强制执行类型。在小型程序中,这看似微不足道,但是,在大型程序中,这可以增加强大的鲁棒性,并使程序更易于阅读。

    泛型方法

    泛型方法是用单个方法声明编写的那些方法,并且可以使用不同类型的参数来调用。编译器将确保使用任何一种类型的正确性。这些是泛型方法的一些属性:

    • 泛型方法在方法声明的返回类型之前具有类型参数(包含该类型的菱形运算符)
    • 类型参数可以是有界的(边界将在本文后面进行解释)
    • 泛型方法可以在方法签名中使用逗号分隔不同类型的参数
    • 通用方法的方法主体就像普通方法一样

    定义将数组转换为列表的通用方法的示例:

    public <T> List<T> fromArrayToList(T[] a) {   
        return Arrays.stream(a).collect(Collectors.toList());
    }

    在前面的例子中,在方法签名意味着该方法将被处理的通用型Ť。即使该方法返回void,这也是必需的。

    如上所述,该方法可以处理一个以上的泛型类型,在这种情况下,必须将所有泛型类型添加到方法签名中,例如,如果我们要修改上述方法以处理类型T和类型G,应该这样写:

    public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) {
        return Arrays.stream(a)
          .map(mapperFunction)
          .collect(Collectors.toList());
    }

    我们传递了一个函数,该函数将具有T类型的元素的数组转换为具有G类型的元素的列表。一个示例是将Integer转换为其String表示形式:

    @Test
    public void givenArrayOfIntegers_thanListOfStringReturnedOK() {
        Integer[] intArray = {1, 2, 3, 4, 5};
        List<String> stringList
          = Generics.fromArrayToList(intArray, Object::toString);
     
        assertThat(stringList, hasItems("1""2""3""4""5"));
    }

    泛型边界

    • 上边界

    当泛型T给定形如 的A类型到A类型任何子类的限制域,可以匹配任何在此限制域中的类型,此种表示叫上边界通配符

    上边界
    上边界

    上边界不能往里存,只能往外取,请看下面例子:

            List<? extends List> lists2 = new ArrayList<>();
            // 编译器正确
            List list1=lists2.get(0);
            // 编译器报错
            lists2.add(new LinkedList());
    • 下边界

    当泛型T给定形如 的A类型到A类型任何父类的限制域,可以匹配任何在此限制域中的类型,此种表示叫下边界通配符。

    下边界
    下边界

    上边界只能往里存,不能往外取,请看下面例子:

            List<? super List> lists = new ArrayList<>();
             // 编译器正确
            lists.add(new ArrayList());
             // 编译器正确
            lists.add(new LinkedList());
            // 编译器报错
            List list = lists.get(0);

    通配符 <? extends A>

    通配符由Java中的问号表示。”,它们用于指代未知类型。通配符在使用泛型时特别有用。

    public static void paintAllBuildings(List<? extends Building> buildings) {
        ...
    }

    泛型擦除

    泛型已添加到Java中,以确保类型安全并确保泛型不会在运行时引起开销,编译器在编译时对泛型应用了一个称为类型擦除的过程。

    类型擦除会删除所有类型参数,并用其边界或对象(如果类型参数是无界的)替换它。因此,编译后的字节码仅包含常规类,接口和方法,从而确保不会产生任何新类型。在编译时,正确的转换也适用于Object类型。

    下面是类型擦除的一个示例:

    public <T> List<T> genericMethod(List<T> list) {
        return list.stream().collect(Collectors.toList());
    }

    使用类型擦除时,无界类型T被Object替换,如下所示:

    // 擦除后的方法只剩下Object
    public List<Object> withErasure(List<Object> list) {
        return list.stream().collect(Collectors.toList());
    }

    // which in practice results in
    public List withErasure(List list) {
        return list.stream().collect(Collectors.toList());
    }

    如果类型是有界的,那么在编译时,该类型将被有界的代替:

    public <T extends Building> void genericMethod(T t) {
        ...
    }

    编译后会改变:

    public void genericMethod(Building t) {
        ...
    }

    END

    泛型是Java语言的强大功能,它使我们的开发工作更加简单,并且不易出错。泛型在编译时强制类型正确,最重要的是,使用泛型是不会给我们的应用程序造成任何额外的开销。

    End

    欢迎关注公众号! 公众号回复:入群 ,扫码加入我们交流群! 扫码关注公众号获取更多学习资料

    阅读更多文章

  • 相关阅读:
    pycharm基本设置
    pycharm破解
    jupyter
    gpu监控收藏
    监控收藏
    k8s 亲和性和反亲和性 以及污点和容忍
    通过服务器 远程管理卡进行装机
    7.11实习培训日志-Git Linux
    ## HashTable和HashMap的区别
    springboot2 -广播式WebSocket
  • 原文地址:https://www.cnblogs.com/1ssqq1lxr/p/14706297.html
Copyright © 2011-2022 走看看