1. 引言

在Java编程中,类的代码块和枚举是两个重要但容易被忽视的特性。代码块用于控制类的初始化过程,而枚举则提供了一种类型安全的方式来表示固定数量的常量。本文将结合代码示例,详细讲解这两种特性的工作原理、使用场景和最佳实践。

2. 类的代码块详解

2.1 什么是代码块

代码块(Code Block)是Java中用于组织代码的一种结构,它可以包含在类中,用于执行初始化操作。根据定义位置和执行时机的不同,代码块主要分为以下几种类型:

  1. 实例初始化块(Instance Initializer Block)
  2. 静态初始化块(Static Initializer Block)
  3. 局部代码块(Local Block)

2.2 实例初始化块

实例初始化块在每次创建对象时执行,先于构造器执行。如果类中有多个实例初始化块,它们将按照在类中出现的顺序依次执行。

public class InstanceBlockDemo {
    private int x;
    private String name;
    
    // 实例初始化块1
    {
        System.out.println("第一个实例初始化块执行");
        x = 10;
    }
    
    // 实例初始化块2
    {
        System.out.println("第二个实例初始化块执行");
        name = "默认名称";
    }
    
    public InstanceBlockDemo() {
        System.out.println("构造器执行,x=" + x + ", name=" + name);
    }
    
    public InstanceBlockDemo(String customName) {
        this();
        this.name = customName;
        System.out.println("带参数的构造器执行,name=" + name);
    }
    
    public static void main(String[] args) {
        System.out.println("创建第一个对象:");
        InstanceBlockDemo obj1 = new InstanceBlockDemo();
        
        System.out.println("\n创建第二个对象:");
        InstanceBlockDemo obj2 = new InstanceBlockDemo("自定义名称");
    }
}

输出结果:

创建第一个对象:
第一个实例初始化块执行
第二个实例初始化块执行
构造器执行,x=10, name=默认名称

创建第二个对象:
第一个实例初始化块执行
第二个实例初始化块执行
构造器执行,x=10, name=默认名称
带参数的构造器执行,name=自定义名称

2.3 静态初始化块

静态初始化块在类加载时执行,且只执行一次。它用于初始化静态变量或执行只需要执行一次的初始化操作。

public class StaticBlockDemo {
    private static int staticCounter;
    private static List<String> dataList;
    
    // 静态初始化块
    static {
        System.out.println("静态初始化块执行 - 开始");
        staticCounter = 0;
        dataList = new ArrayList<>();
        dataList.add("初始化数据1");
        dataList.add("初始化数据2");
        
        // 可以执行复杂的初始化逻辑
        try {
            // 模拟加载配置文件
            System.out.println("加载配置文件...");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("静态初始化块执行 - 结束");
    }
    
    // 实例初始化块
    {
        staticCounter++;
        System.out.println("实例初始化块执行,当前对象编号:" + staticCounter);
    }
    
    public StaticBlockDemo() {
        System.out.println("构造器执行");
    }
    
    public static void main(String[] args) {
        System.out.println("主方法开始执行");
        
        System.out.println("\n创建第一个对象:");
        StaticBlockDemo obj1 = new StaticBlockDemo();
        
        System.out.println("\n创建第二个对象:");
        StaticBlockDemo obj2 = new StaticBlockDemo();
        
        System.out.println("\n静态数据列表:" + dataList);
        System.out.println("总共创建的对象数:" + staticCounter);
    }
}

输出结果:

静态初始化块执行 - 开始
加载配置文件...
静态初始化块执行 - 结束
主方法开始执行

创建第一个对象:
实例初始化块执行,当前对象编号:1
构造器执行

创建第二个对象:
实例初始化块执行,当前对象编号:2
构造器执行

静态数据列表:[初始化数据1, 初始化数据2]
总共创建的对象数:2

2.4 执行顺序总结

当创建对象时,代码块的执行顺序如下:

  1. 父类静态初始化块(如果存在继承关系)
  2. 子类静态初始化块
  3. 父类实例初始化块
  4. 父类构造器
  5. 子类实例初始化块
  6. 子类构造器
class Parent {
    static {
        System.out.println("父类静态初始化块");
    }
    
    {
        System.out.println("父类实例初始化块");
    }
    
    public Parent() {
        System.out.println("父类构造器");
    }
}

class Child extends Parent {
    static {
        System.out.println("子类静态初始化块");
    }
    
    {
        System.out.println("子类实例初始化块");
    }
    
