如何更好的建立Java对象

静态工厂

除了使用构造函数建立对象外,还可使用静态工厂来建立对象,JDK中大量使用了这种技巧,例如:前端

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

静态工厂的优点

使用静态工程有几大优点,具体以下:java

静态工厂有名称编程

当一个类须要多个带有相同签名的构造器时,就用静态工厂方法代替构造函数,而且慎重地选择名称以便突出他们以前的区别。json

相比之下,构造函数只能有一个名称,若是要接受不一样的参数,须要进行重载,面对这样的API,用户很难记住还用哪一个构造函数去建立一个对象,也常常调用错误的构造器,若是在遇到没有参考文档的状况,每每不知从何入手。设计模式

没必要每次都建立一个新的对象缓存

静态方法能够重复调用,返回同一个对象,这种类被称为实例受控的类(instance-controlled)。经过这种技术衍生出来的一个设计模式就是单例模式(Singleton)。安全

关于单例模式的实现能够参考:http://www.javashuo.com/article/p-dwkzzxqk-da.html函数

能够不返回对象自己的类型工具

这句话比较绕,但解释起来很简单,意思是,静态工厂能够返回要返回的对象的父类或接口。性能

也就是说,能够要求客户端调用者使用接口来引用(接收)静态工厂返回的类型,而不是经过它的实现类来引用被返回的对象,这是一个好习惯,也就是咱们常说的,面向接口编程。

虽然构造函数也能够作到这一点,可是,静态工厂能够根据接收的参数返回不一样的类型,这点是构造函数作不到的,好比:

public static ColorInterface createInstance(int type) {
    if (type == 0) {
        return new Red();
    } else {
        return new Green();
    }
}

并且,若是有新的类型须要返回,也能够添加到静态工厂中。

建立对象的语法更简洁

在调用泛型对象时,调用构造函数一般须要编写两次泛型类型:

Map<String, List<String>> m = new HashMap<String, List<String>>();

这种方式在JDK1.7中获得类优化,加入了类型推导,在1.7中不须要再编写两次泛型类型:

// jdk1.7+
Map<String, User> m = new HashMap<>();

也可使用静态工厂让其建立变得简单,而且与JDK版本无关:

Map<String, List<String>> m = Factory.newInstance();

静态工厂的不足

与其余静态方法没有任何区别

静态方法没有像构造函数那样在API中明确标识,所以,若是要用静态工厂实例化一个类,须要特地去了解API文档,可是使用一些命名规范,能够弥补这个劣势,这些规范以下:

valueOf:该方法返回的实例与它的参数具备相同的值,目的是作类型转换;

of:是valueOf的一种更简洁的替代;

getInstance:返回单例对象;

newInstance/createInstance:建立一个实例,至关于new一个对象;

构建器

静态工厂与构造函数有一个共同的局限性:建立对象时,不能很好地扩展大量的可选参数。

举例来讲,在建立一个有20个域(字段)的对象,其中有3个域是建立时必需要初始化的,另外17个是选填初始化的,那么就要进行大量的构造函数重载,提供一个3个参数(3个必填域)的构造函数,虽然重载一个4个参数(3个必填域,1个可选域),随后提供一个5个参数的、6个参数的……

重载构造函数虽然可行,可是类的代码很难写,而且难以阅读与维护。

由此需求,Builder设计模式就诞生了。它不直接生成想要的对象,而是让API使用者利用全部的必填参数来建立一个建造者对象。而后利用建造者对象的setter方法去初始化选填参数。最后调用建造者的build方法来生成对象。

实战经验

不少Web工程都建立了一个返回值对象,用于向前端返回信息,其结构以下:

public class ResponseBean() {
    //  状态码,例如:0表明成功,1表明异常
    private int code;
    //  要返回的信息
    private String message;
    //  要返回的数据
    private Object data;
}

上面的类中,在建立ResponseBean对象时,只有code是必填的,message和data都是选填的,这种状况就可使用Builder模式进行对象的建立(例子中只有3个字段,考虑字段更多的状况)。

分享一个实现,具体以下(能够将WebAppResult设计成泛型类):

package cn.yesway.pms.entity;

import cn.yesway.pms.enums.ResultStatus;

/**
 * Web应用的响应数据实体对象。<br>
 * 包含了与前端约定的属性。
 */
public class WebAppResult {

	// 响应状态
	// 不是http的响应状态,是业务系统的响应状态
	// 业务经过返回Success
	// 业务不经过(例如,登陆失败等)返回Fail
	private final ResultStatus status;
	// 发送给前端的信息
	private final String msg;
	// 发送给前端的json数据
	private final Object data;

	private WebAppResult(Builder builder) {
		this.status = builder.status;
		this.msg = builder.msg;
		this.data = builder.data;
	}

	/**
	 * WebAppResult的构造器。
	 */
	public static class Builder {

		private final ResultStatus status;
		private String msg;
		private Object data;

		public Builder(ResultStatus status) {
			this.status = status;
		}

		public Builder msg(String msg) {
			this.msg = msg;
			return this;
		}

		public Builder data(Object data) {
			this.data = data;
			return this;
		}

		public WebAppResult build() {
			return new WebAppResult(this);
		}
	}

