读《Effective Java》笔记 - 条目17

发布于:2024-12-06 ⋅ 阅读:(110) ⋅ 点赞:(0)

条目17:使可变性最小化

为什么要使可变性最小化?

  1. 不可变对象天然是线程安全的,可以在多个线程之间安全共享。而可变对象需要添加额外的同步机制保证线程安全。
  2. 不可变对象一旦创建就不会改变,便于追踪和理解代码。而可变对象的状态随时可能会改变,导致行为复杂化,增加了调试和维护难度
  3. 不可变对象的行为比较确定,没有意外状态变化,使得代码更加直观。
  4. 在传递不可变对象时,不用担心接收方会修改其内部状态,从而提高代码的健壮性和安全性。

总结:线程安全,简化维护和调试,提高可读性,安全性,健壮性

如何使类的可变性最小?

  1. 不要为对象提供修改状态的方法(确保类中没有 setter 方法或其他能改变内部状态的方法。)

    public final class ImmutablePoint {
        private final int x;
        private final int y;
    
        public ImmutablePoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        public int getX() {
            return x;
        }
    
        public int getY() {
            return y;
        }
    }
    
  2. 确保类不能被继承(使用 final 关键字修饰类。)

    public final class ImmutableClass {
        // Class definition
    }
    
  3. 将所有字段声明为 final(关键字 final 确保字段在初始化后无法重新赋值。)

    public final class ImmutablePerson {
        private final String name;
        private final int age;
    
        public ImmutablePerson(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
  4. 将所有字段声明为 private(使字段 private 可以防止直接访问和修改,保证对象内部的封装性。即使是 public final 字段,也可能被外部直接读取后进行操作,造成安全隐患。)

    public final class ImmutableConfig {
        private final String setting;
    
        public ImmutableConfig(String setting) {
            this.setting = setting;
        }
    
        public String getSetting() {
            return setting;
        }
    }
    
  5. 确保不会在构造过程中泄漏 this(不要在构造函数中把 this 引用传递给其他方法(包括工厂方法),因为对象可能还未完成初始化。)

    public class MutableClass {
        public MutableClass() {
            EventListener.register(this); // 可能在对象未完全构造时使用了它
        }
    }
    
  6. 如果类包含可变组件,确保其不会被修改(如果类中包含其他可变对象(如数组或集合),需要在对外暴露时进行防护性拷贝(defensive copy)。)

    public final class ImmutableWithArray {
        private final int[] array;
    
        public ImmutableWithArray(int[] array) {
            this.array = array.clone(); // 防护性拷贝
        }
    
        public int[] getArray() {
            return array.clone(); // 防护性拷贝
        }
    }
    

不可变类的权衡

  1. 性能开销

    每次状态变化都需要创建新对象,会增加内存消耗,特别是在高频修改场景下(如复杂数值计算)。

    String immutableString = "Hello";
    immutableString = immutableString + " World"; // 创建了两个对象
    
  2. 不适合需要频繁更新状态的场景

    如计数器或缓存等需要频繁更新的场景,更适合使用可变对象。

什么时候使用不可变类?

  1. 表示值的对象

    StringIntegerBigDecimal 等值对象,天然适合不可变设计。

  2. 作为 Map 或 Set 的键

    不可变对象的 hashCodeequals 不会随时间改变,适合作为集合的键。

  3. 需要在多线程中共享的对象

    不可变对象天然线程安全,无需同步。


网站公告

今日签到

点亮在社区的每一天
去签到