Java设计模式之immutable(不可变)模式

immutable简介

不可变对象永远不会发生改变,其字段的值只在构造函数运行时设置一次,其后就不会再改变。例如JDK中常见的两种基本数据类型String和Integer,它们都是不可变对象。为了理解immutable与mutable的区别,可以看看下面的一段代码:

package date0804.demo2;

import java.awt.Point;

public class ImmutableString {

	public static void main(String[] args) {
		//String,immutable
		String str = new String("new book");
		System.out.println(str);
		str.replaceAll("new", "old");
		System.out.println(str);

		//Point,mutable
		Point point = new Point(0,0);
		System.out.println(point);
		point.setLocation(1, 0);
		System.out.println(point);
	}
}

运行结果为

new book
new book
java.awt.Point[x=0,y=0]
java.awt.Point[x=1,y=0]

我们看到point的值发生了改变,而str的值并没有改变。String类型的对象一旦在内存中创建,它的值就不会发生改变,改变的只能是对该对象引用的指针。若想创建可以改变的String,则可以使用StringBuilder和StringBuffer类创建字符串,更加灵活,可以进行添加、插入和追加新的内容等操作。

[拓展,下面一段代码与immutable无关]再看另外一个有趣的String实例:

package date0804.demo2;

public class ImmutableString {

	public static void main(String[] args){

		String str=new String("xyz");
		change(str);
		System.out.println(str);
	}

	public static void change(String s) {
		s="xml";
	}
}

上面这段代码的输出结果为:

xyz

根据我的理解,Java中方法的参数是按值传递的,在这里,Java在内存堆上创建了一个字符串"xyz",然后将该字符串的内存地址赋给了str变量,即str中存储了"xyz"内存地址的引用,而change()方法是按值传递的,相当于再创建了一个字符串对象"xml",而这个对象又有另外一个不同的地址引用,并非之前的x存储的引用。

在并发编程中,一种被普遍认可的原则就是:尽可能的使用不可变对象来创建简单、可靠的代码。不可变对象特别有用,由于创建后不能被修改,所以不会出现由于线程干扰产生的错误或是内存一致性错误。使用不可变对象降低了垃圾回收所产生的额外开销,也减少了用来确保使用可变对象不出现并发错误的一些额外代码。

immutable的优点

不可变对象可以在很大程度上简化我们的程序代码,并且具有以下优点:

——不可变对象易于构造,使用和测试;

——自动地为线程安全型,避免了一些繁杂的同步问题;

——不需要一个复制构造函数(copy constructor);

——不需要一个clone的实现;

——允许hashCode()方法的懒汉模式实现,并且缓存它的返回值;

——当作为一个字段时,不需要复制;

——一旦构造以后,具有类型不变性,不用检查;

——[引用名言]if an immutable object throws an exception, it‘s never left in an undesirable or indeterminate state。

immutable的设计方法

——确保类不会被覆写,即该类不会被继承,实现这一点要加上修饰符final;或者使用静态工厂创建方法,确保构造函数私有的;

——所有的字段必须是私有的,并且加上修饰符final;

——所有的设置只需简单的一个构造函数即可,不要使用空构造函数,不要使用Javabean风格的构造函数;

——不要提供任何可以改变对象的方法,不仅仅是setter,一切能够修改对象状态的方法都要避免;

——如果该类有可变字段的对象,则必须进行保守型复制,并且只能由该类自身进行修改。

immutable类的代码实例

final public class ImmutableRGB {

    // 常量字段
    final private int red;
    final private int green;
    final private int blue;
    final private String name;

    //私有方法,检查三基色的值是否在0~255之间,若不符合,则抛出异常
    private void check(int red,
                       int green,
                       int blue) {
        if (red < 0 || red > 255
            || green < 0 || green > 255
            || blue < 0 || blue > 255) {
            throw new IllegalArgumentException();
        }
    }

    //唯一的一个构造方法,先检查字段是否合法,然后传递所有字段
    public ImmutableRGB(int red,
                        int green,
                        int blue,
                        String name) {
        check(red, green, blue);
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.name = name;
    }

