Java泛型 自限定类型(Self-Bound Types)详解

2023-11-15

简介

java泛型里会有class SelfBounded<T extends SelfBounded<T>> { }这种写法,泛型类有一个类型参数T,但这个T有边界SelfBounded<T>。这边界里有两个疑问:

  1. SelfBounded已经在左边出现,但SelfBounded类还没定义完这里就用了;
  2. 同样,T也在左边出现过了,这是该泛型类的类型参数标识符。

这两点确实挺让人疑惑,思考这个类定义时容易陷入“死循环”。
注意,自限定的要点实际就是这两个“疑问”。

普通泛型类——构成自限定

class BasicHolder<T> {
    T element;
    void set(T arg) { element = arg; }
    T get() { return element; }
    void f() {
        System.out.println(element.getClass().getSimpleName());
    }
}

class Subtype extends BasicHolder<Subtype> {}

public class CRGWithBasicHolder {
    public static void main(String[] args) {
        Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();
        st1.set(st2);
        st2.set(st3);
        Subtype st4 = st1.get().get();
        st1.f();
    }
} /* Output:
Subtype
*///:~
  • BasicHolder<T>泛型类的类型参数并没有什么边界,在继承它的时候,你本可以class A extends BasicHolder<B> {}这样普普通通的用。
  • class Subtype extends BasicHolder<Subtype> {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。
  • 虽然class Subtype extends BasicHolder<Subtype> {}这样用,看起来是类定义还没有结束,就把自己的名字用到了边界的泛型类的类型参数。虽然感觉稍微有点不合理,但这里就强行理解一下吧。自限定的用法:父类作为一个泛型类或泛型接口,用子类的名字作为其类型参数
  • 以上两点,就解释了简介里的第二个“疑问”。正因为class Subtype extends BasicHolder<Subtype>这样用可以让Subtype对象只允许和Subtype对象交互,这里再把Subtype抽象成类型参数T,不就刚好变成了T extends SelfBounded<T>这样的写法。
  • 在主函数里,根据自限定的重要作用,且由于BasicHolder<T>泛型类有个成员变量和set方法,所以st1.set(st2); st2.set(st3);可以像链表一样,节点的后继指向一个节点,后者又可以指向另外的节点。

自限定类型的泛型类

下面是自限定类型的标准用法。

class SelfBounded<T extends SelfBounded<T>> {//自限定类型的标准用法
    //所有
    T element;
    SelfBounded<T> set(T arg) {
        element = arg;
        return this;
    }
    T get() { return element; }
}

class A extends SelfBounded<A> {}

public class SelfBounding {
    public static void main(String[] args) {
        A a = new A();//a变量只能与A类型变量交互,这就是自限定的妙处
        SelfBounded<A> b =  new SelfBounded<A>();
    }
} ///:~
  • 抛开自限定类型的知识点,观察SelfBounded泛型类,发现该泛型类的类型参数T有SelfBounded<T>的边界要求。根据上个章节的讲解,一个普通的泛型类我们都可以继承它来做到自限定,且因为要使用SelfBounded泛型类之前,我们必须有一个实际类型能符合SelfBounded<T>的边界要求,所以这里就模仿上一章,创建一个新类来符合这个边界,即class A extends SelfBounded<A> {},这样新类A便符合了SelfBounded<T>的边界。
  • 这时你觉得终于可以使用SelfBounded泛型类了,于是你便SelfBounded<A> b = new SelfBounded<A>();,但是这个b变量本身的类型是SelfBounded<A>,成员函数的形参或返回值的类型却是A,这个效果看起来不是我们想要的自限定的效果。(b变量不可以和别的SelfBounded<A>对象交互,因为它继承来的成员函数的类型限定是A,这样把别的SelfBounded<A>对象传给成员函数会造成ClassCastException,这属于父类对象传给子类引用,肯定不可以。所以说没有达到自限定。)
  • 其实,这里是我们多此一举了,新类class A extends SelfBounded<A> {}创建的时候就已经一举三得了。1.出现SelfBounded尖括号里面的A需要满足边界SelfBounded<T>,它自己的类定义已经满足了。2.给了SelfBounded泛型类的定义是为了使用它,新类A的对象也能使用到它,只不过这里是继承使用。3.根据上一章的讲解,新类A的类定义形成了自限定。
  • 可能一般我们以为要使用SelfBounded泛型类要有两步(1.创建新类型以符合边界 2.以刚创建的新类型的名字来创建SelfBounded泛型类对象),但由于class SelfBounded<T extends SelfBounded<T>>类定义中,SelfBounded作为了自己的泛型类型参数的边界,这样,想创建一个新类作为T类型参数以符合边界时,这个新类就必须继承到SelfBounded的所有成员(这也是我们想要的效果)。所以就可以class A extends SelfBounded<A> {}这样一步到位。这也解释了简介里的第一个“疑问”。

对了,对于第一个疑问,你可能想看一下,如果边界里的泛型类不是自己,会是什么情况:

class testSelf<T> {
    //假设这里也有一些成员变量,成员方法
}

class SelfBounded<T extends testSelf<T>> {//类型参数的边界不是自己的名字SelfBounded
    T element;
    SelfBounded<T> set(T arg) {
        element = arg;
        return this;
    }
    T get() { return element; }
}

class testA extends testSelf<testA> {}//这个新类可作为SelfBounded的类型参数T,因为符合了边界

public class SelfBounding {
    public static void main(String[] args) {
        SelfBounded<testA> a = new SelfBounded<testA>();
    }
} ///:~

按照一般使用SelfBounded泛型类的两个步骤,首先需要创建新类class testA extends testSelf<testA> {}来符合边界,然后新类型作为类型参数使用来创建SelfBounded对象,即SelfBounded<testA> a = new SelfBounded<testA>()。但a变量的效果却不是我们想要的自限定的效果,总之看起来很奇怪。
一旦你把class SelfBounded<T extends testSelf<T>>的边界改成<T extends SelfBounded<T>>,那么新类testA,那么它的定义就应该是class testA extends SelfBounded<testA> {},然后正因为testA继承了SelfBounded<testA>,所以testA就获得了父类SelfBounded的成员方法且这些成员方法的形参或返回值都是testA。
通过这个反例便进一步解释了简介的第一个“疑问”。

对本章第一个例子作进一步的拓展吧:

class SelfBounded<T extends SelfBounded<T>> {
    T element;
    SelfBounded<T> set(T arg) {
        element = arg;
        return this;
    }
    T get() { return element; }
}

class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // Also OK

class C extends SelfBounded<C> {
    C setAndGet(C arg) { set(arg); return get(); }
}

class D {}
// Can't do this:
// class E extends SelfBounded<D> {}
// Compile error: Type parameter D is not within its bound

// Alas, you can do this, so you can't force the idiom:
class F extends SelfBounded {}

public class SelfBounding {
    public static void main(String[] args) {
        A a = new A();
        a.set(new A());
        a = a.set(new A()).get();
        a = a.get();//最终a是null
        C c = new C();
        c = c.setAndGet(new C());//c换成这行新new出来的C对象了
    }
} ///:~
  • class B extends SelfBounded<A>这样使用也是可以的,毕竟继承SelfBounded时,给定的具体类型A确实满足了边界。不过B对象没有自限定的效果了。
  • class C extends SelfBounded<C>展示了:在自己新增的成员方法里,去调用继承来的成员方法。注意,继承来的方法被限定类型为C即自身了,这就是自限定的效果。
  • class E extends SelfBounded<D>无法通过编译,因为给定的具体类型A不符合边界。
  • class F extends SelfBounded,你可以继承原生类型,此时T会作为它的上限SelfBounded(边界)来执行。如下图:
    在这里插入图片描述

也可以将自限定用于泛型方法:

//借用之前定义好的SelfBounded
class testNoBoundary<T> {}//我自己新加的

public class SelfBoundingMethods {
    static <T extends SelfBounded<T>> T f(T arg) {
        return arg.set(arg).get();
    }

    static <T extends testNoBoundary<T>> T f1(T arg) {
        return arg;
    }
    public static void main(String[] args) {
        A a = f(new A());

        class selfBound extends testNoBoundary<selfBound> {}
        selfBound b = f1(new selfBound());
    }
} ///:~
  • f静态方法要求T自限定,且边界是SelfBounded。那么之前定义的A类型就符合要求了。
  • 我加了个f1静态方法,它也要求T自限定,且边界是testNoBoundary。注意testNoBoundary泛型类对类型参数T没有边界要求。class selfBound extends testNoBoundary<selfBound> {}这里用了局部内部类创建了一个符合边界要求的新类型。

JDK源码里自限定的应用——enum

java中使用enum关键字来创建枚举类,实际创建出来的枚举类都继承了java.lang.Enum。也正因为这样,所以enum不能再继承别的类了。其实enum就是java的一个语法糖,编译器在背后帮我们继承了java.lang.Enum。

下面就是一个枚举类的使用:

public enum WeekDay {
    Mon("Monday"), Tue("Tuesday"), Wed("Wednesday"), Thu("Thursday"), Fri( "Friday"), Sat("Saturday"), Sun("Sunday");
    private final String day;
    private WeekDay(String day) {
        this.day = day;
    }
    public static void printDay(int i){
        switch(i){
            case 1: System.out.println(WeekDay.Mon); break;
            case 2: System.out.println(WeekDay.Tue);break;
            case 3: System.out.println(WeekDay.Wed);break;
            case 4: System.out.println(WeekDay.Thu);break;
            case 5: System.out.println(WeekDay.Fri);break;
            case 6: System.out.println(WeekDay.Sat);break;
            case 7: System.out.println(WeekDay.Sun);break;
            default:System.out.println("wrong number!");
        }
    }
    public String getDay() {
        return day;
    }
    public static void main(String[] args) {
        WeekDay a = WeekDay.Mon;
    }
}

发现通过idea看WeekDay.class文件时看不出继承java.lang.Enum的。只有通过javap命令才能看出来。先看一下java.lang.Enum的定义,Enum<E extends Enum<E>>是自限定类型的标准写法:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { }

截取部分汇编来看:

public final class WeekDay extends java.lang.Enum<WeekDay> {
  public static final WeekDay Mon;

  public static final WeekDay Tue;

  public static final WeekDay Wed;

  public static final WeekDay Thu;

  public static final WeekDay Fri;

  public static final WeekDay Sat;

  public static final WeekDay Sun;

  public static WeekDay[] values();
  public static WeekDay valueOf(java.lang.String);

发现确实WeekDay做到了自限定,因为继承来的成员和方法的类型都被限定成WeekDay它自己了。

分析一下java.lang.Enum这么设计的好处:

  • Enum作为一个抽象类,我们使用enum关键字创建出来的枚举类实际都是Enum的子类,因为class Enum<E extends Enum<E>>的类定义是这种标准的自限定类型,所以编译器直接生成的类必须是WeekDay extends java.lang.Enum<WeekDay>(即本文中讲的:需先创建一个符合边界条件的实际类型,但创建的同时又继承Enum本身,所以就一步到位了)。
  • 正因为编译器生成的枚举类都是Enum的子类,结合上条分析,每种Enum子类的自限定类型都是Enum子类自身。这样WeekDay的实例就只能和WeekDay的实例交互(星期几和星期几比较),Month的实例就只能和Month的实例交互(月份和月份比较)。

JDK源码里自限定的应用——Integer

Integer的类定义是:

public interface Comparable<T> {
    public int compareTo(T o);
}

public final class Integer extends Number implements Comparable<Integer> {//省略}

可以看到Integer实现了Comparable<Integer>,这也是自限定,这样,从Comparable接口继承来的compareTo方法的形参类型就是Integer它自己了。和章节《普通泛型类——构成自限定》里的例子一样。
但接口Comparable的定义可没要求类型参数T必须自限定啊,它甚至连T的边界都没有,当然,这样的好处就是把决定权交给了Comparable的使用者,当使用者想要自限定时,就按照自限定的写法创建新类就好了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java泛型 自限定类型(Self-Bound Types)详解 的相关文章

  • 将字符串转换为整数数组 String at = "1 2 3 4 5" 转换为 ar=[1,2,3,4,5]

    我正在读取一个字符串 作为一整行数字 用空格分隔 即1 2 3 4 5 我想将它们转换为整数数组 以便我可以操作它们 但这段代码不起作用 它说不兼容的类型 String str br readLine int array new int 4
  • Emacs 打字骨架对插入也许

    在 Eclipse 中 编辑 Java 代码时 如果我输入一个左括号 我会得到一对括号 如果我然后 输入 第二个括号 它不会插入额外的括号 我如何在 emacs 中得到它 Eclipse 编辑器足够聪明 当我输入闭括号时 它知道我刚刚完成了
  • 如何实现 Eclipse 清理和构建(又名重建)?

    我删除了我的 binEclipse Indigo 中的文件夹 与 Helios 非常相似 现在我想知道如何重建我的 Java 项目 我只是找不到像 Netbeans 中那样的按钮 对于 Eclipse 您可以在下面找到重建选项项目 gt 清
  • 从插件设置 Maven 属性

    我在这里阅读了一些关于如何从 Maven 插件设置属性的问题 其中大多数讨论了应用程序的版本号 似乎没有简单的方法可以做到这一点 我发现的最佳解决方案是拥有一个从插件更新的 filter properties 文件 并由主 pom 文件使用
  • java.io.IOException: EnsureRemaining: 仅剩余 0 个字节,尝试读取 1

    我在 giraph 中的自定义类方面遇到一些问题 我制作了 VertexInput 和 Output 格式 但总是收到以下错误 java io IOException ensureRemaining Only bytes remaining
  • JCombobox 字符串项(可见)和整数键(固有)

    我有一个数据库模式 它将作为 JTable 列显示在 JCombobox 中以选择名称 但我希望将 ID 字段插入 作为外键 到另一个表中 通常 在下拉列表中选择一个项目 将所选项目带到组合框的显示区域 我想要做的是 当选择组合框中的任何项
  • Java 增强型 For-Loop 比传统的更快?

    所以我的理解是 增强的 for 循环应该更慢 因为它们必须使用迭代器 但是我的代码提供了混合结果 是的 我知道循环逻辑占用了循环中花费的大部分时间 对于少量迭代 100 1000 增强的 for 循环在使用和不使用 JIT 的情况下似乎都要
  • 使用 Bouncy Castle 重建 ED25519 按键 (Java)

    Bouncy Castle 的最新 测试版 版本 bcprov jdk15on 161b20 jar 支持 ED25519 和 ED448 EC 加密以进行签名 我设置了这个完整的工作示例 它按预期工作 我的问题 我是否正确重建了私钥和公钥
  • Junit测试中LocalDateTime反序列化的问题

    我有问题LocalDateTime反序列化Junit测试 我有简单的REST API返回一些DTO目的 当我呼叫端点时 响应没有问题 它是正确的 然后我尝试编写单元测试 得到MvcResult并使用ObjectMapper将其转换为我的DT
  • 使android listview布局可滚动

    我有一个 xml 文件 其布局为 ASCII 形式 ImageView TextView List
  • 堆内存与对象内存

    根据一篇关于Java内存和特性的论文 内存分数分为两种类型 堆内存 即应用程序在运行时消耗的内存 对象内存 即程序中使用的各种对象分配的内存 例如整数和字符串等 他们的意思是stack当他们说时的记忆object记忆 或者它们是什么意思 很
  • IDEA:javac:源版本1.7需要目标版本1.7

    使用 IntelliJ IDEA 运行 JUnit 测试时 我得到 我该如何纠正这个问题 使用SDK 1 7 模块语言级别为1 7 Maven 构建工作正常 这就是为什么我相信IDEA配置问题 您很可能在此处从 Maven 导入了不正确的编
  • Java 中内存高效的稀疏数组

    关于时间高效的稀疏数组存在一些问题 但我正在寻找内存效率 我需要相当于List
  • 无法在android中使用retrofit发出@Post请求

    我正在学习如何在 android 中使用改造 但是每当我尝试从互联网检索数据时 我的应用程序不会返回任何内容我的响应没有成功 我不知道如何修复当前我正在尝试发布的错误并使用此 URL 检索数据https jsonplaceholder ty
  • 字节流和字符流

    请解释一下什么是字节流和字符流 这些究竟意味着什么 Microsoft Word 文档是面向字节的还是面向字符的 Thanks 流是一种顺序访问文件的方式 字节流逐字节访问文件 字节流适用于任何类型的文件 但不太适合文本文件 例如 如果文件
  • 如何在不打开浏览器的情况下查看 Android 应用程序中的网页?

    嘿 我正在开发一个 Android 应用程序 我想连接到该应用程序内的网络 不过 我在某种程度上尝试过 WebView 但它在我的目录中显示的文件很好 但当连接到 google com 时 它显示错误 然后我添加了这个文件
  • 在 Struts 2 中使用单个文件标签上传多个文件

    我想使用单个 Struts 2 文件标签上传多个文件 就像在 Gmail 中一样 我们使用 CTRL 键来选择多个文件来附加多个文件 我知道如何上传多个文件 但我想使用单个文件标签 我在一个小画廊应用程序中上传多个文件 如果您的操作已设置为
  • 使用 Spark SQL 时找不到 Spark Logging 类

    我正在尝试用 Java 进行简单的 Spark SQL 编程 在程序中 我从 Cassandra 表获取数据 将RDD into a Dataset并显示数据 当我运行spark submit命令 我收到错误 java lang Class
  • 如何找到 JAR:/home/hadoop/contrib/streaming/hadoop-streaming.jar

    我正在练习有关 Amazon EMR 的复数视角视频教程 我被困住了 因为我收到此错误而无法继续 Not a valid JAR home hadoop contrib streaming hadoop streaming jar 请注意
  • JAVAFX 缩放、ScrollPane 滚动

    I have JAVAFX application with zoom and scale as described here Scale at pivot point in an already scaled node https sta

随机推荐

  • 学习笔记 JavaScript ES6 声明方式const(一)

    今天学习ES6当中定义常量 先来复习下ES5当中是如何定义常量的 通过如下方法在一个对象上定义新的属性来定义一个常量 见如下代码 这个方法有3个参数 第1个参数是在哪个对象上定义属性 第2个参数是属性名称 第3个参数是对象 Object d
  • 孩子学习机器人法则

    现在社会学习机器人的好处有很多 由于小孩子正处于增长知识 发挥自身应有能力的年纪 格物斯坦表示让小孩子学习一门理论前沿性和实用性都较高的机器人编程教育对小孩子未来发展是非常有益的 首先机器人教育不是孤立存在的 机器人技术是多种学科综合的学科
  • Vue 使用 axios post请求后台数据时 404

    今天遇到Vue 使用 axios post请求后台数据时 404 使用postman 就能获取到 网上找了大半天 终于找到了解决方法 传送门 https www jianshu com p b10454ed38ba 转载于 https ww
  • C语言的一个正则表达式pcre

    1 简介 在C C 中 一个比较好的正则表达式是pcre 被很多工具 包括一些商用工具 使用 2 源码下载 安装 2 1 下载 可以从官网http www pcre org 下载 为方便学习 已放在这里http download csdn
  • ctf.show web入门(信息搜集) 1~20

    目录 web1 源码 web2 源码 web3 抓包 web4 robots web5 index phps web6 解压源码泄露 web7 git泄露 web8 svn泄露 web9 vim缓存 web10 cookie web11 域
  • 快速排序全部算法

    快速排序 cpp 定义控制台应用程序的入口点 include stdafx h include stdlib h include stdio h define MAXSIZE 10 typedef struct int keyWord in
  • 代码随想录算法训练营第13天

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 算法训练营第13天 栈与队列总结 347 前 K 个高频元素 使用堆 基本思路 堆 使用大顶堆还是小顶堆 python 中的heapq 347 前 K 个高频元素 这道题的代
  • 用户级线程和系统级线程

    在多线程操作系统中 各个系统的实现方式并不相同 在有的系统中实现了用户级线程 有的系统中实现了内核级线程 1 内核级线程 1 线程的创建 撤销和切换等 都需要内核直接实现 即内核了解每一个作为可调度实体的线程 2 这些线程可以在全系统内进行
  • 于仕琪C/C++ 学习笔记

    C 函数指针有哪几类 函数指针 lambda 仿函数对象分别是什么 如何利用谓词对给定容器进行自定义排序 传递引用和传递值的区别 传递常引用和传递引用之间的区别 传递右值引用和传递引用之 间的区别 函数对象应该通过什么传递 什么是万能引用
  • 【华为OD机试真题 JAVA】服务器广播

    JS版 华为OD机试真题 JS 服务器广播 标题 服务器广播 时间限制 1秒 内存限制 262144K 语言限制 不限 服务器连接方式包括直接相连 间接连接 A和B直接连接 B和C直接连接 则A和C间接连接 直接连接和间接连接都可以发送广播
  • Java 设计模式之责任链模式

    责任链模式 Chain of Responsibliity 缩写COR 该模式属于对象的行为模式 多个对象连成一条链 请求沿着这条链进行传递 直到有一个对象处理它为止 这样使得多个对象都有机会处理请求 从而避免了请求的发送者和接收者之间的耦
  • 性能测试及相关概念(一)

    目录 一 什么是性能测试 1 1 性能测试概念 1 2 功能测试和性能测试的区别 1 3 影响一个软件性能的因素有哪些 二 一个项目为什么要做性能测试 三 性能测试常见术语以及衡量指标 3 1 专业术语 四 性能测试分类 4 1 基准测试
  • 特征工程之特征选择

    特征工程是数据分析中最耗时间和精力的一部分工作 它不像算法和模型那样是确定的步骤 更多是工程上的经验和权衡 因此没有统一的方法 这里只是对一些常用的方法做一个总结 本文关注于特征选择部分 后面还有两篇会关注于特征表达和特征预处理 1 特征的
  • 单片机学习 6-矩阵按键实验

    矩阵按键实验 矩阵按键介绍 独立按键与单片机连接时 每一个按键都需要单片机的一个 I O 口 若某单片机系统需较多按键 如果用独立按键便会占用过多的 I O 口资源 单片机系统中 I O 口资源往往比较宝贵 当用到多个按键时为了减少 I O
  • vector<int> v 与 vector<int> v(n) 的区别

    使用vector的注意事项 切记 使用 vector
  • ESP32连接阿里云MQTT

    ESP32连接阿里云的github链接 ESP32官网文档 可下载开发文档 文章目录 一 ESP32介绍 二 搭建ESP32开发环境 一 调出终端 二 代码补全 三 ESP32接入阿里云 一 编译项目 二 配置项目 三 烧录程序 四 配置四
  • MLIR Multi-Level Intermediate Representation Overview (多级中间表示概述)

    多级中间表示概述 MLIR项目是一种构建可重用和可扩展的编译器基础结构的新颖方法 MLIR旨在解决软件碎片问题 改善异构硬件的编译 显着降低构建特定于域的编译器的成本 并有助于将现有的编译器连接在一起 要引用MLIR 请使用 此Arxiv出
  • Cause: java.lang.ClassNotFoundException: Cannot find class怎么解决

    java lang ClassNotFoundException Cannot find class 这个异常通常表示在你的 Java 程序中找不到某个类 这可能是由于以下几种情况造成的 类文件没有被编译 在运行 Java 程序时 需要先使
  • TensorFlow学习之LSTM ---语言模型(PTB数据集的处理)

    语言模型是很多自然语言处理应用的基石 非常多自然语言处理应用的技术都是基于语言模型 语言模型的任务就是预测每个句子在语言中出现的概率 一 评价方法 语言模型效果好坏的常用评价指标时复杂度 perplexity 在一个测试集上得到的perpl
  • Java泛型 自限定类型(Self-Bound Types)详解

    文章目录 简介 普通泛型类 构成自限定 自限定类型的泛型类 JDK源码里自限定的应用 enum JDK源码里自限定的应用 Integer 简介 java泛型里会有class SelfBounded