Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • Java基础

    • Java 基础
      • 封装、继承和多态
        • 多态
        • 动态绑定
        • 虚拟方法调用
      • 抽象类和接口
        • 抽象类和普通类区别
        • 抽象类和接口区别
      • 重载重写
      • 对equals()和hashCode()的理解?
        • hashCode 原理
      • equals()与==有什么不同
      • Object
        • Object 类的常见方法有哪些?
      • String
        • String的常用方法
        • String、StringBuffer与StringBuilder的区别
        • String s 与new String的区别
        • String 原理
      • 深拷贝和浅拷贝
      • 反射的原理,反射创建类实例的三种方式是什么
      • final关键字
      • finally一定会执行吗
      • 谈谈序列化与反序列化
      • Java 数据类型
      • BigDecimal
      • 自动装箱、拆包
      • JAVA泛型
      • Java异常
      • BIO、NIO、AIO三者之间的区别
      • IO多路复用
      • java8新特性
      • java17新特性
      • JDK和JRE区别
      • SPI
    • Java 集合
    • JUC
    • JVM
    • Linux
    • 设计模式
  • 框架

  • 数据库

  • 消息队列

  • 408

  • 大数据

  • 面试
  • Java基础
Iekr
2023-11-11
目录

Java 基础

# Java 基础

# 封装、继承和多态

tag: 小米 、 咪咕 、 腾讯 、 招行 、 美团 、 七牛云 、 哔哩哔哩 、 万得 、 阿里云 、 浩鲸科技 、 网新恒天 、 小米 、 快手

count:21

as:对封装继承多态的理解

面向对象的特征,解释一下多态

多态是怎么实现的

面向对象的原则

运行时多态和编译时多态

继承、封装与多态具体概念及使用场景

JDK 中对封装、继承、多态的典型应用

java 三个特性

java 面向对象的好处

Java 面向对象的特性,分别怎么理解的

Java 中构造器的特点?如果显式声明了有参的构造器,那么还能使用无参构造器吗?

Java 为什么不可以多继承

  • 封装:指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
  • 继承:不同类型的对象,相互之间经常有一定数量的共同点。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
  • 多态:顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
    • 对象类型和引用类型之间具有继承(类)/ 实现(接口)的关系;
    • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
    • 多态不能调用 “只在子类存在但在父类不存在” 的方法;
    • 如果子类重写了父类的方法,真正执行的是子类重写的方法,如果子类没有重写父类的方法,执行的是父类的方法。
  • 抽象:抽取事物的本质特征,忽略非本质的细节,通过抽象类和接口实现。

# 多态

多态有以下几个特点和优势:

  1. 可替换性:子类对象可以随时替代父类对象,向上转型。
  2. 可扩展性:通过添加新的子类,可以扩展系统的功能。
  3. 接口统一性:可以通过父类类型的引用访问子类对象的方法,统一对象的接口。
  4. 代码的灵活性和可维护性:通过多态,可以将代码编写成通用的、松耦合的形式,提高代码的可维护性

通过多态,我们可以利用父类类型的引用变量来指向子类对象,并根据实际对象的类型调用对应的方法。这样可以在不修改现有代码的情况下,动态地切换和扩展对象的行为。

实现原理动态绑定和虚拟方法调用

# 动态绑定

动态绑定(Dynamic Binding):指的是在编译时,Java 编译器只能知道变量的声明类型,而无法确定其实际的对象类型。而在运行时,Java 虚拟机(JVM)会通过动态绑定来解析实际对象的类型。这意味着,编译器会推迟方法的绑定(即方法的具体调用)到运行时。正是这种动态绑定机制,使得多态成为可能。

# 虚拟方法调用

虚拟方法调用(Virtual Method Invocation):在 Java 中,所有的非私有、非静态和非 final 方法都是被隐式地指定为虚拟方法。虚拟方法调用是在运行时根据实际对象的类型来确定要调用的方法的机制。当通过父类类型的引用变量调用被子类重写的方法时,虚拟机会根据实际对象的类型来确定要调用的方法版本,而不是根据引用变量的声明类型。

