当前位置: 首页 > news >正文

读书笔记-《ON JAVA 中文版》-摘要20[第十九章 类型信息-1]

文章目录

  • 第十九章 类型信息
    • 1. 为什么需要 RTTI
    • 2. Class 对象
      • 2.1 Class 对象
      • 2.2 类字面常量
      • 2.3 泛化的 Class 引用
    • 3. 类型转换检测
    • 4. 注册工厂
    • 5. 类的等价比较
    • 6. 反射:运行时类信息
    • 7. 自我学习总结

第十九章 类型信息

RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息

RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来,并且让我们可以使用某些非常强大的程序。

本章将讨论 Java 是如何在运行时识别对象和类信息的。主要有两种方式:

  1. “传统的” RTTI:假定我们在编译时已经知道了所有的类型;

  2. “反射”机制:允许我们在运行时发现和使用类的信息。

1. 为什么需要 RTTI

import java.util.stream.Stream;abstract class Shape {void draw() {// PS:基类中包含 draw() 方法,它通过传递 this 参数传递给 System.out.println() ,// 间接地使用 toString() 打印类标识符System.out.println(this + ".draw()");}@Overridepublic abstract String toString();
}class Circle extends Shape {@Overridepublic String toString() {return "Circle";}
}class Square extends Shape {@Overridepublic String toString() {return "Square";}
}class Triangle extends Shape {@Overridepublic String toString() {return "Triangle";}
}public class Shapes {public static void main(String[] args) {// PS:在把 Shape 对象放入 Stream<Shape> 中时就会进行向上转型(隐式)Stream.of(new Circle(), new Square(), new Triangle()).forEach(Shape::draw);}
}

输出:

Circle.draw()
Square.draw()
Triangle.draw()

严格来说, Stream 实际上是把放入其中的所有对象都当做 Object 对象来持有,只是取元素时会自动将其类型转为 Shape 。这也是 RTTI 最基本的使用形式,因为在 Java 中,所有类型转换的正确性检查都是在运行时进行的。这也正是 RTTI 的含义所在:在运行时,识别一个对象的类型

—PS:多态的应用

2. Class 对象

2.1 Class 对象

Java 使用 Class 对象来实现 RTTI,即便是类型转换这样的操作都是用 Class 对象实现的。

类是程序的一部分,每个类都有一个 Class 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 Class 对象(更恰当的说,是被保存在一个同名的 .class 文件中)。

所有的类都是第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。

其实构造器也是类的静态方法,虽然构造器前面并没有 static 关键字。所以,使用 new 操作符创建类的新对象,这个操作也算作对类的静态成员引用。

因此,Java 程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。这一点与许多传统编程语言不同,动态加载使得 Java 具有一些静态加载语言(如 C++)很难或者根本不可能实现的特性。

一旦某个类的 Class 对象被载入内存,它就可以用来创建这个类的所有对象。

2.2 类字面常量

Java 还提供了另一种方法来生成类对象的引用:类字面常量(

例如:FancyToy.class)。类字面常量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。

当使用 .class 来创建对 Class 对象的引用时,不会自动地初始化该 Class 对象。为了使用类而做的准备工作实际包含三个步骤:

  1. 加载,这是由类加载器执行的

  2. 链接。在链接阶段将验证类中的字节码,为 static 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。

  3. 初始化。如果该类具有超类,则先初始化超类,执行 static 初始化器和 static 初始化块。

直到第一次引用一个 static 方法(构造器隐式地是 static )或者非常量的 static 字段,才会进行类初始化。

package typeinfo;import java.util.Random;class Initable {static final int STATIC_FINAL = 47;static final int STATIC_FINAL2 =ClassInitialization.rand.nextInt(1000);static {System.out.println("Initializing Initable");}
}class Initable2 {static int staticNonFinal = 147;static {System.out.println("Initializing Initable2");}
}class Initable3 {static int staticNonFinal = 74;static {System.out.println("Initializing Initable3");}
}public class ClassInitialization {public static Random rand = new Random(47);public static void main(String[] args) throws ClassNotFoundException {// 仅使用 .class 语 法来获得对类对象的引用不会引发初始化Class initable = Initable.class;System.out.println("After creating Initable ref");// 如果一个 static final 值是“编译期常量”(如 Initable.staticFinal ),// 那么这个值不需要对 Initable 类进行初始化就可以被读取System.out.println(Initable.STATIC_FINAL);// PS:对 Initable.staticFinal2 的访问将强制进行类的初始化,因为它不是一个编译期常量,// Initable.staticFinal2 虽然也是用 static final 修饰System.out.println(Initable.STATIC_FINAL2);// 如果一个 static 字段不是 final 的,那么在对它访问时,总是要求在它被读取之前,// 要先进行链 接(为这个字段分配存储空间)和初始化(初始化该存储空间)System.out.println(Initable2.staticNonFinal);//  Class.forName() 来产生 Class 引 用会立即就进行初始化Class initable3 = Class.forName("typeinfo.Initable3");System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);}
}

输出:

After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

2.3 泛化的 Class 引用

Java 引入泛型语法之后,我们可以使用泛型对 Class 引用所指向的 Class 对象的类型进行限定。在下面的实例中,两种语法都是正确的:

public class GenericClassReferences {public static void main(String[] args) {Class intClass = int.class;Class<Integer> genericIntClass = int.class;genericIntClass = Integer.class; // 同一个东西// 普通的类引用可以重新赋值指向任何其他的 Class 对象intClass = double.class;// 使用泛型限定的类引用只能指向其声明的类型// genericIntClass = double.class;}
}

为了在使用 Class 引用时放松限制,我们使用了通配符,它是 Java 泛型中的一部分。通配符就是 ? ,表示“任何事物”。

public class WildcardClassReferences {public static void main(String[] args) {Class<?> intClass = int.class;intClass = double.class;// 为了创建一个限定指向某种类型或其子类的 Class 引用,// 我们需要将通配符与 extends 关键字配 合使用,创建一个范围限定。Class<? extends Number> bounded = int.class;bounded = double.class;bounded = Number.class;}
}

3. 类型转换检测

直到现在,我们已知的 RTTI 类型包括:

  1. 传统的类型转换,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 ClassCastException 异常。

  2. 代表对象类型的 Class 对象. 通过查询 Class 对象可以获取运行时所需的信息。

RTTI 在 Java 中还有第三种形式,那就是关键字 instanceof 。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子:

if(x instanceof Dog) ((Dog)x).bark();

在将 x 的类型转换为 Dog 之前, if 语句会先检查 x 是否是 Dog 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 instanceof 是非常重要的,否则会得到一个 ClassCastException 异常。

instanceof 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 Class 对象作比较。

Class.isInstance() 方法提供了一种动态测试对象类型的方法。

Dog.isInstance(x)

4. 注册工厂

工厂方法可以以多态方式调用,并为你创建适当类型的对象。

5. 类的等价比较

当你查询类型信息时,需要注意:instanceof 的形式(即 instanceof 或 isInstance() ,这两者产生的结果相同) 和 与 Class 对象直接比较 这两者间存在重要区别。

class Base {
}class Derived extends Base {
}public class FamilyVsExactType {static void test(Object x) {System.out.println("Testing x of type " + x.getClass());System.out.println("x instanceof Base " + (x instanceof Base));System.out.println("x instanceof Derived " + (x instanceof Derived));System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x));System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class));System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));System.out.println("x.getClass().equals(Base.class)) " + (x.getClass().equals(Base.class)));System.out.println("x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class)));System.out.println("------------");}public static void main(String[] args) {test(new Base());test(new Derived());}
}

输出:

Testing x of type class typeinfo.Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class)) true
x.getClass().equals(Derived.class)) false
------------
Testing x of type class typeinfo.Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class)) false
x.getClass().equals(Derived.class)) true
------------

