Java探索案例

wen java案例 2

本文目录导读:

Java探索案例

  1. 基础与对象:值传递还是引用传递?
  2. 创建了几个对象?">字符串常量池:new String("xxx") 创建了几个对象?
  3. 多线程:可见性与指令重排序(volatile 实战)
  4. 异常处理:finallyreturn 的复杂关系
  5. 类加载机制:静态块与继承的执行顺序
  6. 泛型:类型擦除与桥接方法
  7. 总结与进阶探索方向

这是一份为你准备的 Java 探索案例合集,这些案例涵盖了从基础语法到并发编程、JVM 原理以及新特性的典型场景,每个案例都配有核心知识点代码示例,旨在通过实践帮助你深入理解 Java 的运行机制。


基础与对象:值传递还是引用传递?

核心探索: 验证 Java 只有值传递,没有引用传递。

public class ValuePassingExploration {
    public static void main(String[] args) {
        int a = 10;
        changePrimitive(a);
        System.out.println("基本类型 a 被修改后: " + a); // 输出 10
        StringBuilder sb = new StringBuilder("Hello");
        changeReference(sb);
        System.out.println("引用类型 sb 被修改后: " + sb); // 输出 Hello World
        StringBuilder sb2 = new StringBuilder("Hello");
        changeReferenceNew(sb2);
        System.out.println("尝试重新分配引用后: " + sb2); // 输出 Hello
    }
    private static void changePrimitive(int x) {
        x = 20; // 修改的是副本
    }
    private static void changeReference(StringBuilder builder) {
        builder.append(" World"); // 通过副本修改了指向的对象
    }
    private static void changeReferenceNew(StringBuilder builder) {
        builder = new StringBuilder("World"); // 仅改变了局部变量的指向,对原对象无影响
    }
}

方法无法修改传入的引用本身,但可以通过引用修改堆上的对象。


字符串常量池:new String("xxx") 创建了几个对象?

核心探索: 理解字符串常量池、intern() 方法以及堆与元空间的关系。

public class StringPoolExploration {
    public static void main(String[] args) {
        String s1 = "abc"; // 字面量,放入常量池
        String s2 = new String("abc"); // 堆中创建新对象
        String s3 = s2.intern(); // 尝试放入常量池,发现已有,返回常量池引用
        System.out.println(s1 == s2); // false (堆 vs 常量池)
        System.out.println(s1 == s3); // true (都是常量池引用)
        // 探索动态拼接
        String s4 = "a" + "b" + "c"; // 编译器优化,等同于 "abc"
        System.out.println(s1 == s4); // true
        String a = "a";
        String b = "b";
        String c = "c";
        String s5 = a + b + c; // 运行时拼接,底层使用 StringBuilder,在堆中创建新对象
        System.out.println(s1 == s5); // false
    }
}

new String 会创建 1 个堆对象(如果常量池没有,还会在常量池创建 1 个),运行时字符串拼接会在堆中生成新对象。


多线程:可见性与指令重排序(volatile 实战)

核心探索: 不加 volatile 导致死循环,加上后保证可见性。

public class VolatileExploration {
    // 重点:去掉 volatile,线程 B 可能永远看不到 A 的修改
    private static volatile boolean flag = false;
    // private static boolean flag = false; // 测试用
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("线程 A 启动,等待 flag 变为 true...");
            while (!flag) {
                // 如果不加 volatile,这里可能会无限循环(寄存器缓存)
                // 如果不加 volatile,但这里加上 System.out.println(),由于 println 内部有锁也会刷新缓存
            }
            System.out.println("线程 A 检测到 flag 变化,退出!");
        }).start();
        Thread.sleep(1000);
        new Thread(() -> {
            System.out.println("线程 B 启动,修改 flag...");
            flag = true;
            System.out.println("线程 B 已修改 flag");
        }).start();
    }
}

更深探索:volatile 可以禁止指令重排序,这是实现 DCL(Double-Checked Locking)单例模式的关键。


异常处理:finallyreturn 的复杂关系

核心探索: 理解 finally 的执行时机以及如何影响返回值。

