为何Child对象返回的Toy实例与构造方法传入的不是同一对象?
关于Java对象封装与防御性拷贝的疑问解答
测试用例代码
Toy t1 = new Toy(1000121, "Red Bike", 3, 98.90); Toy t2 = new Toy(1000123, "Colouring Book", 4, 19.89); Toy t3 = new Toy(1000128, "Skateboard", 5, 149.99); Toy t4 = new Toy(1000127, "SpongeBob DVD", 3, 14.99); Toy t5 = new Toy(1000130, "Bike Helmet", 1, 18.99); Toy t6 = new Toy(1000125, "Toy car", 10, 3.99); Toy t7 = new Toy(1000129, "Ball", 2, 5.59); Toy t8 = new Toy(1000189, "Teddy Bear", 3, 10.79); Toy[] listofToy1 = { t1, t2 }; Child c1 = new Child("James", 1, listofToy1); assertTrue(c1.getChildToy().length == 2 && c1.getChildToy()[0] != t1 && c1.getChildToy()[1] != t2); assertTrue(c1.getChildToy().length == 2 && c1.getChildToy()[0].getToyName().equals("Red Bike") && c1.getChildToy()[1].getToyName().equals("Colouring Book")); assertTrue(c1.getChildToy().length == 2 && c1.getChildToy()[0].getToyQuantity() == 3 && c1.getChildToy()[1].getToyQuantity() == 4);
疑问点
构造方法传入了包含t1、t2的数组,为何第一个断言要求c1.getChildToy()返回的数组元素与t1、t2不是同一对象?同时第二个断言又要求返回的Toy对象属性与t1、t2一致?打印返回对象的信息,结果与t1、t2的属性完全匹配,却不理解其中原理。
核心原理:防御性拷贝(Defensive Copy)
这是面向对象编程里封装性的典型要求,目的是保护Child对象内部状态的独立性:
- 第一个断言要求返回的不是原对象:如果直接把传入的
t1、t2引用存在Child里,外部代码拿到getChildToy()返回的数组后,就能直接修改t1的属性(比如调用t1.setToyQuantity(100)),这会直接改变Child内部的玩具数据,破坏Child对象的封装性。所以必须创建原Toy对象的副本(也就是新的Toy实例,属性和原对象完全一致,但内存地址不同),这样外部修改副本不会影响Child内部的状态。 - 第二个断言要求属性一致:副本必须和原对象的属性完全相同,这样
Child才能正确保存传入的玩具信息,符合业务逻辑。
简单说就是:既要保留原数据的内容,又要切断外部对内部数据的直接修改路径。
实现思路
要满足这些断言,你需要在Child的构造方法里做两件事:
- 不要直接保存传入的
Toy[]数组引用,而是创建一个新的数组。 - 对数组里的每个
Toy对象,调用拷贝构造方法(或者手动创建新的Toy实例,把原对象的属性值一一传入),把副本存入新数组。 getChildToy()方法也要返回内部数组的副本,而不是直接返回内部保存的数组引用(防止外部修改数组结构)。
举个Child构造方法的示例片段:
public Child(String name, int id, Toy[] toys) { // 创建新数组,长度和传入数组一致 this.toys = new Toy[toys.length]; for (int i = 0; i < toys.length; i++) { // 假设Toy有拷贝构造方法 this.toys[i] = new Toy(toys[i]); // 如果没有拷贝构造,就手动复制属性: // this.toys[i] = new Toy(toys[i].getToyId(), toys[i].getToyName(), toys[i].getToyQuantity(), toys[i].getPrice()); } } public Toy[] getChildToy() { // 返回内部数组的副本,防止外部修改数组长度或替换元素 Toy[] copy = new Toy[this.toys.length]; System.arraycopy(this.toys, 0, copy, 0, this.toys.length); return copy; }
内容的提问来源于stack exchange,提问作者Ethan
相关产品推荐
相关产品推荐

