【Java SE】Java代码块详解

Java代码块详解

代码块概述

什么是代码块?

代码块(Block)是指使用一对花括号{}括起来的一段代码区域。在Java中,代码块可以出现在类中、方法中,甚至可以独立存在。根据声明位置和修饰符的不同,代码块可以分为多种类型,每种类型都有其特定的执行时机和用途。

代码块的分类

Java中的代码块主要分为以下四大类:

  1. 局部代码块 - 定义在方法内部的普通代码块
  2. 实例代码块 - 没有static修饰的代码块(又称构造代码块)
  3. 同步代码块 - 使用synchronized修饰的代码块
  4. 静态代码块 - 使用static修饰的代码块

局部代码块

局部代码块是定义在方法内部的普通代码块,它是最简单的一种代码块。

语法格式

java 复制代码
public void myMethod() {
    // 方法中的其他代码
    
    {
        // 局部代码块的内容
        System.out.println("这是局部代码块");
        int localVar = 100;
    }
    
    // 这里无法访问 localVar
    //System.out.println(localVar);
}

执行时机

局部代码块的执行时机相对简单:

  1. 方法调用时执行:当程序执行到代码块位置时执行
  2. 顺序执行:按照代码的书写顺序执行
  3. 可多次执行:如果方法被多次调用,代码块也会多次执行

变量的作用域规则⭐

在局部代码块中,变量的作用域遵循以下规则:

变量类型 作用域范围 说明
代码块内定义的变量 仅在代码块内有效 出代码块后无法访问
方法参数 整个方法内有效 可在代码块中访问
外部方法变量 整个方法内有效 可在代码块中访问

静态代码块⭐

静态代码块是在类中使用static关键字修饰的代码块,它是Java中最先执行的代码块之一。

语法格式

java 复制代码
public class MyClass {
    static {
        // 静态代码块的内容
        System.out.println("这是静态代码块");
    }
}

执行时机⭐

静态初始化块 是用static关键字修饰的代码块,用于执行类的初始化操作。其特点如下:

  1. 类加载时执行:当JVM第一次加载类时执行
  2. 只执行一次:在整个程序生命周期中仅执行一次
  3. 优先于main方法:在main方法执行之前执行
  4. 顺序执行:如果有多个静态代码块,按定义顺序执行

具体原理双亲委派模型

注意事项

  • 静态代码块中不能直接访问非静态成员
  • 静态代码块中不能使用thissuper关键字
  • 静态代码块中不能抛出受检异常
  • 如果类中包含多个静态代码块,它们会按顺序执行

主要用途

静态代码块常用于以下场景:

  • 初始化静态变量:为静态成员变量赋初始值
  • 加载配置文件:读取properties或XML配置文件
  • 建立数据库连接:创建数据库连接池
  • 注册驱动:如JDBC驱动注册
java 复制代码
public class DatabaseConfig {
    private static String url;
    private static String username;
    private static String password;
    
    // 静态代码块 - 加载配置文件
    static {
        System.out.println("静态代码块开始执行:加载配置文件");
        
        // 模拟读取配置文件
        url = "jdbc:mysql://localhost:3306/mydb";
        username = "root";
        password = "123456";
        
        try {
            // 注册JDBC驱动
            Class.forName("com.mysql.jdbc.Driver");
            System.out.println("数据库驱动注册成功");
        } catch (ClassNotFoundException e) {
            System.out.println("数据库驱动注册失败");
        }
        
        System.out.println("静态代码块执行完成");
    }
    
    public static void main(String[] args) {
        System.out.println("main方法开始执行");
        System.out.println("数据库URL: " + url);
        System.out.println("用户名: " + username);
    }
}

执行结果:

复制代码
静态代码块开始执行:加载配置文件
数据库驱动注册成功
静态代码块执行完成
main方法开始执行
数据库URL: jdbc:mysql://localhost:3306/mydb
用户名: root

实例代码块(构造代码块)

实例代码块是定义在类中但没有static修饰的代码块,它属于对象的级别。由于它在构造方法之前执行,因此也被称为构造代码块

语法格式

java 复制代码
public class MyClass {
    {
        // 实例代码块的内容
        System.out.println("这是实例代码块");
    }
}

执行时机

实例代码块的执行具有以下特点:

  1. 创建对象时执行 :每次使用new关键字创建对象时都会执行
  2. 优先于构造方法:在构造方法之前执行
  3. 每次创建都执行:无论使用哪个构造方法,实例代码块都会执行
  4. 顺序执行:如果有多个实例代码块,按定义顺序执行

主要用途

实例代码块主要用于以下场景:

  • 提取共性代码:将多个构造方法中相同的代码提取出来
  • 初始化实例变量:为实例变量赋初始值
  • 执行对象创建的准备工作:如日志记录、计数统计等
