知识模块
☕ Java 知识模块
一、Java 基础
常用类与工具类

常用类与工具类

Java 提供了许多常用类,其中 StringObject 是最核心的两个类。理解它们的特性和底层原理,是 Java 面试的必考内容。


一、String 类

1.1 String 的特性

String 是 Java 中最常用的类,用于表示字符串。它有以下核心特性:

特性说明
不可变性String 对象一旦创建,值就不能被修改
final 类String 类被 final 修饰,不能被继承
实现接口SerializableComparable<String>CharSequence
存储方式JDK 8:char[];JDK 9+:byte[] + 编码标记
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    // JDK 8:使用 char[] 存储
    // private final char[] value;
    
    // JDK 9+:使用 byte[] 存储,节省空间
    private final byte[] value;
    private final byte coder;  // 编码标记(LATIN1 或 UTF16)
}

1.2 String 的不可变性

不可变(Immutable) 是指字符串对象创建后,其内容不能被修改。

String s = "hello";
s = s + " world";  // 看起来是修改,实际是创建了新对象
 
// 原来的 "hello" 对象仍然存在,s 指向新的 "hello world" 对象

为什么设计为不可变?

原因说明
字符串常量池多个引用可以指向同一个字符串对象,节省内存
线程安全不可变对象天然线程安全,无需同步
HashCode 缓存HashCode 可以缓存,提高 HashMap 等集合的效率
安全性防止被篡改,如类名、文件路径、网络地址等
// 字符串常量池示例
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);  // true,指向常量池同一对象
 
// 如果 String 可变,s1 修改会影响 s2,这是不安全的

1.3 String 的创建方式

方式一:字面量

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);  // true(常量池)

方式二:new 关键字

String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4);  // false(堆中不同对象)
System.out.println(s3.intern() == s1);  // true(intern() 返回常量池引用)

内存结构对比

字面量方式:
┌─────────────────┐
│  栈(引用)      │
│  s1 ────────────┼──→ ┌─────────────────┐
│  s2 ────────────┼──→ │ 字符串常量池     │
└─────────────────┘    │ "hello" 对象     │
                       └─────────────────┘

new 方式:
┌─────────────────┐    ┌─────────────────┐
│  栈(引用)      │    │ 堆内存          │
│  s3 ────────────┼──→ │ String 对象1    │
│  s4 ────────────┼──→ │ String 对象2    │
└─────────────────┘    └────────┬────────┘

                       ┌────────↓────────┐
                       │ 字符串常量池     │
                       │ "hello" 对象     │
                       └─────────────────┘

1.4 字符串常量池

字符串常量池(String Pool)是 JVM 为了节省内存而设计的特殊区域。

位置变化

JDK 版本位置
JDK 6 及之前方法区(永久代)
JDK 7堆内存
JDK 8+堆内存(元空间取代永久代,但字符串池仍在堆)

intern() 方法

intern() 是一个 native 方法,作用是:

  1. 如果常量池中已存在该字符串,返回常量池中的引用
  2. 如果不存在,将字符串添加到常量池,并返回引用
String s1 = new String("hello");
String s2 = s1.intern();  // 将 "hello" 加入常量池
String s3 = "hello";
 
System.out.println(s1 == s2);  // false(s1 在堆,s2 在常量池)
System.out.println(s2 == s3);  // true(都指向常量池)
 
// JDK 6 vs JDK 7 的区别
String s4 = new String("a") + new String("b");
s4.intern();
String s5 = "ab";
 
System.out.println(s4 == s5);  
// JDK 6:false(intern() 复制到常量池)
// JDK 7+:true(intern() 直接引用堆中对象)

1.5 String 常用方法

String s = "Hello, World!";
 
// 长度
s.length();                     // 13
 
// 获取字符
s.charAt(0);                    // 'H'
 
// 截取
s.substring(7);                 // "World!"
s.substring(0, 5);              // "Hello"
 