所以,多态的实现原理主要是依靠 “动态绑定” 和 “虚拟方法调用”,它的实现流程如下:

  1. 创建父类类型的引用变量,并将其赋值为子类对象。
  2. 在运行时,通过动态绑定确定引用变量所指向的实际对象的类型。
  3. 根据实际对象的类型,调用相应的方法版本。

# 抽象类和接口

tag: 小米 、 数字马力 、 腾讯 、 用友 、 网新恒天 、 阿里 、 经纬恒润 、 瑞幸 、 税友 、 中通 、 竞技世界 、 淘天 、 一嗨租车

count:21

as:抽象类和普通类区别

抽象类能加 final 修饰吗

抽象类可以定义构造函数吗

# 抽象类和普通类区别

普通类和抽象类是两种不同的类类型。普通类是可以直接实例化的类,而抽象类则不能直接实例化。

抽象类通常用于定义一些基本的行为和属性,而具体的实现则由其子类来完成。

  • 实例化:普通类可以直接实例化,而抽象类不能直接实例化。
  • 方法:抽象类中既包含抽象方法又可以包含具体的方法,而普通类只能包含普通方法。
  • 实现:普通类实现接口需要重写接口中的方法,而抽象类可以实现接口方法也可以不实现。

# 抽象类和接口区别

抽象类和接口是两种不同的类类型。它们都不能直接实例化,并且它们都是用来定义一些基本的属性和方法的,但它们有以下几点不同:

  1. 定义:定义的关键字不同,抽象类是 abstract,而接口是 interface。
  2. 方法:抽象类可以包含抽象方法和具体方法,而接口只能包含方法声明(抽象方法)。 接口可以包含默认方法(使用 default 关键字定义的方法)和静态方法(使用 static 关键字定义的方法),这些方法提供了具体的实现。
  3. 方法访问控制符:抽象类无限制,只是抽象类中的抽象方法不能被 private 修饰;而接口有限制,接口默认的是 public 控制符,不能使用 protected 、 private 或 default 这样的修饰符。
  4. 实现:一个类只能继承一个抽象类,但可以实现多个接口。
  5. 变量:抽象类可以包含实例变量和静态变量,而接口只能包含常量。
  6. 构造函数:抽象类可以有构造函数,而接口不能有构造函数。
  • 一个子类只能继承一个抽象类,但能实现多个接口
  • 抽象类可以有构造方法,接口没有构造方法
  • 抽象类可以有普通成员变量,接口没有普通成员变量
  • 抽象类和接口都可有静态成员变量,抽象类中静态成员变量访问类型任意,接口只能 public static final (默认)
  • 抽象类可以没有抽象方法,抽象类可以有普通方法;接口在 JDK8 之前都是抽象方法,在 JDK8 可以有 default 方法,在 JDK9 中允许有私有普通方法
  • 抽象类可以有静态方法;接口在 JDK8 之前不能有静态方法,在 JDK8 中可以有静态方法,且只能被接口类直接调用(不能被实现类的对象调用)
  • 抽象类中的方法可以是 public、protected; 接口方法在 JDK8 之前只有 public abstract,在 JDK8 可以有 default 方法,在 JDK9 中允许有 private 方法

# 重载重写

tag: 数字马力 、 阿里 、 软通 、 亚信 、 途牛 、 税友 、 中通 、 七牛云 、 快手 、 货拉拉 、 博彦

count:16

as:重载重写应用场景

什么情况下需要用到重载

  • 方法重载是指在同一个类中定义多个方法,它们具有相同的名称但参数列表不同、访问修饰符可以不同

返回值不同不算方法重载

public String myMethod(int arg1) {
    // 方法体
}

public int myMethod(int arg1) {
    // 方法体
}

// 方法调用
myMethod(1);
1
2
3
4
5
6
7
8
9
10

因为不同的返回值类型,JVM 没办法分辨到底要调用哪个方法,JVM 调用方法是通过方法签名来判断到底要调用哪个方法的,而方法签名 = 方法名称 + 参数类型 + 参数个数组成的一个唯一值,这个唯一值就是方法签名。

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

  1. 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  3. 构造方法无法被重写

重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。