instanceof 和 isInstance() 产生的结果相同, equals() 和 == 产生的结果也相同。但测试本身得出了不同的

结论。与类型的概念一致, instanceof 说的是“你是这个类,还是从这个类派生的类?”。而如果使用 == 比较实际的 Class 对象,则与继承无关 —— 它要么是确切的类型,要么不是。

6. 反射:运行时类信息

类 Class 支持反射的概念, java.lang.reflect 库中包含类 Field 、 Method 和 Constructor (每一个都实现了 Member 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 Constructor 创建新对象, get() 和 set() 方法读取和修改与 Field 对象关联的字段, invoke() 方法调用与 Method 对象关联的方法。此外,还可以调

用便利方法 getFields() 、 getMethods() 、 getConstructors() 等,以返回表示字段、方法和构造函数的对象数组。

RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 .class 文件。换句话说,你可以 用“正常”的方式调用一个对象的所有方法。通过反射, .class 文件在编译时不可用;它由运行时环境打开并检查。

7. 自我学习总结

  1. RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息

  2. Java 使用 Class 对象来实现 RTTI,保存在 java 文件编译后 .class 文件中

  3. Java 还提供了另一种方法来生成类对象的引用:类字面常量(

    例如:FancyToy.class),此时不会自动初始化该 Class 对象

  4. 使用类前要做到3个步骤:加载(JVM加载类)、链接(为 static 字段分配存储空间)、初始化(初始化超类及 static 修饰的方法和块)

  5. 可以使用泛型对 Class 引用所指向的 Class 对象的类型进行限定:

Class<Integer> genericIntClass = int.class;

​ 通配符与 extends 关键字配合使用,创建一个范围限定:

Class<? extends Number> bounded = int.class;
  1. 类型转换检测:

​ 1)instanceof

