码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 详解 Java 泛型(Generic)机制


    泛型是什么?

    使用泛型可以指定类型变量,从而让代码可以对不同类型的对象进行重用。以及,还可以让编译器更好的了解类型,从而避免强制类型转换,提升代码的安全性。

    类型变量就是尖括号 <>中的变量,类型变量的命名规范是使用大写字母,例如 E 表示元素类型,K、V 分别表示键和值类型,T 和相邻的 U、S 表示任意类型。当然你也可以起其他的名字,编译器对此并没有强制限制,但是还是按照规范来。

    泛型类

    泛型类(generic class)就是有一个或多个类型变量的类。例如下面的 Pair 类,就有两个类型变量,分别是 T 和 U:

    public class Pair<T, U> {
       
      public T first;
      public U second;
      
      public Pair(T first, U second) {
       
        this.first = first;
        this.second = second;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在使用时,就可以传入任意的类型。例如:

    Pair<Integer, String> pair = new Pair<>(1, "A");
    
    • 1

    思考一下,如果没有泛型的存在,那么当我们要操作不同类型的对象时,要么为每种类型都创建一个类,但是这样就会存在大量的重复代码,不容易维护。要么就直接使用对象的顶级父类 Object 类,但是这样的话在使用时就需要进行强制转换,存在安全问题,即使每次强转前进行判断,也会存在重复代码和遗漏的风险。

    说回泛型类,泛型类的类型变量是定义在类名后面,在整个类中都可以使用定义的类型变量。所以在选择使用泛型类时,泛型变量应该是和类所关联的。如果仅与方法关联,那么可以使用泛型方法。

    泛型方法

    泛型方法的类型参数是定义在方法返回类型的前面的,只在当前方法中可以使用。例如:

    public static <T> T getMiddle(T... a) {
       
      return a[a.length / 2];
    }
    
    • 1
    • 2
    • 3
    • 4

    泛型方法可以在普通类中定义,也可以在泛型类中定义。如果泛型变量仅在方法内会用到,就可以考虑使用泛型方法。

    类型变量的限制

    默认情况下,类型变量可以是任何类型。但是有时候,我们需要限制类型变量的类型。此时可以通过 extends关键字来限制:

    • 限制泛型类型为特定类型或者特定类型的子类

    例如,假设我们有一个 Printer 类,我们只需要这个类打印动物的信息,那么就可以使用 来限制只能接收 Animal 或其实现类:

    public interface Animal {
       
      String getName();
    }
    
    public static class Printer<T extends Animal> {
       
      public void print(T t) {
       
        System.out.println(t.getName());
      }
    }
    
    public static class Dog implements Animal {
       
      @Override
      public String getName() {
       
        return "Woof";
      }
    }
    
    public static class Cat implements Animal {
       
      @Override
      public String getName() {
       
        return "Meow";
      }
    }
    
    public static void main(String[] args) {
       
      Printer<Animal> animalPrinter = new Printer<>();
      animalPrinter.print(new Dog());
      animalPrinter.print(new Cat());
      animalPrinter.print(new People("Abc")); // 如果试图传入一个其他类型,则会编译失败
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    一个类型变量,可以指定多个限制类型。例如:

    public class Printer<T extends Dog & Animal> {
       
      ...
    }
    
    • 1
    • 2
    • 3
    • 4

    上面的限制的意思是,类型变量必须是 Dog 或其子类,并且实现了 Animal 接口。需要注意的是,在指定多个限制的类型时,除了第一个限制类型可以是类以外,其他的限制类型必须是接口类型。这是因为 Java 是不支持多重继承的。

    类型擦除

    由于泛型是在 Java 1.5 才推出的,所以 Java 为了保证在之前版本上的兼容性,泛型实际上在 JVM 中是没有的!

    也就是说,编译器在编译时会擦除掉泛型的参数类型,并提供一个相应的原始类型(raw type),这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除(erased),并替换为其限定类型,没有限定类型则替换为 Object 类型。

    举个例子,如果我们定义了一个泛型类:

    public class Printer<T> {
       
      ...
    }
    
    • 1
    • 2
    • 3
    • 4

    那么当类型擦除后,实际上就变为了 Printer 类型。

    如果指定了限定类型,例如:

    public class Printer<T extends Animal> {
       
      ...
    }
    
    • 1
    • 2
    • 3
    • 4

    那么擦除后,就变为了 Printer 类型。

    如果指定了多个限定的类型,那么擦除后就会是第一个限定的类型。例如:

    public class Printer<T extends Dog & Animal> {
       
      ...
    }
    
    • 1
    • 2
    • 3
    • 4

    擦除后就变为了 Printer类型。

    Java 泛型的很多限制都是由于泛型擦除导致的,下面会详细介绍下泛型的局限性。

    通配符

    通配符(Wildcard)就是 Java 泛型中的问号 ? 。要想知道通配符是要解决什么样的问题,我觉得首先需要知道什么是 Variance(不知道这个正确的译名是什么,如有知道请留言)?

    提到 Variance 必须先提到子类型(subtyping),子类型是面向对象中类型多态的其中一种表现形式,主要用于描述 is-a 这样的关系,例如 S 是 T 的子类型,那么 S is a subtype of T。

    Variance 指的是如何根据组成类型之间的子类型关系,来确定更复杂的类型之间的子类型关系。

    上面这段话比较绕,举个实际的例子来说,Variance 指的就是当子类型在更复杂的场景下,例如 If S is a subtype of T,那 Generic is subtype of Generic 这种关系是否还能成立。

    Variance 分为下面几种形式:

    1. 不变(Invariance):如果 B 是 A 的
  • 相关阅读:
    ASEMI快恢复二极管SF1606参数,SF1606图片,SF1606应用
    RT-thread lts-v3.1.x版本,GD32F450以太网,上电之后有一定概率ping不通问题处理。
    【大数据】Kafka 入门指南
    java计算机毕业设计华夏球迷俱乐部网站设计与实现MyBatis+系统+LW文档+源码+调试部署
    git常用的几个命令
    产品经理岗位进阶的唯一法则
    vue2和vue3的区别,优缺点,vue3新特性
    贪吃蛇(C语言详解)
    在国内CDMP认证认可度如何?
    中缀表达式 - 栈实现综合计算器
  • 原文地址:https://blog.csdn.net/Airsaid/article/details/127752321
    • 最新文章
    • 攻防演习之三天拿下官网站群
      数据安全治理学习——前期安全规划和安全管理体系建设
      企业安全 | 企业内一次钓鱼演练准备过程
      内网渗透测试 | Kerberos协议及其部分攻击手法
      0day的产生 | 不懂代码的"代码审计"
      安装scrcpy-client模块av模块异常,环境问题解决方案
      leetcode hot100【LeetCode 279. 完全平方数】java实现
      OpenWrt下安装Mosquitto
      AnatoMask论文汇总
      【AI日记】24.11.01 LangChain、openai api和github copilot
    • 热门文章
    • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
      奉劝各位学弟学妹们,该打造你的技术影响力了!
      五年了,我在 CSDN 的两个一百万。
      Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
      面试官都震惊,你这网络基础可以啊!
      你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
      心情不好的时候,用 Python 画棵樱花树送给自己吧
      通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
      13 万字 C 语言从入门到精通保姆级教程2021 年版
      10行代码集2000张美女图,Python爬虫120例,再上征途
    Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
    正则表达式工具 cron表达式工具 密码生成工具

    京公网安备 11010502049817号