沉梦听雨的编程指南 沉梦听雨的编程指南
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • RPC
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 多媒体框架
  • 虚拟机
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)

沉梦听雨

时间是最好的浸渍剂,而沉淀是最好的提纯器🚀
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • RPC
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 多媒体框架
  • 虚拟机
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)
  • 基础篇

    • Java基础小结
    • Java基本数据类型
    • 面向对象基础小结
    • Java常见类详解
      • Object
        • Object 类的常见方法有哪些?
        • == 和 equals() 的区别
        • 为什么 Java 中只有值传递?
        • hashCode() 有什么用?
        • 为什么要有 hashCode?
        • 为什么重写 equals() 时必须重写 hashCode() 方法?
        • 重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题?
      • String
        • String、StringBuffer 和 StringBuilder 的区别是什么?
        • String 为什么是不可变的?
        • 字符串拼接用 + 还是 StringBuilder?
        • String#equals() 和 Object#equals() 有何区别?
        • 字符串常量池的作用了解吗?
        • String str = new String("aaa");会创建几个字符串对象?
        • String.intern() 方法有什么作用?
        • String 类型的变量和常量做 + 运算时发生了什么?
      • toString 和 String.valueOf
        • 默认的 toString() 方法
    • Java异常详解
    • Java泛型核心知识总结
    • Java反射详解
    • Java SPI机制详解
    • 聊聊什么是IO流
    • jdk指令
  • 集合篇

  • 并发篇

  • JVM

  • 新特性

  • Java核心技术卷I

  • Java
  • 基础篇
沉梦听雨
2023-06-24
目录

Java常见类详解

# Java 常见类详解

# Object

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

Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:

/**
 * 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

# == 和 equals() 的区别

1、== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

2、equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。

equals() 方法存在两种使用情况:

  1. 类没有重写 equals() 方法时:

    • 等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。
    • 即比较的是内存地址。
  2. 类重写了 equals()方法:

    • 一般我们都重写 equals() 方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即认为这两个对象相等)。

    • 即比较的是内容,比如 String 中的 equals 方法就是被重写过的。

String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb); // true
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
System.out.println(42 == 42.0); // true
1
2
3
4
5
6
7
8

# 为什么 Java 中只有值传递?

Java 中只有值传递,是因为 Java 中的变量(包括基本类型和引用类型)实际上只是存储在内存中的值,并没有直接指向对象的内存地址。

# hashCode() 有什么用?

hashCode() 方法是 Java Object 类中的一个方法,主要用于 计算对象的哈希值,以便在基于哈希的数据结构(如 HashMap、HashSet、HashTable)中高效存储和查找对象,同时也经常用于对象的比较和判等。

# 为什么要有 hashCode?

  • 因为对于一个类的实例,如果要将其存储到哈希表中,就需要实现 hashCode() 方法,以便在计算哈希值时能够正确地反映对象的属性。
  • 同时,由于哈希表中可能会有哈希冲突(即不同对象的哈希码相同),因此还需要实现 equals() 方法来判断两个对象是否相等。

hashCode() 和 equals()都是用于比较两个对象是否相等。

1、那为什么 JDK 还要同时提供这两个方法呢?

这是因为在一些容器(比如 HashMap、HashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高。

也就是说 hashCode 帮助我们大大缩小了查找成本。

2、那为什么不只提供 hashCode() 方法呢?

这是因为两个对象的 hashCode 值相等并不代表两个对象就相等。

3、那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。

越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。

总结下来就是:

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

# 为什么重写 equals() 时必须重写 hashCode() 方法?

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

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

# 重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题?

如果在重写 equals() 方法的同时没有重写 hashCode() 方法,那么在将对象存储到 HashMap 中时,可能会出现以下问题:

  1. 相等的对象返回不同的哈希码:如果两个对象在 equals() 方法中被认为是相等的,但是它们的 hashCode() 方法返回的哈希码不同,那么它们将会被存储在 HashMap 中的不同位置,这样可能会导致在查找或删除元素时出现问题。
  2. 不同的对象返回相同的哈希码:如果两个对象在 equals() 方法中被认为不相等,但是它们的 hashCode() 方法返回的哈希码相同,那么它们将会被存储在 HashMap 中的同一个位置,这样可能会导致在查找或删除元素时出现问题。

简单来说就是会出现两个问题:无法正常存储对象和无法正常查找对象。

因此,在重写 equals() 方法的同时,也应该重写 hashCode() 方法,以确保相等的对象具有相同的哈希码,不相等的对象具有不同的哈希码。这样可以保证 HashMap 的正确性和性能。

# String

# String、StringBuffer 和 StringBuilder 的区别是什么?

  • String 是不可变的,每次对字符串操作都会生成一个新的字符串对象;
  • 而 StringBuffer 和 StringBuilder 是可变,可以在原有对象的基础上进行修改,因此能够提高程序的执行效率。

String 中的对象是不可变的,也就可以理解为常量,线程安全。

StringBuffer 是线程安全的,它的方法都是同步的,依次可以在多线程环境下安全使用但执行速度较慢;

而 StringBuilder 是线程不安全的,它的方法不是同步的,因此在单线程环境下的执行速度比 StringBuffer 快。

# String 为什么是不可变的?

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

除此之外,String 对象的不可变是由于对 String 类型的所有改变内部存储结构的操作都会 new 出一个新的 String 对象。

# 字符串拼接用 + 还是 StringBuilder?

Java 语言本身并不支持运算符重载,+ 和 += 是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。

字符串对象通过 + 的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

需要注意的是:

如果在循环内使用 + 进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。

即每循环一次就会创建一个 StringBuilder 对象

String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
    s += arr[i];
}
System.out.println(s);
1
2
3
4
5
6

如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder(); // 使用 StringBuilder 对象
for (String value : arr) {
    s.append(value);
}
System.out.println(s);
1
2
3
4
5
6

# String#equals() 和 Object#equals() 有何区别?

  • String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。
  • Object 的 equals 方法是比较的对象的内存地址

# 字符串常量池的作用了解吗?

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

# String str = new String("aaa");会创建几个字符串对象?

会创建 1 或 2 个字符串对象。

创建 2 个的情况

  1. 第一个字符串对象是 "aaa",它是在编译期间就创建好的,存储在字符串常量池中。
  2. 第二个字符串对象是 new String("aaa"),它是在运行期间通过 new 关键字创建的。这个对象会在堆内存中开辟一个新的空间,用于存储字符串 "aaa" 的拷贝。

创建 1 个的情况

但是,如果字符串常量池中已经存在了一个值为 "aaa" 的字符串,那么在执行 new String("aaa") 时,JVM 会先在字符串常量池中查找是否存在相同值的字符串,如果存在,则直接返回该字符串的引用,不会再创建一个新的对象。这种情况下,只会创建一个字符串对象。

# String.intern() 方法有什么作用?

String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中。

可以简单分为两种情况:

  • 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
  • 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
// 在常量池中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# String 类型的变量和常量做 + 运算时发生了什么?

1、字符串不加 final 关键字拼接的情况:

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; // 常量池中的对象
String str4 = str1 + str2;   // 在堆上创建的新的对象
String str5 = "string";
System.out.println(str3 == str4); // false
System.out.println(str3 == str5); // true
System.out.println(str4 == str5); // false
1
2
3
4
5
6
7
8

str3 == str5

这主要是因为在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。

2、字符串使用 final 关键字之后拼接的情况:

被 final 关键字修改之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。

final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing"; // 常量池中的对象
String d = str1 + str2;   // 常量池中的对象
System.out.println(c == d); // true
1
2
3
4
5
6

如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。

示例代码(str2 在运行时才能确定其值):

final String str1 = "str";
final String str2 = getStr();
String c = "str" + "ing"; // 常量池中的对象
String d = str1 + str2;   // 在堆上创建的新的对象
System.out.println(c == d); // false

public static String getStr() {
      return "ing";
}
1
2
3
4
5
6
7
8
9

# toString 和 String.valueOf

toString() 方法和 String.valueOf() 方法在 Java 中用于将对象转换为字符串表示形式。

区别

  • toString() 方法是一个实例方法,必须通过具体的对象调用。它通常用于自定义类,可以根据需要自定义返回的字符串格式。
  • String.valueOf() 方法是一个静态方法,可以直接通过类名调用。它适用于将各种类型的数据转换为字符串,包括基本数据类型、对象和 null。
  • 如果对象为 null,toString() 方法会抛出 NullPointerException 异常,而 String.valueOf() 方法会返回字符串 "null"。

# 默认的 toString() 方法

  1. 如果在自定义类中没有重写 toString() 方法,将使用 Object 类中的默认实现。
  2. 默认 toString() 方法返回一个由类名、@ 符号和对象的哈希码组成的字符串,例如:"ClassName@HashCode"。
上次更新: 2025/3/20 16:42:28
面向对象基础小结
Java异常详解

← 面向对象基础小结 Java异常详解→

Theme by Vdoing | Copyright © 2023-2025 沉梦听雨 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式