if(x instanceof Dog) ((Dog)x).bark();

​ 2)isInstance

Dog.isInstance(x)
  1. instanceof 说的是“你是这个类,还是从这个类派生的类?”(父类或者本类);而如果使用 == 比较实际的 Class 对象,则与继承无关 —— 它要么是确切的类型,要么不是(确切的对象)

相关文章:

读书笔记-《ON JAVA 中文版》-摘要20[第十九章 类型信息-1]

文章目录 第十九章 类型信息1. 为什么需要 RTTI2. Class 对象2.1 Class 对象2.2 类字面常量2.3 泛化的 Class 引用 3. 类型转换检测4. 注册工厂5. 类的等价比较6. 反射&#xff1a;运行时类信息7. 自我学习总结 第十九章 类型信息 RTTI&#xff08;RunTime Type Information&am…...

3、Linux驱动开发:模块_传递参数

目录 &#x1f345;点击这里查看所有博文 随着自己工作的进行&#xff0c;接触到的技术栈也越来越多。给我一个很直观的感受就是&#xff0c;某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了&#xff0c;只有经常会用到的东西才有可能真正记…...

基于 ThinkPHP 5.1(稳定版本) 开发wms 进销存系统源码

基于ThinkPHP 5.1&#xff08;LTS版本&#xff09;开发的WMS进销存系统源码 管理员账号密码&#xff1a;admin 一、项目简介 这个系统是一个基于ThinkPHP框架的WMS进销存系统。 二、实现功能 控制台 – 权限管理&#xff08;用户管理、角色管理、节点管理&#xff09; – 订…...

全面解析 SOCKS5 代理和 HTTP 代理在网络安全与爬虫应用中的技术对比与应用指南

一、SOCKS5 代理和 HTTP 代理的基本原理 SOCKS5 代理&#xff1a;SOCKS5 是一种网络协议&#xff0c;可以在传输层代理 TCP 和 UDP 请求。它不解析请求内容&#xff0c;仅在客户端和代理服务器之间建立连接&#xff0c;并转发数据。SOCKS5 代理支持众多网络协议和端口类型&…...

DevOps系列文章 之 docker 制作kafka镜像

Docker制作Kafka镜像教程 概述 本教程将指导你如何使用Docker制作一个Kafka镜像。Kafka是一个高性能、分布式的消息队列系统&#xff0c;用于处理大规模的实时数据流。使用Docker制作Kafka镜像可以方便地部署和管理Kafka集群。 整体流程 下面是制作Kafka镜像的整体流程&#xf…...

iPhone 安装 iOS 17公测版(Public Beta)

文章目录 步骤1. 备份iPhone资料步骤2. 申请iOS 17 公测Beta 资格步骤3. 下载iOS 16 Beta 公测描述档步骤4. 选择iOS 17 Beta 公测描述档更新项目步骤5. 升级iOS 17 Public Beta 公开测试版 苹果已经开始向大众释出首个iOS 17 公开测试版/ 公测版( iOS 17 Public Beta)&#xf…...

Spingboot yaml 配置文件及数据读取

属性配置在这里插入图片描述 修改服务器端口 → server.port80 修改 banner → spring.main.banner off(关闭)/console(控制台)/log(日志) 日志 → logging.level.rootinfo Common Application Properties 配置文件分类 优先级 如果三种文件共存时&#xff0c;优先级为&am…...

vue中使用axios发送请求时,后端同一个session获取不到值

问题描述&#xff1a; 在登录页面加载完成后通过axios请求后端验证码接口&#xff08;这时后端会生成一个session用于保存验证码数值&#xff09;&#xff0c;当输入完用户名、密码、验证码后请求登录接口&#xff0c;报错验证码输入错误&#xff0c;打印后端保存验证码的sessi…...