    public Child() {
        System.out.println("子类构造器");
    }
}

public class ExecutionOrderDemo {
    public static void main(String[] args) {
        System.out.println("第一次创建子类对象:");
        Child child1 = new Child();
        
        System.out.println("\n第二次创建子类对象:");
        Child child2 = new Child();
    }
}

输出结果:

第一次创建子类对象:
父类静态初始化块
子类静态初始化块
父类实例初始化块
父类构造器
子类实例初始化块
子类构造器

第二次创建子类对象:
父类实例初始化块
父类构造器
子类实例初始化块
子类构造器

3. 枚举详解

3.1 枚举的基本概念

枚举(Enum)是Java 5引入的一种特殊的数据类型,用于定义一组命名的常量。与使用整数常量或字符串常量相比,枚举提供了更好的类型安全性和可读性。

3.2 基本枚举定义

// 简单的枚举定义
public enum Day {
    MONDAY,    // 周一
    TUESDAY,   // 周二
    WEDNESDAY, // 周三
    THURSDAY,  // 周四
    FRIDAY,    // 周五
    SATURDAY,  // 周六
    SUNDAY     // 周日
}

public class BasicEnumDemo {
    public static void main(String[] args) {
        // 使用枚举
        Day today = Day.MONDAY;
        System.out.println("今天是:" + today);
        
        // 遍历所有枚举值
        System.out.println("\n一周的所有天:");
        for (Day day : Day.values()) {
            System.out.println(day);
        }
        
        // 获取枚举的序号
        System.out.println("\n枚举序号:");
        System.out.println("MONDAY的序号:" + Day.MONDAY.ordinal());
        System.out.println("SUNDAY的序号:" + Day.SUNDAY.ordinal());
        
        // 根据字符串获取枚举
        Day parsedDay = Day.valueOf("FRIDAY");
        System.out.println("\n解析的枚举:" + parsedDay);
    }
}

3.3 带属性和方法的枚举

枚举可以像类一样拥有属性、构造器和方法,这使得枚举更加灵活和强大。

public enum Planet {
    // 枚举常量,调用构造器
    MERCURY("水星", 3.303e+23, 2.4397e6),
    VENUS("金星", 4.869e+24, 6.0518e6),
    EARTH("地球", 5.976e+24, 6.37814e6),
    MARS("火星", 6.421e+23, 3.3972e6),
    JUPITER("木星", 1.9e+27, 7.1492e7),
    SATURN("土星", 5.688e+26, 6.0268e7),
    URANUS("天王星", 8.686e+25, 2.5559e7),
    NEPTUNE("海王星", 1.024e+26, 2.4746e7);
    
    // 枚举属性
    private final String chineseName;
    private final double mass;   // 质量(千克)
    private final double radius; // 半径(米)
    
    // 枚举构造器(必须是private或package-private)
    Planet(String chineseName, double mass, double radius) {
        this.chineseName = chineseName;
        this.mass = mass;
        this.radius = radius;
    }
    
    // 枚举方法
    public double getMass() {
        return mass;
    }
    
    public double getRadius() {
        return radius;
    }
    
    public String getChineseName() {
        return chineseName;
    }
    
    // 计算表面重力
    public double surfaceGravity() {
        final double G = 6.67300E-11; // 万有引力常数
        return G * mass / (radius * radius);
    }
    
    // 计算物体在行星表面的重量
    public double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
    
    // 静态方法:根据中文名查找行星
    public static Planet findByChineseName(String name) {
        for (Planet planet : values()) {
            if (planet.chineseName.equals(name)) {
                return planet;
            }
        }
        throw new IllegalArgumentException("未找到名为 " + name + " 的行星");
    }
}

public class AdvancedEnumDemo {
    public static void main(String[] args) {
        double earthWeight = 75; // 地球上的体重(千克)
        
        System.out.println("行星信息比较:");
        for (Planet planet : Planet.values()) {
            System.out.printf("%s(%s):质量=%.2e kg,半径=%.2e m,重力=%.2f m/s²,体重=%.2f kg%n",
                planet,
                planet.getChineseName(),
                planet.getMass(),
                planet.getRadius(),
                planet.surfaceGravity(),
                planet.surfaceWeight(earthWeight));
        }
        
        // 使用静态方法
        Planet earth = Planet.findByChineseName("地球");
        System.out.println("\n查找到的行星:" + earth);
        
        // 使用switch语句
        System.out.println("\n行星分类:");
        classifyPlanet(Planet.EARTH);
        classifyPlanet(Planet.JUPITER);
    }
    
    private static void classifyPlanet(Planet planet) {
        switch (planet) {
            case MERCURY:
            case VENUS:
            case EARTH:
            case MARS:
                System.out.println(planet.getChineseName() + "是类地行星");
                break;
            case JUPITER:
            case SATURN:
                System.out.println(planet.getChineseName() + "是气态巨行星");
                break;
            case URANUS:
            case NEPTUNE:
                System.out.println(planet.getChineseName() + "是冰巨星");
                break;
        }
    }
}

3.4 枚举实现接口

枚举可以实现接口,这使得枚举可以拥有多态行为。

// 定义操作接口
interface Operation {
    double apply(double x, double y);
}

// 实现接口的枚举
public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            if (y == 0) {
                throw new ArithmeticException("除数不能为零");
            }
            return x / y;
        }
    };
    
    private final String symbol;
    
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }
    
    @Override
    public String toString() {
        return symbol;
    }
}

// 扩展的操作枚举
public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    MOD("%") {
        public double apply(double x, double y) {
            return x % y;
        }
    };
    
    private final String symbol;
    
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
    
    @Override
    public String toString() {
        return symbol;
    }
}