区别点 重载方法 重写方法
发生范围 同一个类 子类
参数列表 必须修改 一定不能修改
返回类型 可修改 子类方法返回值类型应比父类方法返回值类型更小或相等
异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期

# 对 equals () 和 hashCode () 的理解?

tag: 货拉拉 、 京东 、 美团 、 数字马力 、 淘天 、 完美世界 、 联想 、 小米 、 飞猪 、 得物

count:11

as:一个自定义对象,分别创建了两个实例,怎么样比较它们的大小,使用 equals 可以比较吗

为什么要有 hashcode,hashcode 是做什么用的

如果结构不是 hash 结构的话可不可以直接使用 equals 方法

为什么要同时重写 equals 和 hashcode 方法

为什么重写 eqauls 就要重写 hashcode?

为什么在重写 equals 方法的时候需要重写 hashCode 方法?

因为有强制的规范指定需要同时重写 hashcode 与 equals 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。

两个对象的 hashCode 值相等并不代表两个对象就相等。

  • 如果两个对象的 hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的 hashCode 值相等并且 equals() 方法也返回 true ,我们才认为这两个对象相等。
  • 如果两个对象的 hashCode 值不相等,我们就可以直接认为这两个对象不相等。

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象, hashCode 值却不相等。

# hashCode 原理

