不可变对象

2020-11-08  本文已影响0人  going_hlf

文章java final关键字中讨论了final的作用。其中提到,对于一个对象FinalClass objRef = new FinalClass;,限制的只是引用objRef不能再指向其它对象,而objRef所指向的对象的内容是可以被改变的。那么如何才能做到一个对象真正的不可变呢(类似C++中的const Class const &ref)?在java中是靠一组设计规范来实现的,在语言层面没有直接的限制手段。

不可变类的定义

简单来讲,对外不提供setter方法,或者setter/getter方法返回对象/成员的一个副本,对象就可以称为immutable对象。oracle官方文档Immutable Objects对不可变对象进行了定义说明,核心在于该对象一经创建则其状态不再可变。

典型的不可变类的使用场景:

例如String,String在传递过程中,都是以副本的形式copy传递,因此无论传递后如何被修改,修改的都是新的String对象,而老的String对象则是不会被修改的。(这样在传递过程中会产生大量的副本对象,不要担心,java的gc机制会回收不用的对象,效率先不讲)
在Java中,对于String、包装器这些类,我们经常会用他们来作为HashMap的key,试想一下如果这些类是可变的,将会发生什么?后果不可预知,这将会大大增加Java代码编写的难度。
下面是一个不可变对象使用的例子:

class InnerClass {
    private final int para;

    InnerClass(int para) {
        this.para = para;
    }

    InnerClass(InnerClass obj) {
        para = obj.para;
    }

    int GetPara() {
        return para;
    }
}

public class ImmutableClass {
    private final int val;
    private final InnerClass innerObj;

    ImmutableClass(int val, InnerClass obj) {
        this.val = val;
        this.innerObj = obj;
    }

    // 获取内部成员对象,返回副本
    InnerClass GetInnerObj() {
        return new InnerClass(innerObj);
    }

    // 获取内部成员变量,返回副本
    int GetVal() {
        return val;
    }

    // 修改内部成员对象,返回副本
    ImmutableClass ModInnerObj(InnerClass obj) {
        return new ImmutableClass(val, obj);
    }

    // 修改内部成员变量,返回副本
    ImmutableClass ModVal(int val) {
        return new ImmutableClass(val, innerObj);
    }

    void Show() {
        System.out.format("ImmutableClass::val = %d, InnerClass::para = %d\n", val, innerObj.GetPara());
    }

    public static void main(String... args) {
        ImmutableClass immutableObj1 = new ImmutableClass(10, new InnerClass(20));
        ImmutableClass immutableObj2 = immutableObj1.ModVal(30);
        ImmutableClass immutableObj3 = immutableObj2.ModInnerObj(new InnerClass(40));
        System.out.println("immutableObj1:");
        immutableObj1.Show();
        System.out.println("immutableObj2:");
        immutableObj2.Show();
        System.out.println("immutableObj3:");
        immutableObj3.Show();
    }
}

输出

immutableObj1:
ImmutableClass::val = 10, InnerClass::para = 20
immutableObj2:
ImmutableClass::val = 30, InnerClass::para = 20
immutableObj3:
ImmutableClass::val = 30, InnerClass::para = 40

可见,无论如何,每一次对不可变对象的修改操作,都不会影响其它的不可变对象。

不可变,并非语言机制上的严格不可变。下面我们再来看下,如何用反射机制绕过这种不可变性的限制:

import java.lang.reflect.Field;

public class ReflactImmutableClass {
    public static void main(String... args) {
        ImmutableClass immutableObj = new ImmutableClass(5, new InnerClass(10));
        System.out.println("before immutableObj.val = " + immutableObj.GetVal());
        try {
            Field filed = immutableObj.getClass().getDeclaredField("val");
            filed.setAccessible(true);
            filed.set(immutableObj, 100);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

        System.out.println("after immutableObj.val = " + immutableObj.GetVal());
    }
}

执行结果:

before immutableObj.val = 5
after immutableObj.val = 100

不可变对象的成员确实被修改了。

java不可变类定义规范:

  1. 不可变类的成员变量必须是private(最好用final修饰)
  2. 不对外提供setter方法,如果提供,则返回值为当前对象的副本。
  3. 对外提供的getter方法,不能返回本对象和任何内部成员的this引用。
  4. 不可变类内部对于引用类型的拷贝必须是深拷贝。
  5. 不可变类最好不要再被继承?
  6. 如果不可变类包含了可变类的对象,那么需要确保返回的是可变类对象的副本。
  7. 不要试图通过反射机制打破不可变类的限制,不可变类的实现本身是一种设计约束,好的软件设计要遵循这些设计契约。

不可变类和数据共享

很多对于不可变对象的解释,没有回答一个问题:不可变对象是为了防止多线程同时对数据读写带来的不一致性,那么如果需要多线程间共享对象,如果每次返回的是当前对象的一个副本,那么如何保证两个线程间能够共享到对方的修改?
答案是,不可变对象解决的问题并不是多线程共享对象的问题,虽然二者都跟并发编程有关。如果要实现多线程间对象共享,则需要使用加锁机制。
下面是一个多线程数据共享的例子(更多分析见并发编程):

// 参考https://www.cnblogs.com/john8169/p/9780560.html
public class MulteThreadShareData {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(shareData).start();
        new Thread(shareData).start();
    }

    static class ShareData implements Runnable {
        int count = 100;

        @Override
        public void run() {
            while (count > 0) {
                decrease();
            }
        }

        public synchronized void decrease() {
            count--;
            System.out.println(Thread.currentThread().getName() + "this count: " + count);
        }

    }
}

代码链接:https://gitee.com/haoliangfei/java_beginner

上一篇下一篇

猜你喜欢

热点阅读