public class FinallyExploration {
    public static void main(String[] args) {
        System.out.println("result 1: " + getValue1()); // 输出 20
        System.out.println("result 2: " + getValue2()); // 输出 10
        System.out.println("result 3: " + getValue3()); // 输出 2
    }
    // case 1: finally 修改返回值
    private static int getValue1() {
        int a = 10;
        try {
            return a;
        } finally {
            a = 20; // 修改的是局部变量,但返回值已经拷贝到栈帧的快照中
            // 对基本类型,修改 a 不影响返回值
        }
    }
    // case 2: finally 覆盖 try 的 return
    @SuppressWarnings("finally")
    private static int getValue2() {
        try {
            return 10;
        } finally {
            return 20; // 覆盖了 try 中的 return
        }
    }
    // case 3: 复杂对象引用
    private static StringBuilder getValue3() {
        StringBuilder sb = new StringBuilder("1");
        try {
            return sb;
        } finally {
            sb.append("0"); // 修改了对象内部状态,返回值指向的对象被改变
            // 但如果 sb = new StringBuilder("2"),则对原返回值无影响
        }
    }
}

finally 总是在 return 之前执行,除非执行 System.exit() 或发生致命错误。


类加载机制:静态块与继承的执行顺序

核心探索: 类初始化的顺序:父类静态块 -> 子类静态块 -> 父类实例代码块/构造器 -> 子类实例代码块/构造器。

class Parent {
    static { System.out.println("Parent 静态块"); }
    { System.out.println("Parent 实例代码块"); }
    public Parent() { System.out.println("Parent 构造器"); }
}
class Child extends Parent {
    static { System.out.println("Child 静态块"); }
    { System.out.println("Child 实例代码块"); }
    public Child() { System.out.println("Child 构造器"); }
}
public class ClassInitExploration {
    public static void main(String[] args) {
        System.out.println("--- 首次创建 Child 对象 ---");
        Child child1 = new Child();
        System.out.println("--- 再次创建 Child 对象 ---");
        Child child2 = new Child();
    }
}

输出结果:

--- 首次创建 Child 对象 ---
Parent 静态块
Child 静态块
Parent 实例代码块
Parent 构造器
Child 实例代码块
Child 构造器
--- 再次创建 Child 对象 ---
Parent 实例代码块
Parent 构造器
Child 实例代码块
Child 构造器

静态块仅执行一次,实例代码块在每次构造对象时执行,并在构造器之前执行。


泛型:类型擦除与桥接方法

核心探索: 验证编译后泛型信息被擦除,并观察桥接方法的存在。

import java.lang.reflect.Method;
class StringBox implements Box<String> {
    private String item;
    @Override
    public void put(String item) {
        this.item = item;
    }
    public static void main(String[] args) {
        // 通过反射查看字节码中的方法
        for (Method method : StringBox.class.getDeclaredMethods()) {
            System.out.println("方法名: " + method.getName() + 
                               ", 参数类型: " + 
                               java.util.Arrays.toString(method.getParameterTypes()));
        }
    }
}
interface Box<T> {
    void put(T item);
}

输出结果(存在桥接方法):

方法名: put, 参数类型: [class java.lang.String]
方法名: put, 参数类型: [class java.lang.Object]  // 这就是编译器生成的桥接方法

泛型在运行时被擦除,桥接方法保证了多态的正确性。


总结与进阶探索方向

案例主题 核心知识点 难度
值传递 栈帧、局部变量表
字符串常量池 元空间、intern、编译优化 ⭐⭐
volatile 可见性 CPU 缓存一致性、MESI 协议 ⭐⭐⭐
finally 返回值 字节码、异常表、atsn 指令 ⭐⭐
类加载顺序 加载、验证、准备、解析、初始化 ⭐⭐
泛型擦除 桥接方法、Signature 属性 ⭐⭐⭐

进一步探索建议:

  1. JVM 监控: 使用 VisualVMjconsole 监控上述代码的内存和线程状态。
  2. 反编译: 使用 javap -c -v 查看 finally 或泛型案例的字节码,你会看到更底层的结构。
  3. AQS 锁: 尝试实现一个简单的 ReentrantLock 案例,探索 LockSupport.park()unpark()

这些案例不仅帮助你通过面试,更能让你真正理解 Java 的底层设计哲学。

抱歉,评论功能暂时关闭!