public class EnumInterfaceDemo {
    public static void main(String[] args) {
        double x = 10;
        double y = 3;
        
        System.out.println("基本运算:");
        for (BasicOperation op : BasicOperation.values()) {
            System.out.printf("%.1f %s %.1f = %.2f%n", 
                x, op, y, op.apply(x, y));
        }
        
        System.out.println("\n扩展运算:");
        for (ExtendedOperation op : ExtendedOperation.values()) {
            System.out.printf("%.1f %s %.1f = %.2f%n", 
                x, op, y, op.apply(x, y));
        }
        
        // 使用接口类型
        System.out.println("\n使用接口类型:");
        testOperation(BasicOperation.PLUS, x, y);
        testOperation(ExtendedOperation.EXP, x, y);
    }
    
    private static void testOperation(Operation op, double x, double y) {
        System.out.printf("测试 %s:%.1f %s %.1f = %.2f%n",
            op.getClass().getSimpleName(), x, "?", y, op.apply(x, y));
    }
}

3.5 枚举的单例模式

枚举是实现单例模式的最佳方式之一,它天生就是线程安全的,并且能防止反射攻击和序列化问题。

// 使用枚举实现单例模式
public enum Singleton {
    INSTANCE;
    
    private int counter = 0;
    
    // 单例的业务方法
    public void doSomething() {
        counter++;
        System.out.println("Singleton实例被调用,计数器:" + counter);
    }
    
    public int getCounter() {
        return counter;
    }
}

// 传统的单例实现(对比)
class TraditionalSingleton {
    private static TraditionalSingleton instance;
    private int counter = 0;
    
    private TraditionalSingleton() {
        // 防止反射攻击
        if (instance != null) {
            throw new RuntimeException("请使用getInstance()方法获取实例");
        }
    }
    
    public static synchronized TraditionalSingleton getInstance() {
        if (instance == null) {
            instance = new TraditionalSingleton();
        }
        return instance;
    }
    
    public void doSomething() {
        counter++;
        System.out.println("TraditionalSingleton实例被调用,计数器:" + counter);
    }
}

public class SingletonEnumDemo {
    public static void main(String[] args) {
        System.out.println("=== 枚举单例测试 ===");
        Singleton singleton1 = Singleton.INSTANCE;
        Singleton singleton2 = Singleton.INSTANCE;
        
        singleton1.doSomething();
        singleton2.doSomething();
        
        System.out.println("两个引用是否相同:" + (singleton1 == singleton2));
        System.out.println("计数器值:" + singleton1.getCounter());
        
        System.out.println("\n=== 传统单例测试 ===");
        TraditionalSingleton ts1 = TraditionalSingleton.getInstance();
        TraditionalSingleton ts2 = TraditionalSingleton.getInstance();
        
        ts1.doSomething();
        ts2.doSomething();
        
        System.out.println("两个引用是否相同:" + (ts1 == ts2));
    }
}

4. 代码块与枚举的结合使用

4.1 枚举中的代码块

枚举中也可以使用静态初始化块和实例初始化块,用于初始化枚举常量。

public enum Status {
    // 枚举常量
    PENDING("等待中", 1) {
        // 每个枚举常量可以有自己的匿名类实现
        @Override
        public boolean canTransitionTo(Status nextStatus) {
            return nextStatus == PROCESSING || nextStatus == CANCELLED;
        }
    },
    PROCESSING("处理中", 2) {
        @Override
        public boolean canTransitionTo(Status nextStatus) {
            return nextStatus == COMPLETED || nextStatus == FAILED;
        }
    },
    COMPLETED("已完成", 3) {
        @Override
        public boolean canTransitionTo(Status nextStatus) {
            return false; // 已完成状态不能转换到其他状态
        }
    },
    FAILED("失败", 4) {
        @Override
        public boolean canTransitionTo(Status nextStatus) {
            return nextStatus == PENDING; // 失败后可以重新开始
        }
    },
    CANCELLED("已取消", 5) {
        @Override
        public boolean canTransitionTo(Status nextStatus) {
            return false; // 已取消状态不能转换
        }
    };
    
    // 枚举属性
    private final String description;
    private final int code;
    
    // 静态变量和静态初始化块
    private static Map<Integer, Status> codeMap;
    
    static {
        System.out.println("Status枚举静态初始化块执行");
        codeMap = new HashMap<>();
        for (Status status : values()) {
            codeMap.put(status.code, status);
            System.out.println("初始化状态码映射:" + status.code + " -> " + status);
        }
    }
    
    // 实例初始化块
    {
        System.out.println("初始化Status实例:" + this.name());
    }
    
    // 枚举构造器
    Status(String description, int code) {
        this.description = description;
        this.code = code;
        System.out.println("调用Status构造器:" + description);
    }
    
    // 抽象方法 - 每个枚举常量必须实现
    public abstract boolean canTransitionTo(Status nextStatus);
    
    // 静态方法:根据code查找Status
    public static Status fromCode(int code) {
        Status status = codeMap.get(code);
        if (status == null) {
            throw new IllegalArgumentException("无效的状态码:" + code);
        }
        return status;
    }
    
    // Getter方法
    public String getDescription() {
        return description;

更多推荐