`
DoubleEO
  • 浏览: 154395 次
  • 性别: 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 不一定需要这个吧

相关推荐

Global site tag (gtag.js) - Google Analytics