点击上方蓝字关注 ?
来源:蓝灰_qhttps://www.jianshu.com/p/78ee812fec13
详解
1 场景在面向对象编程里,不变性是一个有点冷门的话题,一般在考察String特性的面试题中比较常见,但其实不变性是一个非常有用的设计。例如:某个通用的配置对象,只在初始化时赋值一次,之后不能被修改,如何避免被同事调用和修改?某个为多线程同时提供数据的只读对象,如何确保只读,也就是避免被同事修改?...
2 分析要在Java中实现不变性的对象,主要是确保它禁止修改,但完全写死的对象实用性很低,还是需要为对象赋值一次,这就可以分解为两个问题:
2.1 禁止修改
1-私有化方法和字段setter和public字段当然是禁止的,这样在普通语法层面上屏蔽修改入口。2-使用final字段final修饰符的直接作用是从编译器级别就锁死引用,引用不可变更,否则编译器会报错。final可以用于修饰类、方法和字段,分别作用如下:final类—禁止继承,把类锁死。final方法—禁止重写,在编译期即绑定具体方法。final字段—禁止修改,在类加载时即把final字段放进常量池。
所以,需要使用final字段,声明该字段为不可变。3-小心里式替换里式替换本身是六大设计原则之一,但是在不可变对象中,这是一个非常危险的性质,因为一个继承自基类的子类实例,可以轻松替换掉基类实例,从而破坏不可变性。
解决方法就是给类加上final修饰,禁止继承。4-杜绝变量逃逸/逸出变量逸出是一个很容易忽视的点,如果外部能够拿到内部字段的引用,或者在赋值时能够传入一个引用,就可以通过修改引用去修改内部字段,这也是不允许的。
解决方法就是,涉及外部引用或赋值时,一律使用深拷贝,如:
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); // 深拷贝赋值 }
2.2 赋值一次
不可变对象一般是需要赋值一次的,完全写死的不可变对象意味着扩展性很差,只能通过改代码来修改,不具备实用性。但是,采用了final修饰符的字段,只能在三种情况下得到值:1-常量;2-代码块;3-构造函数赋值;前两种情况其实就是代码写死的方式,所以我们只能在构造函数中实现赋值一次。
3 实现根据上文分析,要实现不可变对象,需要做到以下几点:1.代码中禁用setter和public字段;2.使用final修饰类和字段;3.在构造函数中赋值,注意要使用深拷贝赋值;
经过这样的操作,就可以实现一个比较安全的不可变对象了。
4 漏洞为什么说比较安全呢,因为还有一些漏洞可以破坏不可变性。1- 反射final字段虽然实现了不可变,但那是常规操作,如果使用反射,还是可以操作final字段的。不过,如果final字段在代码中定义为常量,就是安全的,因为编译器会优化getter函数,直接返回定义的这个常量,而不是final字段。当然,这种情况下,final字段就不能通过构造函数赋值了。2- 序列化如果不可变对象实现了序列化,通过反序列化也可以得到一个对象的拷贝。我们知道序列化可以破坏单例,因为他会创建一个新的内存对象及其引用。不过序列化对于不可变性还是比较友好的,因为反序列化得到的新内存对象与原始对象内容一致,在只读时没有不良影响。
—————END—————
创作不易,点个“在看”