美文网首页
不可变对象

不可变对象

作者: going_hlf | 来源:发表于2020-11-08 23:28 被阅读0次

    文章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

    相关文章

      网友评论

          本文标题:不可变对象

          本文链接:https://www.haomeiwen.com/subject/gdmcbktx.html