    //get方法
    public int getRGB() {
        return ((red << 16) | (green << 8) | blue);
    }

    //get方法
    public String getName() {
        return name;
    }

    //对颜色值进行求逆
    public ImmutableRGB invert() {
        return new ImmutableRGB(255 - red,
                       255 - green,
                       255 - blue,
                       "Inverse of " + name);
    }
}

从上面一段代码可以看出,这是一个不可变的类,其对象是一个不可变对象。对象一旦创建,就不会被修改。最后的一段代码,反映了一旦对对象进行修改,不要返回对象本身,而是拷贝一份返回。这种不可变对象很好地解决了并发编程中的竞争问题,不用考虑线程的同步,因为对象一旦创建,不会改变。

immutable类的不正确使用

下面一段代码显示了表面上看起来是一个immutable类,实际上则是一个mutable类:

import java.util.Date;
	public final class BrokenPerson
	{
		private String firstName;
		private String lastName;
		private Date dob;

		public BrokenPerson( String firstName,
		  String lastName, Date dob)
		{
			this.firstName = firstName;
			this.lastName = lastName;
			this.dob = dob;
		}

		public String getFirstName()
		{
			return this.firstName;
		}
		public String getLastName()
		{
			return this.lastName;
		}
		public Date getDOB()
		{
			return this.dob;
		}
	}

这段代码看起来没有问题,但是
请注意dob是一个Date对象,则说明它是一个mutable对象字段,如果客户用下面一段代码去调用:

Date myDate = new Date();
	BrokenPerson myPerson =
	  new BrokenPerson( "David", "O‘Meara", myDate );
	System.out.println( myPerson.getDOB() );
	myDate.setMonth( myDate.getMonth() + 1 );
	System.out.println( myPerson.getDOB() );

那么,返回的结果有可能是

Mon Mar 24 21:34:16 GMT+08:00 2013
Thu Apr 24 21:34:16 GMT+08:00 2013

因此,我们看到myPerson实际上是一个mutable对象,问题就出在BrokenPerson具有一个mutable对象的字段,那么
我们应该使用它的拷贝来创建对象,而不是直接引用:

