保护性拷贝和不可变视图的学习,更面向对象的设计
来源:互联网 发布:印尼旅行社软件 编辑:程序博客网 时间:2024/06/05 10:52
最近在阅读《Effective JAVA》,感谢它让我重温了很多知识点。不过有些章节不是一时半会就能全懂,也是要多回头再看,结合一些实际经验就好理解了。今天看到“必要时进行保护性拷贝”有感,记录一下。
JAVA是一门面对向象的语言,对象作为主体。对象中可能有不少内部组件,比如List,map等。按照惯例,我们可能会为一些属性提供setter和getter。这个时候我们可能就越过了对象控制属性,而是直接操作了。另外从安全性上讲,对外提供的查询类型的方法,是不允许修改到组件内部来的。因为对外的方法返回的是内部组件对象的引用,客户端在不可控的时候,有可能破坏。
如下面的代码,声称可以表示一段不可变的时间。粗略看start和end都有比较先后,并且都是final的。
public final class Period { private final Date start; private final Date end; /** * @param start * the beginning of the period * @param end * the end of the period; must not precede start * @throws IllegalArgumentException * if start is after end * @throws NullPointerException * if start or end is null */ public Period(Date start, Date end) { if (start.compareTo(end) > 0) throw new IllegalArgumentException(start + " after " + end); this.start = start; this.end = end; } // Repaired constructor - makes defensive copies of parameters - Page 185 // Stops first attack // public Period(Date start, Date end) { // this.start = new Date(start.getTime()); // this.end = new Date(end.getTime()); // // if (this.start.compareTo(this.end) > 0) // throw new IllegalArgumentException(start +" after "+ end); // } public Date start() { return start; } public Date end() { return end; } // Repaired accessors - make defensive copies of internal fields - Page 186 // Stops second attack // public Date start() { // return new Date(start.getTime()); // } // // public Date end() { // return new Date(end.getTime()); // } public String toString() { return start + " - " + end; } // Remainder omitted}然而,Date类本身是可变的。
Date start = new Date();Date end = new Date();Period p = new Period(start, end);// 修改了end对象,也间接地影响了p对象end.setYear(2016);为了保护Period实例的内部不受这种篡改,对构造方法的每个可变参数进行保护性拷贝。
public Period(Date start, Date end) { this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if (start.compareTo(end) > 0) throw new IllegalArgumentException(start + " after " + end); this.start = start; this.end = end;}使用新对象而不是老对象的引用,这样就切断了通过老对象的修改,间接影响p对象。但是这并不能完全解决问题,还可以通过 p.end().setYear(2016);来修改。两个方法得改。
public Date start() {return new Date(start.getTime());}public Date end() { return new Date(end.getTime());}这样子后,Period真的是不可变的了。
把内部组件返回给调用方之前,应该认真考虑一下是否会引起上述的问题。一种方法是进行保护性拷贝,另一种是返回不可变视图。
保护性视图可以见下面的例子,引用一篇博客,这也是之前未接触过的一个方法 Collections.unmodifiableList(List<? extends T> list))。在《重构——改善既有代码的设计》一书中,有一种重构手法叫Encapsulate Collection (封装集群)。类Student有一个ArrayList属性, 很多人可能会如下设计类Student。但是,如果通过Student.getCourses()获得对ArrayList属性引用后,就可以任意为Student对象添加“课程”,而Student对象对此一无所知,这不符合面向对象编程的约定。
import java.util.ArrayList;public class Student {private String name;private ArrayList<String> courses;public Student(String name, ArrayList<String> courses) {this.name = name;this.courses = courses;}public ArrayList<String> getCourses() {return courses;}public void setCourses(ArrayList<String> courses) {this.courses = courses;}public String getName() {return name;}public void setName(String name) {this.name = name;}public static void main(String[] args) {ArrayList<String> list = new ArrayList<String>();list.add("001");list.add("002");Student s = new Student("Tom", list);ArrayList<String> anotherList = s.getCourses();anotherList.add("999"); // 获取了list的引用,就可以随意增加课程。System.out.println("Tom's course.length = " + s.getCourses().size());}}稍作修改,首先仅对外提供的getCourses()方法,而没有setCourses()方法,而且通过getCourses()方法获得的courses是“只读的”,如果你试图向其添加一个新课程,则抛出java.lang.UnsupportedOperationException。你必须通过Student1.addCourse()来向特定的Student1对象添加一个新课程。就好像,你必须让顾客自己向购物车里放食物,而不能在顾客毫不知情下,偷偷向其购物车里放食物。
import java.util.ArrayList;import java.util.Collections;import java.util.List;public class Student {private String name;private ArrayList<String> courses;public Student(String name, ArrayList<String> courses) {this.name = name;this.courses = courses;}public String getName() {return name;}public void setName(String name) {this.name = name;}public void addCourse(String course) {courses.add(course);}public String removeCourse(String course) {boolean removed = courses.remove(courses);if (removed) {return course;} else {return null;}}public List<String> getCourses() {return Collections.unmodifiableList(courses);}public static void main(String[] args) {ArrayList<String> list = new ArrayList<String>();list.add("001");list.add("002");Student s = new Student("Tom", list);List<String> anotherList = s.getCourses();/** * throws java.lang.UnsupportedOperationException should replace with * s.addCourse(String course) */anotherList.add("999");// never reachedSystem.out.println("Tom's course.length = " + s.getCourses().size());}}
返回只读对象Collections.unmodifiableList(List);实现的,它是一个新的类,对于那些不涉及修改的方法,直接返回原对象的方法结果;对于增删的方法,全部都抛出了“不支持的操作”的异常。
public int size() {return c.size();} public boolean isEmpty() {return c.isEmpty();} public boolean contains(Object o) {return c.contains(o);} public Object[] toArray() {return c.toArray();} public <T> T[] toArray(T[] a) {return c.toArray(a);} public String toString() {return c.toString();} public boolean add(E e) { throw new UnsupportedOperationException(); } public boolean remove(Object o) { throw new UnsupportedOperationException(); } public boolean containsAll(Collection<?> coll) { return c.containsAll(coll); } public boolean addAll(Collection<? extends E> coll) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection<?> coll) { throw new UnsupportedOperationException(); } public boolean retainAll(Collection<?> coll) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); }
参考文献:http://hi.baidu.com/onejava/blog/item/10388f2b77d340fae6cd4091.html
阅读全文
0 0
- 保护性拷贝和不可变视图的学习,更面向对象的设计
- 【java】保护性拷贝的重要性
- python的可变和不可变对象
- 不可变的String对象
- 不可变对象的权衡
- None用法+连接字符串优先使用join +用format而不是%+区别可变对象和不可变对象(list的深拷贝和浅拷贝)
- 面向对象:不可变类
- 不可变的原始值和可变的对象引用
- 不可变的原始值和可变的对象引用
- 不可变字符串的学习
- 不可变对象和类,变量的作用域
- python的list,tuple,dist,set和不可变对象
- javascript中不可变的原始值和可变对象
- “变点和不变点”说明面向对象的设计方法,弱偶合,(interfance,factory等等)
- 为什么对象应该是不可变的
- string对象不可变的原理
- C++创建不可拷贝的对象
- [疯狂Java]面向对象:不可变类
- easyui-datagrid使用load()保持分页显示数量
- 用两个栈实现队列
- Spring 3.0 抛异常 java.lang.ClassNotFoundException: org.springframework.expression.PropertyAccessor
- leetcode week15
- swift3.0 元组的用法
- 保护性拷贝和不可变视图的学习,更面向对象的设计
- 使用JS创建表格和得到/修改样式
- websocket建立连接时传递参数
- [绍棠] iOS播放器全屏与半屏幕按钮方法实现
- Spark:工作集上的集群计算
- API&REST API
- FreeMarker标签使用整理
- Eclipse常用的快捷键
- 不知道发布端(即服务器端)是哪种发布技术的情况下,客户端使用axis去调用,报错:unexpected element (uri:" Expected elements are <