	/**
	 * @return 得到响应状态
	 */
	public ResultStatus getStatus() {
		return status;
	}

	/**
	 * @return 得到响应消息
	 */
	public String getMsg() {
		return msg;
	}

	/**
	 * @return 得到响应数据
	 */
	public Object getData() {
		return data;
	}

}

建立WebAppResult实体以下:

// 没有data
return new WebAppResult.Builder(ResultStatus.SUCCESS).msg("操做成功").build();

这些编写,可让代码很容易的编写,并且易于阅读。

条件检测

将参数从builder拷贝到对象后,对象域能够对这些参数进行检查,若是失败能够抛出IllegalStateException异常。在setter时也能够加入条件约束,失败抛出IllegalArgumentException。在set阶段就会检查条件,而不是等到调用build方法时。

存在的不足

为了建立对象,必须建立Builder。若是十分注重性能,可能就会形成问题。

通常在须要用不少参数去构造一个对象时,好比4个参数或更多,使用Builder更为合适。

简而言之,若是类的构造函数或者静态方法中具备多个参数,设计这种类时,Builder模式就是一种不错的选择。特别是大多数参数都是可选的时,相比构造函数和静态工厂,Builder模式更易于阅读和编写。

建立不可实例化的类

有时候可能须要编写只包含静态方法和静态域的类。这些类的名声很很差,由于有些人在面向对象语言中滥用这样的类。尽管如此,他们也确实有他们特有的用处。例如java.lang.Math和java.util.Arrays。

这样的工具类不但愿被实例化,实例对它没有意义。而后在默认状况下,编译器会提供一个公有的、无参数的构造函数。咱们能够将类的构造函数显示实现为private的,来防止类的实例化:

public class Util {
    private Util() {
        throw new AssertionError();
    }
}

AssertionError不是必须的,它是防止在Util类的内部,在任何状况下都不会实例化该类。

避免建立没必要要的对象

通常来讲,最好能重用对象而不是在每次须要的时候就建立一个相同功能的对象。重用方式既快速,又流行。若是对象是不可变的(immutable),它就始终能够被重用。

对用同时提供类静态工厂和构造函数的不可变类,一般可使用静态工厂而不是构造函数,以免建立没必要要的对象,例如:

// 推荐
Boolean.valueOf(String);
// 不推荐
Boolean(String)

有些时候,对象并不必定是不可变对象,这时候能够考虑适配器模式(adapter),有时也叫视图模式(view)。

它把功能委托给一个后备对象(backing object),从而为后备对象提供一个能够替代的接口。因为适配器除了后备对象以外,没有其余的状态信息,因此针对某个给定对象的特定适配器而言,它不须要建立多个适配器实例,关于适配器模式的信息能够参考:http://www.javashuo.com/article/p-sfpkgdol-dz.html

避免建立没必要要的,不等于尽可能避免建立对象,因为小对象的构造器只作了少许工做,它的建立和回收是特别廉价的,特别在现代的JVM上更是如此。经过建立对象提高程序的清晰性、简洁性和功能性是件好事。关于对象的分配和回收,能够参考:http://www.javashuo.com/article/p-rxxjxtjk-o.html

过时的对象引用

过时引用很容易形成内存泄漏,在支持垃圾回收的语言中,内存泄漏是很隐蔽的,这类问题的修复方法很简单,一旦对象引用已通过期,只须要清空这些引用便可。

清空对象引用应该是一种例外,而不是一种规范行为。

通常而言有三个常见问题会形成内存泄漏:

第一个常见问题,只要类的本身管理内存的,就要警戒内存泄漏,解决方法是一旦对象被释放,就清空这个对象内包含的任何引用;

第二个常见问题,来自于缓存的内存泄漏,解决方法是按期清除过时的缓存;

第三个常见问题,来自于监听器和回调器,解决方法是确保手动取消注册,并在监听器中将注册的对象保存在弱引用(week reference)中;

避免使用finalizer

终结方法通常状况下是没必要要的。Java中能够采用try-finally来完成相似的工做。

终结方法不能确保被及时的调用,并且在不一样的JVM实现中,调用终结方法的时间点大不相同,并且终结方法的线程优先级不高。Java语言规范不只不保证总结方法会被及时的调用,并且根本就不保证他们会被执行。

结论为:不该该依赖终结方法来更新重要的状态。

避免使用终结方法,只须要提供一个显示的终止方法(自定义),并要求客户端在每一个实例再也不有用时调用这个方法,例如InputStream、OutputStream、Connection等都提供类close()方法。他们一般是配合try-finally结构结合起来使用的。

终结方法的用途有两种:

第一种,当客户端忘记调用上面提到的显示终止方法时,充当“安全网”(safety net),使用这种方法,强烈建议在终结方法中,发现资源未终止,必须在日志中记录一条警告。

第二种,在使用native有关的操做时,JVM不知道须要回收这些native object,在native object不拥有关键资源的前提下,在终结方法中回收它。

注意,若是子列覆盖类父类的终结方法,须要手动调用父类的终结方法,不然父类的终结方法永远不会执行。

再次强调,除了以上两种状况,不然请不要使用终结方法。

相关文章
相关标签/搜索