java 复制代码
public class Student {
    private String name;
    private int age;
    private static int count = 0;  // 静态变量,记录创建的对象数量
    
    // 实例代码块
    {
        count++;  // 每创建一个对象,计数器加1
        System.out.println("实例代码块执行:这是创建的第" + count + "个学生对象");
    }
    
    // 无参构造方法
    public Student() {
        System.out.println("无参构造方法执行");
        this.name = "未知";
        this.age = 0;
    }
    
    // 有参构造方法
    public Student(String name, int age) {
        System.out.println("有参构造方法执行");
        this.name = name;
        this.age = age;
    }
    
    public static void main(String[] args) {
        System.out.println("开始创建对象...");
        
        Student stu1 = new Student();
        Student stu2 = new Student("张三", 20);
        Student stu3 = new Student("李四", 22);
    }
}

执行结果:

复制代码
开始创建对象...
实例代码块执行:这是创建的第1个学生对象
无参构造方法执行
实例代码块执行:这是创建的第2个学生对象
有参构造方法执行
实例代码块执行:这是创建的第3个学生对象
有参构造方法执行

实例代码块与构造方法的执行顺序⭐

通过反编译可以得知,实例代码块的代码实际上会被Java编译器复制到每一个构造方法的最前面(在super()调用之后)。因此,执行顺序是:

父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法

同步代码块

同步代码块是使用synchronized关键字修饰的代码块,用于解决多线程并发访问共享资源时的线程安全问题。

语法格式

java 复制代码
public class MyClass {
    public void myMethod() {
        synchronized (lockObject) {
            // 需要同步的代码
            System.out.println("这是同步代码块");
        }
    }
}

执行机制

同步代码块的核心机制是

  1. 获取锁:线程进入同步代码块前必须获取指定对象的锁
  2. 执行代码:只有获得锁的线程才能执行代码块中的内容
  3. 释放锁:代码块执行完毕后自动释放锁
  4. 互斥访问:同一时刻只有一个线程可以执行该代码块

锁对象的选择

同步代码块的锁对象可以是任何Java对象,常见的选择有:

锁对象类型 示例 适用场景
当前对象 synchronized(this) 实例方法中的同步
类对象 synchronized(MyClass.class) 静态方法中的同步
自定义对象 private final Object lock = new Object() 细粒度控制
字符串常量 synchronized("lock") 不建议使用,容易造成死锁
java 复制代码
public class BankAccount {
    private double balance;  // 账户余额
    private final Object lock = new Object();  // 自定义锁对象
    
    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }
    
    // 存款方法
    public void deposit(double amount) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 开始存款: " + amount);
            double newBalance = balance + amount;
            
            // 模拟处理耗时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            balance = newBalance;
            System.out.println(Thread.currentThread().getName() + " 存款完成,当前余额: " + balance);
        }
    }
    
    // 取款方法
    public void withdraw(double amount) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 开始取款: " + amount);
            
            if (balance >= amount) {
                double newBalance = balance - amount;
                
                // 模拟处理耗时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                balance = newBalance;
                System.out.println(Thread.currentThread().getName() + " 取款成功,当前余额: " + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
            }
        }
    }
    
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);
        
        // 创建多个线程同时操作同一个账户
        Thread t1 = new Thread(() -> account.withdraw(800), "张三");
        Thread t2 = new Thread(() -> account.deposit(500), "李四");
        Thread t3 = new Thread(() -> account.withdraw(700), "王五");
        
        t1.start();
        t2.start();
        t3.start();
    }
}

同步代码块的优点

相比于同步方法,同步代码块具有以下优势:

  1. 细粒度控制:可以只同步需要同步的代码,提高并发性能
  2. 灵活选择锁:可以使用任意对象作为锁,实现更复杂的同步逻辑
  3. 减少锁持有时间:只对关键代码加锁,减少线程阻塞时间

各种代码块的执行顺序⭐

例1:继承关系+局部代码块+静态代码块+实例代码块+同步代码块

java 复制代码
class Parent {
    static {
        System.out.println("1. 父类静态代码块");
    }
    
    {
        System.out.println("3. 父类实例代码块");
    }
    
    public Parent() {
        System.out.println("4. 父类构造方法");
    }
}

public class Child extends Parent {
    static {
        System.out.println("2. 子类静态代码块");
    }
    
    {
        System.out.println("5. 子类实例代码块");
    }
    
    public Child() {
        System.out.println("6. 子类构造方法");
    }
    
    public void testMethod() {
        System.out.println("8. 开始执行testMethod方法");
        
        // 局部代码块
        {
            System.out.println("9. 局部代码块");
        }
        
        // 同步代码块
        synchronized (this) {
            System.out.println("10. 同步代码块");
        }
        
        System.out.println("11. testMethod方法执行结束");
    }
    