hashCode() 的作用是获取哈希码( int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

该方法在 Oracle OpenJDK8 中默认是 "使用线程局部状态来实现 Marsaglia's xor-shift 随机数生成", 并不是 "地址" 或者 "地址转换而来", 不同 JDK/VM 可能不同在 Oracle OpenJDK8 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode=4 启用第五种。参考源码:

  • https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hppopen in new window (opens new window)(1127 行)
  • https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cppopen in new window (opens new window)(537 行开始)

OpenJDK 8 提供了多种不同的 hashCode() 生成策略,可以通过 VM 参数 -XX:hashCode=<n> 来选择不同的实现方式:

  1. Default:使用 Marsaglia's xor-shift 随机数生成器。
  2. Identity:基于对象标识(类似于早期版本的行为)。
  3. Random:完全随机化的哈希码。
  4. ThreadLocalXORShift:基于线程局部状态的 xor-shift 随机数生成器。
  5. Address-based:基于对象地址(这可能是为了兼容性考虑)。
  6. Zero:总是返回 0(通常用于测试目的)。

通过设置 JVM 启动参数来指定具体的哈希码生成策略 java -XX:hashCode=4 MyApplication

# equals () 与 == 有什么不同

tag: 软通 、 苏小研 、 亚信 、 阿里 、 快手 、 联想 、 百度 、 大象慧云 、 京东 、 博彦 、 捷运达

count:14

as:String 类里面的 equals 方法实现看过吗,大概的实现流程

两个 new String 相同的字符串,== 为 true 吗,为啥

  • == 是判断两个变量或实例是不是指向同一个内存空间,equals 是判断两个变量或实例所指向的内存空间的值是不是相同
  • == 是指对内存地址进行比较 , equals () 是对字符串的内容进行比较
  • == 指引用是否相同, equals () 指的是值是否相同

对于 Object 来说,其 equals 方法底层实现就是 “==”,对于 Object 对象来说,equals 和 == 都是一样的,都是比较对象的引用是否相同。

public boolean equals(Object obj) {
	return (this == obj);
}
1
2
3

String 中的 equals 实现源码,是将 Object 中的引用比较重写成了值比较了,此时就不再是对比两个对象的引用了,而是对比两个对象的值是否相等。

public boolean equals(Object anObject) {
if (this == anObject) { // 引用相同返回 true,引用相同,那么值肯定相同了
    return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value); // equals 为下面的 equals 方法
}
@IntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
    if (value.length == other.length) {
        for (int i = 0; i < value.length; i++) { // 循环每个字符对比,本质是值比较
            if (value[i] != other[i]) {
                return false;
            }
        }
        return true;
    }
    return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Object

# Object 类的常见方法有哪些?

/**
 * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
 */
public final native Class<?> getClass()
/**
 * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
 */
public native int hashCode()
/**
 * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
 */
public boolean equals(Object obj)
/**
 * native 方法,用于创建并返回当前对象的一份拷贝。
 */
protected native Object clone() throws CloneNotSupportedException
/**
 * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
 */
public String toString()
/**
 * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
 */
public final native void notify()
/**
 * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
 */
public final native void notifyAll()
/**
 * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
 */
public final native void wait(long timeout) throws InterruptedException
/**
 * 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
 */
public final void wait(long timeout, int nanos) throws InterruptedException
/**
 * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
 */
public final void wait() throws InterruptedException
/**
 * 实例被垃圾回收器回收的时候触发的操作
 */
protected void finalize() throws Throwable { }
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
39
40
41
42
43
44

# String

# String 的常用方法

tag:

count:1

as:

# String、StringBuffer 与 StringBuilder 的区别

tag: 快手 、 神州出行 、 用友 、 大象慧云 、 货拉拉 、 青书 、 人人网 、 经纬恒润 、 金蝶 、 宁波银行

count:17

as:String 为什么不能拆分

为什有了 string 还要 stringbuffer,buffer 和 string 的区别,性能比较

有很多个字符串和变量,需要把它们加起来,这时候用 String 会有什么问题

StringBuffer 追加一个数,内部如何变化。

  • 可变性:String 对象是不可变的,而 StringBuffer 和 StringBuilder 是可变字符序列,String 是 final 类,不能被继承,String 实现了 equals () 方法和 hashCode () 方法
  • 性能:每次对 String 的操作相当于生成一个新的 String 对象,而对 StringBuffer 和 StringBuilder 的操作是对对象本身的操作,而不会生成新的对象,所以对于频繁改变内容的字符串避免使用 String。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
  • 线程安全:
    • String 由 final 修饰,是 immutable 的,安全性是简单而纯粹的
    • StringBuilder 和 StringBuffer 的区别在于 StringBuilder 不保证同步,也就是说如果需要线程安全需要使用 StringBuffer,不需要同步的 StringBuilder 效率更高

# String s 与 new String 的区别

tag: 完美世界 、 快手 、 一嗨租车

count:6

as:String a = "123", String b = new String ("123"),会创建几个变量

String a = "str_a", String b = new String (a + "str_b"); 创建了几个对象

string 的创建方式?什么情况下会用到字符串池

我现在 new 一个 String 构造器传 123,这个操作会产生有几个对象?

String str ="whx";
String newStr =new String ("whx");
1
2

String str ="whx" 先在常量池中查找有没有 "whx" 这个对象,如果有,就让 str 指向那个 "whx". 如果没有,在常量池中新建一个 “whx” 对象,并让 str 指向在常量池中新建的对象 "whx"。

String newStr =new String ("whx"); 是在堆中建立的对象 "whx" , 在栈中创建堆中 "whx" 对象的内存地址。

# String 原理

tag: 神州出行 、 快手 、 百度 、 青书 、 字节 、 数字马力

count:9

as:String 的特性,不可变的好处,怎么实现不可变的

string 底层的 char [] 数组存储都是两个字节,如果我换成不定长的存储数组会有什么问题,比如字符串 "12 我是",12 占用两个字节,我是占用应该是 6 个字节,这样存在什么问题?

string 为什么要设置成引用类型而不是基本类型

String 有什么特点?它的不可变体现在哪里?String 有长度限制吗?

String 是不是基本数据类型,那是什么类型

String 为什么不可变

String a=123,a=1234,内部是怎么变化的?

String 类中使用 final 关键字修饰字符数组来保存字符串

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
  //...
}
1
2
3
4

我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此, final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的( final 修饰引用类型变量的情况)。

String 真正不可变有下面几点原因:

  1. 保存字符串的数组被 final 修饰且为私有的,并且 String 类没有提供 / 暴露修改这个字符串的方法。
  2. String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

在 Java 9 之后, String 、 StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串。

public final class String implements java.io.Serializable,Comparable<String>, CharSequence {
    // @Stable 注解表示变量最多被修改一次,称为“稳定的”。
    @Stable
    private final byte[] value;
}

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    byte[] value;

}
1
2
3
4
5
6
7
8
9
10

Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?

新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下, byte 占一个字节 (8 位), char 占用 2 个字节(16), byte 相较 char 节省一半的内存空间。

JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。

# 深拷贝和浅拷贝

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
  • 引用拷贝:就是两个不同的引用指向同一个对象。

image-20240710181519919

# 反射的原理,反射创建类实例的三种方式是什么

tag: 得物 、 用友 、 美团 、 tp-link 、 快手 、 字节 、 小米 、 阿里 、 途牛 、 数字马力 、 深信服 、 亚信 、 大智慧 、 完美

count:21

as:反射的核心类是什么

反射,反射的应用,反射存在的问题

反射是什么,什么时候用反射,为什么要用

反射底层是怎么实现的

反射,反射创建对象的方式

反射加载类的几种方式?原理了解吗?应用场景?

写注解实现反射

Java 反射机制:

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制


获取 Class 类对象三种方式:

  • 使用 Class.forName 静态方法
  • 使用类的.class 方法
  • 使用实例对象的 getClass () 方法

下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

另外,像 Java 中的一大利器 注解 的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个 @Component 注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value 注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类 / 属性 / 方法 / 方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

# final 关键字

tag:

count:8

as:final/finalize 的使用?(finalize 不知道,好像是垃圾回收里的)

final 修饰变量 不能改写 修饰 list 集合,可以加数据吗

final 代码块和 finally 区别

  • 在修饰方法的时候,说明该方法无法被重写。

  • 在修饰类的时候,说明该类无法被继承。

  • 在修饰属性的时候,说明该变量从创建到销毁过程中不会改变。

  • 在修饰形参的时候,说明该形参的引用在方法执行前后都会不发生改变。

使用 finalize () 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。

# finally 一定会执行吗

tag: 亚信 、 招银 、 饿了么

count:7

as:finally 什么时候不会被执行

try catch finally 代码块的作用

try catch finally 中,如果 catch 中 return 了 finally 中也 return 了,他的执行情况。

finally 能抛出异常吗?

try 的时候 catch 和 finally 哪个是可以不写的

不一定的!在某些情况下,finally 中的代码不会被执行。

正常运行的情况下,finally 中的代码是一定会执行的,但是,如果遇到以下异常情况,那么 finally 中的代码就不会继续执行了:

程序在 try 块中遇到 System.exit () 方法,会立即终止程序的执行,这时 finally 块中的代码不会被执行

public class FinallyExample {
    public static void main(String[] args) {
        try {
            System.out.println("执行 try 代码.");
            System.exit(0);
        } finally {
            System.out.println("执行 finally 代码.");
        }
    }
}
1
2
3
4
5
6
7
8
9
10

在 try 块中遇到 Runtime.getRuntime ().halt () 代码,强制终止正在运行的 JVM。与 System.exit () 方法不同,此方法不会触发 JVM 关闭序列。因此,当我们调用 halt 方法时,都不会执行关闭钩子或终结器。