// 查找
s.indexOf("World");             // 7
s.lastIndexOf("o");             // 8
s.contains("World");            // true
s.startsWith("Hello");          // true
s.endsWith("!");                // true
 
// 大小写转换
s.toUpperCase();                // "HELLO, WORLD!"
s.toLowerCase();                // "hello, world!"
 
// 去除空白
"  hello  ".trim();             // "hello"
 
// 替换
s.replace("World", "Java");     // "Hello, Java!"
s.replaceAll("\\s+", "-");      // 正则替换
 
// 分割
"a,b,c".split(",");             // ["a", "b", "c"]
 
// 拼接
String.join("-", "a", "b", "c"); // "a-b-c"
 
// 比较
"hello".equals("HELLO");        // false
"hello".equalsIgnoreCase("HELLO"); // true
"abc".compareTo("abd");         // -1(字典序比较)
 
// 空判断
String.isEmpty(s);              // false(JDK 6+)
String.isBlank("   ");          // true(JDK 11+,空白也算空)
 
// 类型转换
String.valueOf(123);            // "123"
String.valueOf(true);           // "true"
Integer.parseInt("123");        // 123

1.6 String 拼接方式

方式性能适用场景
+ 拼接低(大量循环时)少量拼接
concat()少量拼接
StringBuilder大量拼接、单线程
StringBuffer大量拼接、多线程
String.join()集合/数组拼接
// 1. + 拼接(编译器优化为 StringBuilder)
String s = "a" + "b" + "c";  // 编译后:String s = "abc";
String s = "a" + b + "c";    // 编译后:new StringBuilder().append("a").append(b).append("c")
 
// ⚠️ 循环中使用 + 效率低
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i;  // 每次循环都创建新的 StringBuilder 和 String
}
 
// ✅ 使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();
 
// 2. concat()
"hello".concat(" world");  // "hello world"
 
// 3. StringBuilder(推荐)
StringBuilder sb = new StringBuilder();
sb.append("hello").append(" ").append("world");
String s = sb.toString();
 
// 4. StringBuffer(线程安全)
StringBuffer sb = new StringBuffer();
sb.append("hello");

1.7 常见面试题

Q1: String s = new String("abc") 创建了几个对象?

A:

  • 1 个或 2 个
    • 如果常量池中没有 "abc":创建 2 个(堆中 1 个 + 常量池 1 个)
    • 如果常量池中已有 "abc":创建 1 个(堆中 1 个)

Q2: String 为什么设计为不可变?

A:

  1. 字符串常量池:节省内存,多个引用可共享同一对象
  2. 线程安全:不可变对象天然线程安全
  3. HashCode 缓存:HashCode 可缓存,提高集合效率
  4. 安全性:防止敏感数据被篡改

Q3: String、StringBuilder、StringBuffer 的区别?

A:

对比项StringStringBuilderStringBuffer
可变性不可变可变可变
线程安全安全(不可变)不安全安全(synchronized)
性能最低最高中等
适用场景少量字符串单线程大量操作多线程大量操作

Q4: 以下代码输出什么?

String s1 = "hello";
String s2 = "he" + new String("llo");
System.out.println(s1 == s2);

A: 输出 falsenew String("llo") 在堆中创建对象,"he" + 堆对象 结果也在堆中,不会进入常量池。

Q5: 如何判断两个字符串是否相等?

A: 使用 equals() 方法,不要用 ==

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);       // false(比较地址)
System.out.println(s1.equals(s2));  // true(比较内容)

二、StringBuilder 与 StringBuffer

2.1 StringBuilder

StringBuilder 是可变的字符串序列,JDK 5 引入,用于解决 String 拼接性能问题。

// 继承关系
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

核心方法

StringBuilder sb = new StringBuilder();
 
// 追加
sb.append("hello");
sb.append(' ');
sb.append(123);  // 支持多种类型
 