import java.util.Date;
	public final class BetterPerson
	{
		private String firstName;
		private String lastName;
		private Date dob;

		public BetterPerson( String firstName,
		  String lastName, Date dob)
		{
			this.firstName = firstName;
			this.lastName = lastName;
			this.dob = new Date( dob.getTime() );
		}
                //.......
                public Date getDOB()
	       {
		return new Date( this.dob.getTime() );
	        } 

注意,对于所有mutable的字段,应该要使用拷贝来在类中"进进出出",这样才比较安全。

总结

本文简单介绍了immutable的优点及其设计方法,不可变对象最大的缺点就是创建对象的开销,因为每一步操作都会产生一个新的对象,然而合理地使用immutable可以带来极大的好处。不可变类最适合表示抽象数据类型(如数字、枚举类型或颜色)的值。Java 类库中的基本数据类型的包装类(如Integer 、 Long 和 Float )都是不可变的,其它数字类型(如 BigInteger 和 BigDecimal )也是不可变的。表示复数或任意精度的有理数的类将比较适合设计为不可变类。甚至包含许多离散值的抽象类型(如向量或矩阵)也很适合设计成不可变类,这取决于你的应用程序。
另一个适合用不可变类实现的好示例就是事件 。事件的生命期较短,而且常常会在创建它们的线程之外的线程中消耗,所以使它们成为不可变的是利大于弊。大多数 AWT 事件类都没有严格的作为不可变类来实现。同样地,在通信系统的组件间进行消息传递,将消息对象设计成不可变的是明智的。

时间: 2024-08-29 06:02:59

Java设计模式之immutable(不可变)模式的相关文章

Java设计模式(十) 备忘录模式 状态模式

(十九)备忘录模式 备忘录模式目的是保存一个对象的某个状态,在适当的时候恢复这个对象. class Memento{ private String value; public Memento(String value){ this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } class Storage

Java设计模式(五)外观模式 桥梁模式

(九)外观模式 外观模式为子系统提供一个接口,便于使用.解决了类与类之间关系的,外观模式将类之间的关系放在一个 Facade 类中,降低了类类之间的耦合度,该模式不涉及接口. class CPU { public void startup(){ System.out.println("cpu start"); } public void shutdown(){ System.out.println("cpu stop"); } } class Memory { pu

Java设计模式(六)合成模式 享元模式

(十一)合成模式 Composite 合成模式是一组对象的组合,这些对象可以是容器对象,也可以是单对象.组对象允许包含单对象,也可以包含其他组对象,要为组合对象和单对象定义共同的行为.合成模式的意义是 保证客户端调用单对象与组合对象的一致性. class TreeNode{ private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector<TreeNode>();

java设计模式--观察者模式和事件监听器模式

文章转载于:http://www.java2000.net/p9452 复习设计模式,看到observer观察者模式,说法是该模式和iterator迭代器模式类似已经被整合进jdk,但是jdk提供了两种接口: 一.java.util.Observer -- 观察者接口 对应: java.util.Observable --受查者根类 二.java.util.EventListener -- 事件监听/处理接口 对应: java.util.EventObject -- 事件(状态)对象根类 研究了

Java设计模式(七)策略模式 模板模式

(十三)策略模式 策略模式定义了多个封装起来的算法,封装的算法可以相互替换,并且算法的变化不会影响到使用算法的客户.借用另一位大神的例子. interface ICalculator{ public int calculate(String exp); } abstract class AbstractCalculator{ public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayI

Java设计模式(三)原型模式

(五)原型模式 Prototype 原型模式目的是复制一个现有对象来生成新的对象,而不是通过实例化的方式.原型模式需要实现 Cloneable 接口,覆写clone方法,复制分为浅复制.深复制. 浅复制:将一个对象复制后,基本数据类型的变量都重新创建,引用类型,指向的还是原对象所指向的. 深复制:讲一个对象复制后,不论基本数据类型和引用类型,都是重新创建,是完全的彻底的复制. public class ProtoType { public static void main(String[] ar

Java设计模式(十一)访问者模式 中介者模式

(二十一)访问者模式 对已存在的类进行扩展,通常需要增加方法,但是如果需要的行为与现有的对象模型不一致,或者无法修改现有代码.在这种情况下,不更改类的层次结构,就无法扩展该层次结构的行为.如果运用了访问者模式,就可以支持开发人员扩展该类层次结构的行为. 和解释器模式一样,访问者模式通常是基于合成模式的. 访问者模式在不改变类层次结构的前提下,对该层次结构进行扩展. interface Visitor{ public void visit(VisiSubject sub); } interface

java设计模式(2)------DAO模式

java设计模式(2) 一.DAO模式 1.在java程序中,经常需要把数据永久化,或者需要获取永久化的数据,但是在这类过程中会有很多的问题出现,例如:数据源不同,存储类型不同,供应商不同.访问方式不同等,使用DAO模式能够以统一的接口进行数据持久化操. 2.解决方案: 3.DAO的理解 ①.DAO其实是利用组合工厂模式来解决问题的,并没有带来新的功能,所以学的其实就是个思路. ②.DAO理论上是没有层数限制的. ③.DAO的各层理论上是没有先后的. 4.DAO模式的本质是一层屏蔽一种变化.  

Java设计模式菜鸟系列(一)策略模式建模与实现

转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/39721563 今天开始咱们来谈谈Java设计模式.这里会结合uml图形来讲解,有对uml建模不熟的可以参考我的另一篇博文uml建模. 首先,个人觉得模式的设计就是一个将变化的东西和不变(稳定)的东西分离的过程.咱们的应用中可能有很多需要改变的地方,而模式要做的就是把它们"抽取"出来并进行"封装"和"实现",因此更多的时候咱们是面向接口编程