public class FinallyExample {
    public static void main(String[] args) {
        try {
            System.out.println("执行 try 代码.");
            Runtime.getRuntime().halt(0);
        } finally {
            System.out.println("执行 finally 代码.");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
  • 程序在 try 块中遇到无限循环或者发生死锁等情况时,程序可能无法正常跳出 try 块,此时 finally 块中的代码也不会被执行。
  • 掉电问题,程序还没有执行到 finally 就掉电了(停电了),那 finally 中的代码自然也不会执行。
  • JVM 异常崩溃问题导致程序不能继续执行,那么 finally 的代码也不会执行。

# 谈谈序列化与反序列化

tag:

count:1

as:

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

  • 序列化是指将对象转换为字节序列的过程
  • 反序列化则是将字节序列转换为对象的过程。

Java 对象序列化是将实现了 Serializable 接口的对象转换成一个字节序列,能够通过网络传输、文件存储等方式传输 ,传输过程中却不必担心数据在不同机器、不同环境下发生改变,也不必关心字节的顺序或其他任何细节,并能够在以后将这个字节序列完全恢复为原来的对象。

下面是序列化和反序列化常见应用场景:

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
  • 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
  • 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。

# Java 数据类型

tag:

count:20

as:char 占多少字节,多少位

int 在不同平台占得内存是一样的吗

int 是多少字节以及它的范围?1 后面 31 个 0 数字是多少?

int 的取值范围(问我怎么推倒出来的取值范围)

int?char?汉字是几个字节?

基本数据类型和包装数据类型有什么区别?

Java 的基础数据类型和占用内存

Java 中有 8 种基本数据类型,分别为:

  • 6 种数字类型:
    • 4 种整数型: byte 、 short 、 int 、 long
    • 2 种浮点型: float 、 double
  • 1 种字符类型: char
  • 1 种布尔型: boolean 。
基本类型 位数 字节 默认值 取值范围
byte 8 1 0 -128 ~ 127
short 16 2 0 -32768(−215) ~ 32767(215−1)
int 32 4 0 -2147483648(−231) ~ 2147483647(231−1)
long 64 8 0L -9223372036854775808(−263) ~ 9223372036854775807(263−1)
char 16 2 'u0000' 0 ~ 65535(216−1)
float 32 4 0f 1.4E-45 ~ 3.4028235E38
double 64 8 0d 4.9E-324 ~ 1.7976931348623157E308
boolean 1 FALSE true、false

这八种基本类型都有对应的包装类分别为: Byte 、 Short 、 Integer 、 Long 、 Float 、 Double 、 Character 、 Boolean 。

# BigDecimal

tag:

count:4

as:BigDecimal 如何进行计算、怎么四舍五入 BigDicimal 可以存整数吗?

说一下金额为什么要用 BigDecimal 而不是 float 和 double (标度和数值,float 和 double 近似值)

Java 中表示 1,用 int,Integer,BigDicimal 三个去表达有什么区别?

# 自动装箱、拆包

tag:

count:3

as:Integer 和 int 的各种 == 判断

Java 的自动拆装箱机制

Java 包装类型

自动装箱就是 Java 自动将原始类型值转换成对应的对象,比如将 int 的变量转换成 Integer 对象,这个过程叫做装箱,反之将 Integer 对象转换成 int 类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型 byte, short, char, int, long, float, double 和 boolean 对应的封装类为 Byte, Short, Character, Integer, Long, Float, Double, Boolean。

基本类型和包装类型的区别?

  • 用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。
  • 存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
  • 占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
  • 默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null 。
  • 比较方式:对于基本数据类型来说, == 比较的是值。对于包装数据类型来说, == 比较的是对象的内存地址。整型包装类对象之间值的比较,全部使用 equals() 方法。

什么是自动拆装箱?

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;
Integer i = 10;  //装箱
int n = i;   //拆箱
1
2

对应的字节码

   L1

    LINENUMBER 8 L1

    ALOAD 0

    BIPUSH 10

    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

    PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;

   L2

    LINENUMBER 9 L2

    ALOAD 0

    ALOAD 0

    GETFIELD AutoBoxTest.i : Ljava/lang/Integer;

    INVOKEVIRTUAL java/lang/Integer.intValue ()I

    PUTFIELD AutoBoxTest.n : I

    RETURN
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

从字节码中,我们发现装箱其实就是调用了 包装类的 valueOf() 方法,拆箱其实就是调用了 xxxValue() 方法。

所以:

  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue() ;

如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。可以尝试使用基本类型进行替换

# JAVA 泛型

tag:

count:2

as:

Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。

编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList<Person> persons = new ArrayList<Person>() 这行代码就指明了该 ArrayList 对象只能传入 Person 对象,如果传入其他类型的对象就会报错。

泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。

# Java 异常

tag:

count:4

as:Java 中的异常有哪些

Java 异常体系是怎么设计的?异常分类? Java 异常类,层次结构 (说了 Error, Checked Exception 和 Unchecked Exception) Java 异常类,运行时异常具体有哪些

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。 Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error: Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过 catch 捕获 。例如 Java 虚拟机运行错误( Virtual MachineError )、虚拟机内存不够错误 ( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Java 异常类层次结构图概览:

image-20240710201424159

Checked Exception 和 Unchecked Exception 有什么区别?

  • Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch 或者 throws 关键字处理的话,就没办法通过编译。 除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、 ClassNotFoundException 、 SQLException ...。
  • Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

# BIO、NIO、AIO 三者之间的区别

tag:

count:17

as:BIO NIO AIO 模型

netty 的 nio 模型,使用场景,以及在个人项目的使用(组件等等)

NIO 核心的三大组件是什么?

nio,bio 和 aio 区别,nio reactor 模型

netty(项目中用到了)的特点介绍

  • BIO (Blocking I/O):BIO 属于同步阻塞 IO 模型 ,同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
  • NIO (Non-blocking/New I/O):NIO 可以看作是 I/O 多路复用模型。同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。
  • AIO (Asynchronous I/O):AIO 也就是 NIO 2。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

image-20240723093557966

# IO 多路复用

tag:

count:30

as:IO 多路复用的系统调用了解吗?(select 调用、epoll 调用)

IO 多路复用,epoll、select、poll,还有哪些运用了该技术?

epoll 原理,怎么实现的多路复用?

epoll 底层

epoll 是怎么实现 IO 多路复用的?

epoll 对比 poll,性能区别

epoll 的 LT 和 ET 模式的使用场景?

epoll 的底层数据结构是什么

epoll select 区别

epoll 的介绍 epoll_wait 为什么用红黑树不用 hash? epoll_wait 是否阻塞? epoll_wait 组件数据结构?

nio 中的 io 多路复用原理

Java IO 模型 Java io 哪些类型,适用场景

netty 的 io 模型

netty 连接断开怎么办

netty 的 io 模型和线程模型,eventloop

netty pipeline context handler 的关系

# java8 新特性

tag:

count:10

as:stream 流在实际项目中有用到过吗?具体做什么?他可以用来排序吗?

jdk 的 stream

java 8 其他方面有哪些改进

java lambda 表达式讲讲具体的使用场景 java stream 讲讲具体的使用场景

# java17 新特性

tag:

count:2

as:jdk 最高版本和新特性

# JDK 和 JRE 区别

tag:

count:3

as:

  • JDK(Java Development Kit),它是功能齐全的 Java SDK,是提供给开发者使用,能够创建和编译 Java 程序的开发套件。它包含了 JRE,同时还包含了编译 java 源码的编译器 javac 以及一些其他工具比如 javadoc(文档注释工具)、jdb(调试器)、jconsole(基于 JMX 的可视化监控⼯具)、javap(反编译工具)等等。
  • JRE(Java Runtime Environment) 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,主要包括 Java 虚拟机(JVM)、Java 基础类库(Class Library)。

JRE 是 Java 运行时环境,仅包含 Java 应用程序的运行时环境和必要的类库。而 JDK 则包含了 JRE,同时还包括了 javac、javadoc、jdb、jconsole、javap 等工具,可以用于 Java 应用程序的开发和调试。

image-20240703142057510

从 JDK 9 开始,就不需要区分 JDK 和 JRE 的关系了,取而代之的是模块系统(JDK 被重新组织成 94 个模块)+ jlink (opens new window) 工具 (随 Java 9 一起发布的新命令行工具,用于生成自定义 Java 运行时映像,该映像仅包含给定应用程序所需的模块) 。并且,从 JDK 11 开始,Oracle 不再提供单独的 JRE 下载

# SPI

SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。

SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。

很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。

image-20240710201740293

那 SPI 和 API 有啥区别?

说到 SPI 就不得不说一下 API 了,从广义上来说它们都属于接口,而且很容易混淆。下面先用一张图说明一下:

image-20240710201806367

一般模块之间都是通过接口进行通讯,那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个 “接口”。

  • 当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。
  • 当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。

通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:

  • 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。
  • 当多个 ServiceLoader 同时 load 时,会有并发问题。

SLF4J (Simple Logging Facade for Java)是 Java 的一个日志门面(接口),其具体实现有几种,比如:Logback、Log4j、Log4j2 等等,而且还可以切换,在切换日志具体实现的时候我们是不需要更改项目代码的,只需要在 Maven 依赖里面修改一些 pom 依赖就好了。

image-20240710212033534

简单 SPI 实现日志 (opens new window)

编辑 (opens new window)
上次更新: 2025/01/01, 10:09:39
Java 集合

Java 集合→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式