`
DoubleEO
  • 浏览: 157801 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

不可变对象,深层次克隆等总结

阅读更多
   前面有篇自己总结的文章,是用final方法标注引用的,引用为final,表名这个引用只能指向这个对象,不能改变,但是对象本身可以变,java没有关键字来帮你实现不可变对象,必须自己写实现方法(thinking in java上面说“读者可以自己想”)。
    不可变对象的几个基本原则:
1.class要声明为final
2.所有属性都声明为private访问权限
3.只提供get方法,不提供set方法
4.提供构造方法,并且在构造方法中初始化所有你想初始化的数据
5.类中有组合情况发生时,从get方法得到组合对象之前,先克隆这个对象。
6.组合对象传递给构造函数的时候,也要先克隆一份。

1.一般不可变类是这样的:
public final class Example
{
	private String userName;
	private String password;
	public Example(String userName,String password)
	{
		this.userName = userName;
		this.password = password;
	}
	public String getPassword()
	{
		return password;
	}
	public String getUserName()
	{
		return userName;
	}
	
}

原理如下:
声明为final,以防它被子类继承修改,所以参数都是private,不能直接访问,而只能通过get访问,但是没有set方法,所以不能设定值,而值的设定是在构造器中设置的,这样就形成了不可变的类。它的属性都是String或者可以是一些基本类型,不需要克隆,而String本身就是final修饰的类,也是恒定的。这些都是很容易想到的。
    现在扩展一下,类中需要组合另一个对象,也就是说,不光只有基本类型了,还有引用类型。下面是复杂一些的情况

这是另一个类,他要被组合到Example中
public class Address
{
	private String homeAddr;
	private String mail;
	private int code;
	public Address(String homeAddr, String mail, int code)
	{
		super();
		this.homeAddr = homeAddr;
		this.mail = mail;
		this.code = code;
	}
	//以下是get/set,省略....
}

现在修改Example为Example1,加入了Address类的组合。
public class Example1
{
	private String userName;
	private String password;
	private Address address;
	public Example1(String userName,String password,Address address)
	{
		this.userName = userName;
		this.password = password;
		this.address = address;
	}
	public String getPassword()
	{
		return password;
	}
	public String getUserName()
	{
		return userName;
	}
	public Address getAddress()
	{
		return address;
	}		
}

现在这个类,貌似符合了不可变类的原则(1-4条),现在来测试一下。
public class TestExample1
{
	public static void main(String[] args)
	{
		// 先填充Address对象
		Address address = new Address("大拐弯幼儿园", "daguanwai.com", 1);
		// 填充Example1
		Example1 e1 = new Example1("王小黑", "123", address);
		Address temp = e1.getAddress();
		System.out.println("王小黑上的幼儿园名字是: " + temp.getHomeAddr());
		// 修改address
		address.setHomeAddr("大黄豆劳改所");
		System.out.println("王小黑上的幼儿园名字是: " + temp.getHomeAddr());

	}
}
打印结果:
王小黑上的幼儿园名字是: 大拐弯幼儿园
王小黑上的幼儿园名字是: 大黄豆劳改所

还是发生了改变,没达到不可变类的目的。

2.java总是按值传递的,引用的值就是地址,前面的blog已经说过(http://doubleeo.iteye.com/admin/blogs/310082),所以现在共享的是同一个address ,对address的修改,都会体现在Example1 上面,为了达到不可变类的效果,要用克隆。
所以Example1修改成如下这样
public class Example1
{
	private String userName;
	private String password;
	private Address address;
	public Example1(String userName,String password,Address address)
	{
		this.userName = userName;
		this.password = password;
		this.address = (Address)address.clone();
	}
	public String getPassword()
	{
		return password;
	}
	public String getUserName()
	{
		return userName;
	}
	public Address getAddress()
	{
		return (Address)address.clone();
	}		
}


当然Address给重写clone方法
public class Address implements Cloneable
{
	private String homeAddr;
	private String mail;
	private int code;
	//构造方法及get/set省略...
	@Override
	protected Object clone()
	{
		try
		{
			return super.clone();
		}
		catch (Exception e)
		{
			throw new InternalError();
		}		
	}		
}
再测试一下Example1
打印结果:
王小黑上的幼儿园名字是: 大拐弯幼儿园
王小黑上的幼儿园名字是: 大拐弯幼儿园


这次正确了,把example1塑造为不可变的了。

以上的克隆叫浅克隆(Shallow Cloning)

3.再近一步复杂,Example1中还是组合,但是这次组合的是容器,像List这种,用来装对象引用的容器,如果你还按刚才那种clone方法,肯定是达不到目的了,虽然List本身的引用克隆了一个新的,但是里面包裹着的对象的引用,还是那一份,所有改变它还是会破坏不可变类,因此要用到深层克隆(Deep Cloning)
修改后的版本如下
public class Example1
{
	private String userName;
	private String password;
	private List<Address> address;
	public Example1(String userName,String password,List<Address> address)
	{
		this.userName = userName;
		this.password = password;
		this.address = cloneAddress(address);
	}
	public String getPassword()
	{
		return password;
	}
	public String getUserName()
	{
		return userName;
	}
	public List<Address> getAddress()
	{
		return cloneAddress(address);
	}
        //深层次clone方法
	private List<Address> cloneAddress(List<Address> address){
		int size = address.size();
		List<Address> newList = new ArrayList<Address>(size);
		for(Address addr:address)
			newList.add((Address)addr.clone());
		
		return newList;
	}		
}

也就是说,要把容器中的每个引用再一次克隆,如果引用是一个List,那么就要再把这个List中的所有引用再克隆。
最后再总结一下:

1.class要声明为final
2.所有属性都声明为private访问权限
3.只提供get方法,不提供set方法
4.提供构造方法,并且在构造方法中初始化所有你想初始化的数据
5.类中有组合情况发生时,从get方法得到组合对象之前,先克隆这个对象。
6.组合对象传递给构造函数的时候,也要先克隆一份。
7.如果浅克隆不能符合不可变对象的正常行为,就要实现深层克隆
分享到:
评论
1 楼 Corwen 2012-04-18  
class要声明为final 不一定需要这个吧

相关推荐

    java不同对象及集合的多次复制

    - **使用`clone()`或`copy()`方法**:如果集合中的元素是可克隆的,可以直接调用`clone()`方法;如果集合支持`copy()`方法(如`ArrayList`),也可以使用它进行复制。 - **使用工具类**:如Apache Commons ...

    开源项目-lukechampine-freeze.zip

    总结,Lukechampine-freeze 为 JavaScript 开发者提供了一种高效且全面的手段,用于创建和维护不可变对象。通过理解并合理运用这一工具,我们可以编写出更加安全、可维护的代码,提升整体项目的质量和稳定性。

    JS克隆,属性,数组,对象,函数实例分析

    这表明,尽管我们可以克隆对象,但是数组和对象内部的引用仍然会被共享,导致深层次的克隆问题。 为了处理这个问题,可以使用更复杂的克隆方法,如`JSON.parse(JSON.stringify(obj))`,或者使用库如lodash的`_....

    设计模式之Prototype

    在Java中,我们可以利用内置的`java.lang.Cloneable`接口,但需要注意的是,该接口本身不包含任何方法,只是声明一个对象是可克隆的。为了实现克隆,我们还需要重写`Object`类的`clone()`方法。 3. 请求克隆:在...

    设计模式之原型模式Java实现和类设计图

    - 如果对象包含不可克隆的成员,如静态字段或不可变对象,那么可能需要调整拷贝策略。 总之,原型模式是软件设计中的一种有效工具,尤其适用于需要频繁复制对象的场景。Java提供的`Cloneable`接口和`clone()`方法...

    原型模式.doc

    然而,缺点包括需要为类添加克隆逻辑,可能会增加复杂性,特别是处理引用了不可串行化或循环引用的对象时;此外,每个可克隆的类都必须实现`Cloneable`接口。 原型模式的应用场景包括但不限于: 1. 资源密集型对象...

    Prototype Pattern原型模式

    在Java等语言中,可以利用内置的克隆机制或实现Cloneable接口来实现对象的复制。这种方式避免了每次创建新对象时都执行完整的构造过程,特别是在复杂的对象层级结构中,原型模式能显著提高性能。 ### **主要角色** ...

    继承借口之类

    在Java编程语言中,"继承接口克隆"是面向对象设计的重要概念,它们构成了Java类层次结构的关键组成部分。本文将详细探讨这两个主题,并通过实际示例来解释它们的应用。 首先,我们来理解“继承接口”。在Java中,...

    大话设计模式总结材料.doc

    4. **开放封闭原则**:这是另一个重要的面向对象原则,它提倡软件实体(如类、模块和函数)应该是可扩展的,但不可修改。这意味着添加新功能时,不应修改现有代码。 5. **依赖倒转原则**:它主张抽象不应该依赖细节...

    Java面试八股文十万字总结.docx

    - **String**:不可变字符串,适合少量修改场景。 - **StringBuffer**:线程安全的可变字符串,适用于多线程环境。 - **StringBuilder**:非线程安全的可变字符串,效率高于StringBuffer。 **11. ArrayList和...

    Java原型模式

    如果引用的对象也是可克隆的,可以递归调用其clone()方法。例如: ```java public class DeepPrototype implements Cloneable { private String property; private OtherObject otherObject; // 省略getter和...

    C++设计模式全总结-通俗易懂

    5. **Prototype模式**:原型模式通过克隆现有的对象实例来创建新对象,而不是通过创建新实例的方式。这种方式可以提高性能,尤其是在创建对象成本较高的情况下。在C++中,可以通过重载赋值运算符或者使用深拷贝来...

    2022最新Java面试题常见面试题与答案汇总0001.pdf

    3. `final`用于声明常量或不可变对象,也可修饰方法和类,确保不可被重写或继承。 4. `Math.round(-1.5)`返回-1。 5. String不是基础数据类型,而是对象。 6. 操作字符串的类有`String`、`StringBuilder`和`...

    C#面向对象设计模式纵横谈(11)

    5. **原型模式**: 原型模式通过克隆已有对象来创建新对象,减少类的实例化开销。C#中的`ICloneable`接口可以用来支持浅复制或深复制。 6. **装饰器模式**: 装饰器模式动态地给对象添加额外的职责,允许扩展对象的...

    java牛人总结

    `final`关键字用于声明不可变的变量或方法,`finally`块用于确保代码段在异常发生或正常执行后总能执行,`finalize`方法是`Object`类的一个方法,用于垃圾回收前的资源清理,但不应被直接调用,而应由JVM自动调用。...

    vue data对象重新赋值无效(未更改)的解决方式

    这是因为JavaScript对象的赋值是引用传递,而不是值传递,所以当对数据对象进行深度赋值时,Vue无法跟踪到这些深层次的变化。 在标题和描述中提到的问题是关于`vue data对象重新赋值无效(未更改)`的解决方法。这个...

    Effictive Java

    - 创建不可变类,确保对象在其生命周期内状态不会改变。 - 使用最终变量(`final`)、只读集合等技术实现不可变性。 ##### Item14:偏好组合而非继承 - **目的**:避免类层次结构中的复杂性和问题。 - **实现方式...

    设计模式实验报告文档

    原型模式通过克隆现有对象来创建新对象,分为浅克隆和深克隆。在客户类Customer的案例中,浅克隆只是复制对象引用,而深克隆则会复制对象及其引用的对象。比较两者,深克隆能完全复制整个对象树,而浅克隆只复制对象...

Global site tag (gtag.js) - Google Analytics