html请求谷歌音频跨域问题(谷歌翻译接口)虚拟机ping不通google(下载谷歌音频、下载百度翻译音频)

文章目录 调用谷歌翻译接口&#xff0c;尝试了几种方案&#xff0c;都提示跨域不行第一种&#xff08;通过js代码获取音频文件的Blob对象&#xff0c;提示跨域了&#xff09;代码结果 第二种&#xff08;尝试新窗打开音频url&#xff0c;404&#xff0c;估计也是跨域了&#xf…...

【设计模式|结构型】享元模式(Flyweight Pattern)

概述 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它旨在通过共享对象来减少系统中的对象数量&#xff0c;以便在有限的内存中节省空间和提高性能。在享元模式中&#xff0c;对象分为两部分&#xff1a;内部状态&#xff08;Intrinsic…...

最小覆盖子串(JS)

最小覆盖子串 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量…...

<C语言> 预处理和宏

1.预定义符号 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C&#xff0c;其值为1&#xff0c;否则未定义这些预定义符号都是C语言内置的。 举个例子&…...

代驾公司如何进行运营分析

在这个快节奏的社会中&#xff0c;人们的生活节奏也在不断加快&#xff0c;对于代驾服务的需求也日益增长。然而&#xff0c;如何在这个竞争激烈的市场中&#xff0c;让订单稳稳地握在自己的手中&#xff0c;成为了每一个代驾公司都需要深思的问题。那么&#xff0c;代驾公司如…...

初学HTML:采用CSS绘制一幅夏天的图

下面代码使用了HTML和CSS来绘制一幅炎炎夏日吃西瓜的画面。其中&#xff0c;使用了伪元素和阴影等技巧来实现部分效果。 <!DOCTYPE html> <html> <head><title>炎炎夏日吃西瓜</title><style>body {background-color: #add8e6; /* 背景颜…...

经典文献阅读之--NoPe-NeRF(优化无位姿先验的神经辐射场)

0. 简介 在没有预先计算相机姿态的情况下训练神经辐射场&#xff08;NeRF&#xff09;是具有挑战性的。最近在这个方向上的进展表明&#xff0c;在前向场景中可以联合优化NeRF和相机姿态。然而&#xff0c;这些方法在剧烈相机运动时仍然面临困难。我们通过引入无畸变单目深度先…...

在docker中没有vi如何修改docker中的文件

今天在做学成在线的项目&#xff0c;遇到了一个问题&#xff0c;就是死活登不上xxl-job&#xff0c;按照之前遇到的nacos的问题&#xff0c;我怀疑很大概率是和当时的ip设置有关&#xff0c;不知道nacos的ip怎么修改的同学&#xff0c;可以看看这篇文章&#xff1a;关于docker中…...

【Docker】Docker应用部署之Docekr容器安装Nginx

目录 一、搜索镜像 二、拉取镜像 三、创建容器 四、测试使用 一、搜索镜像 docker search nginx 二、拉取镜像 docker pull nginx # 不加冒号版本号 默认拉取最新版 三、创建容器 首先我们需要在宿主机创建数据卷目录 mkdir nginx # 创建目录 cd nginx # 进入目录 mkd…...

flutter开发实战-jsontodart及 生成Dart Model类

flutter开发实战-jsontodart及 生成Dart Model类。 在开发中&#xff0c;经常遇到请求的数据Json需要转换成model类。这里记录一下Jsontodart生成Dart Model类的方案。 一、JSON生成Dart Model类 在开发中经常用到将json转成map或者list。通过json.decode() 可以方便 JSON 字…...

C++复刻:[流光按钮]+[悬浮波纹按钮]

目录 参考效果实现main.cppdialog.hdialog.cppflowingRayButton.h 流动光线按钮flowingRayButton.cpp 流动光线按钮hoveringRippleButton.h 悬浮波纹按钮hoveringRippleButton.cpp 悬浮波纹按钮模糊知识点 源码 参考 Python版本&#xff1a;GitHub地址 B站主页 效果 实现 ma…...

CompletableFuture 详解

目录 简单介绍 常见操作 创建 CompletableFuture new 关键字 静态工厂方法 处理异步结算的结果 简单介绍 CompletableFuture 同时实现了 Future 和 CompletionStage 接口。 public class CompletableFuture<T> implements Future<T>, CompletionStage<T…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...