// 插入
sb.insert(5, ", world");  // 在位置 5 插入
 
// 删除
sb.delete(5, 12);  // 删除 [5, 12) 范围
sb.deleteCharAt(0);  // 删除指定位置
 
// 替换
sb.replace(0, 5, "hi");
 
// 反转
sb.reverse();
 
// 设置长度
sb.setLength(0);  // 清空
 
// 转换为 String
String s = sb.toString();

扩容机制

// 初始容量 16
StringBuilder sb = new StringBuilder();
 
// 指定初始容量
StringBuilder sb = new StringBuilder(100);
 
// 扩容公式:(旧容量 * 2) + 2
// 如:16 → 34 → 70 → 142 ...

2.2 StringBuffer

StringBufferStringBuilder 的线程安全版本,所有方法都加了 synchronized

public synchronized StringBuffer append(String str) { ... }
public synchronized StringBuffer insert(int offset, String str) { ... }
// 所有 public 方法都加了 synchronized

2.3 性能对比

// 性能测试
int count = 100000;
 
// String:极慢
String s = "";
for (int i = 0; i < count; i++) {
    s += i;
}
 
// StringBuilder:最快
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
    sb.append(i);
}
 
// StringBuffer:稍慢(同步开销)
StringBuffer sb = new StringBuffer();
for (int i = 0; i < count; i++) {
    sb.append(i);
}

2.4 使用建议

场景推荐
少量字符串操作String
大量字符串操作(单线程)StringBuilder
大量字符串操作(多线程)StringBuffer
方法链式调用StringBuilder
// 链式调用
String result = new StringBuilder()
    .append("Name: ")
    .append(name)
    .append(", Age: ")
    .append(age)
    .toString();

三、Object 类

3.1 Object 类概述

Object 是 Java 所有类的根类,每个类都直接或间接继承自 Object

public class Object {
    // 共 11 个方法(不含构造器)
}

3.2 Object 类的方法

方法说明
getClass()返回对象的运行时类(Class 对象)
hashCode()返回对象的哈希码
equals(Object obj)判断是否与另一个对象相等
clone()创建并返回对象的副本
toString()返回对象的字符串表示
notify()唤醒等待该对象锁的一个线程
notifyAll()唤醒等待该对象锁的所有线程
wait()使当前线程等待
wait(long timeout)等待指定毫秒数
wait(long timeout, int nanos)等待指定毫秒数+纳秒数
finalize()垃圾回收时调用(已废弃)

四、equals() 与 hashCode()

4.1 equals() 方法

equals() 用于判断两个对象是否"相等"。

默认实现

// Object 类中的默认实现
public boolean equals(Object obj) {
    return (this == obj);  // 默认比较地址
}

重写 equals()

public class Person {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        // 1. 判断是否为同一对象
        if (this == obj) return true;
        
        // 2. 判断是否为 null 或类型不同
        if (obj == null || getClass() != obj.getClass()) return false;
        
        // 3. 类型转换并比较属性
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
}

equals() 的 5 个特性(Java 规范)

特性说明
自反性x.equals(x) 必须返回 true
对称性x.equals(y)true 时,y.equals(x) 也必须为 true
传递性x.equals(y)y.equals(z),则 x.equals(z) 必须为 true
一致性多次调用 x.equals(y) 结果必须一致
非空性x.equals(null) 必须返回 false

4.2 hashCode() 方法

hashCode() 返回对象的哈希码(整数),用于散列存储结构(HashMap、HashSet 等)。

默认实现

// Object 类中的默认实现(native 方法)
public native int hashCode();

hashCode() 的约定

  1. 一致性:对象未修改时,多次调用返回相同值
  2. 相等对象必须相等哈希码equals() 返回 true,则 hashCode() 必须相同
  3. 不等对象可以不等哈希码equals() 返回 falsehashCode() 可以相同(哈希冲突)