    public static void main(String[] args) {
        System.out.println("开始创建第一个对象");
        Child child1 = new Child();
        
        System.out.println("\n开始调用方法");
        child1.testMethod();
        
        System.out.println("\n开始创建第二个对象");
        Child child2 = new Child();
    }
}

执行结果:

复制代码
1. 父类静态代码块
2. 子类静态代码块
开始创建第一个对象
3. 父类实例代码块
4. 父类构造方法
5. 子类实例代码块
6. 子类构造方法

开始调用方法
8. 开始执行testMethod方法
9. 局部代码块
10. 同步代码块
11. testMethod方法执行结束

开始创建第二个对象
3. 父类实例代码块
4. 父类构造方法
5. 子类实例代码块
6. 子类构造方法

通过上面的示例,可以总结出Java中各类代码块的完整执行顺序:

  1. 类加载阶段 :父类静态代码块 → 子类静态代码块(只执行一次) 具体原理双亲委派模型
  2. 对象创建阶段:父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法
  3. 方法调用阶段:方法中的普通代码 → 局部代码块 → 同步代码块(如果遇到)

例2:静态代码块和静态变量的顺序

java 复制代码
public class OrderTrick {
    static int x = 10;
    
    static {
        System.out.println("静态代码块1: x = " + x);
        x = 20;
    }
    
    static int y = x + 5;  // y会是多少?
    
    static {
        System.out.println("静态代码块2: x = " + x + ", y = " + y);
        x = 30;
        y = 100;
    }
    
    public static void main(String[] args) {
        System.out.println("main: x = " + x + ", y = " + y);
    }
}
复制代码
静态代码块1: x = 10
静态代码块2: x = 20, y = 25
main: x = 30, y = 100
  • 静态变量声明时的赋值和静态代码块,按照出现顺序合并,后面的赋值会覆盖前面的值

常见问题

Q1:静态代码块中抛出异常会怎样?

java 复制代码
public class ExceptionDemo {
    static {
        // 静态代码块中抛出异常会导致类初始化失败
        if (true) {
            throw new RuntimeException("静态代码块异常");
        }
    }
    
    public static void main(String[] args) {
        try {
            // 访问该类的任何内容都会导致ExceptionInInitializerError
            Class.forName("ExceptionDemo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (ExceptionInInitializerError e) {
            System.out.println("类初始化失败: " + e.getCause());
        }
    }
}

解决方案 :静态代码块中应该避免抛出受检异常,使用try-catch处理可能的异常。

Q2:实例代码块和构造方法谁先执行?

java 复制代码
public class OrderDemo {
    {
        System.out.println("实例代码块");
    }
    
    public OrderDemo() {
        System.out.println("构造方法");
    }
    
    public static void main(String[] args) {
        new OrderDemo();  // 输出:实例代码块 → 构造方法
    }
}

答案:实例代码块总是先于构造方法执行。

Q3:同步代码块使用不当导致死锁

java 复制代码
public class DeadLockDemo {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            System.out.println("method1持有lock1");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            
            synchronized (lock2) {
                System.out.println("method1持有lock2");
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            System.out.println("method2持有lock2");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            
            synchronized (lock1) {  // 与method1相反的顺序导致死锁
                System.out.println("method2持有lock1");
            }
        }
    }
}

解决方案 :始终保持一致的锁获取顺序,或使用tryLock()等高级并发工具。

相关推荐
白云如幻2 小时前
【JDBC】面向对象的思路编写JDBC程序
java·数据库
摇滚侠2 小时前
Java SpringBoot 项目,项目启动后执行的方法,有哪些方式实现
java·开发语言·spring boot
chao_7892 小时前
【hello-agent】ReAct 第一个demo实践
python·agent·react-agent
艾莉丝努力练剑2 小时前
【Linux进程间通信:共享内存】为什么共享内存的 key 值由用户设置
java·linux·运维·服务器·开发语言·数据库·mysql
Reisentyan2 小时前
GoLang Learn Data Day 0
开发语言·rpc·golang
Chengbei112 小时前
AI 自动逆向 JS 加密!自动抓密钥、出报告,彻底解放双手,解决抓包数据包加密难题
开发语言·javascript·人工智能·安全·网络安全·网络攻击模型
NPE~2 小时前
[爬虫]获取某鱼网页版商品数据
爬虫·python·教程·逆向
天若有情6732 小时前
【实战】从零开发企业级 B 端风格字符串值管理系统(Python+MySQL)
开发语言·python·mysql·企业级应用·b端应用
郝学胜-神的一滴2 小时前
深度学习入门全解析:从核心概念到实战基础 | 技术研讨会精华总结
人工智能·python·深度学习·算法·cnn