4.3 为什么重写 equals() 必须重写 hashCode()?

核心原因:违反了 Java 规范——相等对象必须有相等的哈希码。

// 反例:只重写 equals(),不重写 hashCode()
public class Person {
    private String name;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return Objects.equals(name, person.name);
    }
    
    // 没有重写 hashCode()
}
 
// 问题演示
Person p1 = new Person("张三");
Person p2 = new Person("张三");
 
System.out.println(p1.equals(p2));  // true
System.out.println(p1.hashCode() == p2.hashCode());  // false!
 
// HashSet/HashMap 会出问题
Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size());  // 2(期望是 1,因为 p1.equals(p2))

正确做法

public class Person {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

4.4 == 与 equals() 的区别

对比项==equals()
基本类型比较值不适用
引用类型比较地址默认比较地址,可重写比较内容
可重写不可可重写
String s1 = new String("hello");
String s2 = new String("hello");
 
System.out.println(s1 == s2);       // false(地址不同)
System.out.println(s1.equals(s2));  // true(内容相同)
 
int a = 10;
int b = 10;
System.out.println(a == b);  // true(基本类型比较值)

五、toString() 方法

5.1 默认实现

// Object 类中的默认实现
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
 
// 输出示例:com.example.Person@1a2b3c

5.2 重写 toString()

public class Person {
    private String name;
    private int age;
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
 
Person p = new Person("张三", 25);
System.out.println(p);  // Person{name='张三', age=25}
 
// IDEA 自动生成
@Override
public String toString() {
    return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
}
 
// Lombok 注解
@ToString
public class Person {
    private String name;
    private int age;
}

5.3 使用场景

  • 日志输出
  • 调试信息
  • 错误信息

六、clone() 方法

6.1 浅拷贝与深拷贝

类型说明
浅拷贝复制对象本身和基本类型属性,引用类型属性仍指向原对象
深拷贝复制对象本身和所有属性(包括引用类型指向的对象)
浅拷贝:
┌─────────────┐       ┌─────────────┐
│   原对象     │       │   新对象     │
│  name: "张三"│       │  name: "张三"│
│  address ───┼──┐    │  address ───┼──┐
└─────────────┘  │    └─────────────┘  │
                 │                     │
                 └──→ ┌─────────────┐ ←┘
                      │ Address 对象 │(共享)
                      │  city: "北京" │
                      └─────────────┘

深拷贝:
┌─────────────┐       ┌─────────────┐
│   原对象     │       │   新对象     │
│  name: "张三"│       │  name: "张三"│
│  address ───┼──┐    │  address ───┼──┐
└─────────────┘  │    └─────────────┘  │
                 │                     │
                 ↓                     ↓
        ┌─────────────┐       ┌─────────────┐
        │ Address 对象 │       │ Address 对象 │(独立)
        │  city: "北京" │       │  city: "北京" │
        └─────────────┘       └─────────────┘

6.2 实现克隆

方式一:实现 Cloneable 接口(浅拷贝)

public class Person implements Cloneable {
    private String name;
    private int age;
    private Address address;  // 引用类型
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 浅拷贝
    }
}
 
public class Address {
    private String city;
    // ...
}
 
// 测试
Person p1 = new Person("张三", 25, new Address("北京"));
Person p2 = (Person) p1.clone();
 
System.out.println(p1 == p2);  // false(对象不同)
System.out.println(p1.getAddress() == p2.getAddress());  // true(引用共享)

方式二:深拷贝

// 方式1:递归克隆
public class Person implements Cloneable {
    private String name;
    private Address address;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) address.clone();  // 递归克隆
        return cloned;
    }
}
 
public class Address implements Cloneable {
    private String city;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
 
// 方式2:序列化/反序列化
public class Person implements Serializable {
    private String name;
    private Address address;
    
    public Person deepCopy() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Person) ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
 
// 方式3:构造器复制
public class Person {
    private String name;
    private Address address;
    
    public Person(Person other) {
        this.name = other.name;
        this.address = new Address(other.address);  // 新建对象
    }
}

6.3 Cloneable 接口

Cloneable 是一个标记接口(Marker Interface),没有任何方法。

public interface Cloneable {
    // 空接口,仅作为标记
}

如果不实现 Cloneable 接口,调用 clone() 会抛出 CloneNotSupportedException


七、getClass() 方法

7.1 获取 Class 对象

// 方式1:Object.getClass()
Person p = new Person();
Class<?> clazz1 = p.getClass();
 
// 方式2:类名.class
Class<?> clazz2 = Person.class;
 
// 方式3:Class.forName()
Class<?> clazz3 = Class.forName("com.example.Person");
 
// 三种方式获取的是同一个 Class 对象
System.out.println(clazz1 == clazz2);  // true
System.out.println(clazz2 == clazz3);  // true

7.2 Class 对象的作用

Class<?> clazz = Person.class;
 
// 获取类名
clazz.getName();        // "com.example.Person"
clazz.getSimpleName();  // "Person"
 
// 获取父类
clazz.getSuperclass();
 
// 获取接口
clazz.getInterfaces();
 
// 获取字段
clazz.getDeclaredFields();
 
// 获取方法
clazz.getDeclaredMethods();
 
// 创建实例
clazz.newInstance();  // 已废弃
clazz.getDeclaredConstructor().newInstance();  // 推荐

八、finalize() 方法(已废弃)

8.1 说明

finalize() 在对象被垃圾回收前调用,用于释放资源。

@Override
protected void finalize() throws Throwable {
    try {
        // 释放资源
    } finally {
        super.finalize();
    }
}

8.2 为什么废弃?

问题说明
不可靠不保证一定会执行
性能差影响垃圾回收效率
安全风险可能导致对象"复活"
替代方案try-with-resourcesCleaner
// 推荐方式:try-with-resources
try (FileInputStream fis = new FileInputStream("test.txt")) {
    // 使用资源
} // 自动关闭
 
// JDK 9+:Cleaner
Cleaner cleaner = Cleaner.create();
cleaner.register(obj, () -> {
    // 清理逻辑
});

九、综合面试题

Q1: String s = "a" + "b" + "c" 编译后是什么?

A: 编译后是 String s = "abc"。编译器会在编译期优化常量拼接。

Q2: String s = new String("a") + new String("b") 创建了几个对象?

A: 创建了 5 个对象

  • 2 个 String 对象("a" 和 "b")
  • 2 个堆中 String 对象(new String("a") 和 new String("b"))
  • 1 个 StringBuilder 对象(用于拼接)
  • 注:JDK 9+ 后,"a" 和 "b" 可能不会在常量池创建

Q3: 如何避免创建重复的字符串对象?

A: 使用 intern() 方法:

String s1 = new String("hello").intern();
String s2 = "hello";
System.out.println(s1 == s2);  // true

Q4: Object 类有哪些方法是线程相关的?

A: wait()notify()notifyAll(),用于线程间通信。

Q5: 为什么 String 的 hashCode() 可以缓存?

A: 因为 String 不可变,hashCode 计算一次后永远不会变。

public final class String {
    private int hash;  // 缓存 hashCode
    
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            hash = h = isLatin1() ? StringLatin1.hashCode(value)
                                  : StringUTF16.hashCode(value);
        }
        return h;
    }
}

十、总结

类/方法核心要点面试关键词
String不可变、常量池、final 类intern、不可变性、拼接优化
StringBuilder可变、单线程高效append、扩容
StringBuffer可变、线程安全synchronized
equals()比较内容、重写规则对称性、传递性
hashCode()哈希码、与 equals 关系哈希一致性、HashSet/HashMap
clone()浅拷贝、深拷贝Cloneable、引用复制
toString()字符串表示调试、日志

最后更新:2026年3月2日