最近在读Effect Java这本书,发现一些我们平时开发常见或者没特别注意的问题有很多,在此对一些内容做记录。其中的Java开发的建议的每一条都有相应的详细讲解我觉得是挺受用的。
考虑使用静态工厂方法替代构造器
我们这里说的使用静态工厂方法不是设计模式中使用工厂方法模式来创建对象,是说在类中提供静态的工厂方法而不是共有的构造器方法来创建对象。为什么要这样做呢,这样做肯定有它的好处的,下面我们分析出这样做的好处和小部分的不足。
优势:
静态工厂方法不同于构造器方法,它们可以有自己的名称。
构造器我们知道需要有和类名相同的构造方法,我们可以拥有不同的构造参数的构造方法来按照我们的需求来创建对象。如果构造方法众多,这个时候会在构造的时候不好按照需求参数来明确的区分,并且默认的构造方法如果参数冗长那我们构造对象的时候就很痛苦了,当我们提供一个附有含义的名字的静态工厂方法来构造对象那就更加明确与简练。
不必在每次调用它们的时候都创建一个对象。
当我们有这样的需求的时候,只有一个类的对象实例,我们可以预先创建好对象,然后通过静态工厂方法获取提取建立的对象即可。
可以返回该类的任何子类型对象。
这个和我们平时的多态的概念很相似,我们客户端不必在乎具体是哪个的实现,我们使用客户端使用接口指向对象的静态工厂方法的引用即可,当要修改和删除对象的实现的时候,我们客户端都可以隔离出来。
缺点:
类如果不含有共有的或者受保护的构造器,就不能被子类化(即不能够被继承)。
这时我们使用静态工厂方法的时候就不能扩展返回任意的子类型了。它们与其他静态方法没有区别。
这个我的理解是我们如果使用静态工厂方法来构造对象,这样它实质是就是一个静态的工厂方法(废话了)。它不同于默认的那些构造方法和类名相同,我们不能一下就知道是如何来创建这个类的,那么就有了如下的一些静态的工厂方法的命名规则。
方法名 | 含义 |
---|---|
valueOf | 这个方法的返回实例就是它的参数具有的相同的值,就是类型参数转换的一个方法 |
of | valueOf的一种更加简洁的替代 |
getInstance | 返回的实例通过方法的参数来描述的,但是不能够说与参数具有同样的值。对于SingleTton来说,这个方法没有参数,并返回唯一的实例 |
newInstance | 和getInstance一样,但是它获取的实例都是一个新的实例对象 |
getType/newType | 在不同的类中,Type表示工厂方法返回类对应的对象类型 |
有多个构造器参数时要考虑使用构造器(Builder)
静态工厂和构造器有个共同的局限性:它们都不能很好的扩展到大量的可选参数。对于有很多的构造参数的类写起来可能有很多不必要的参数,那写的很不舒服,也不好看。
这里有一种很好的方式来解决这个问题。既能保证像重叠构造器那样的安全性,也能保证像JavaBeans模式那样的好的可读性。这就是Builder模式。示例如下:
/**
* Author: Lyman
* Email: lymanye@gmail.com
* Data: 2017/4/12
* Description:
*/
public class Programer {
private String java, c, php, unity3D, js, javaScript, html;
public Programer(String java, String c, String php, String unity3D, String js, String javaScript, String html) {
this.java = java;
this.c = c;
this.php = php;
this.unity3D = unity3D;
this.js = js;
this.javaScript = javaScript;
this.html = html;
}
public String getJava() {
return java;
}
public void setJava(String java) {
this.java = java;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
public String getPhp() {
return php;
}
public void setPhp(String php) {
this.php = php;
}
public String getUnity3D() {
return unity3D;
}
public void setUnity3D(String unity3D) {
this.unity3D = unity3D;
}
public String getJs() {
return js;
}
public void setJs(String js) {
this.js = js;
}
public String getJavaScript() {
return javaScript;
}
public void setJavaScript(String javaScript) {
this.javaScript = javaScript;
}
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
@Override
public String toString() {
return "Programer{" +
"java='" + java + '\'' +
", c='" + c + '\'' +
", php='" + php + '\'' +
", unity3D='" + unity3D + '\'' +
", js='" + js + '\'' +
", javaScript='" + javaScript + '\'' +
", html='" + html + '\'' +
'}';
}
public static class Builder {
private String java, c, php, unity3D, js, javaScript, html;
public Builder() {
java = c = php = unity3D = js = javaScript = html = "unkown";
}
public Builder addJava(String j) {
this.java = j;
return this;
}
public Builder addC(String c) {
this.c = c;
return this;
}
public Builder addPhp(String p) {
this.php = php;
return this;
}
public Builder addUnity3D(String u) {
this.unity3D = u;
return this;
}
public Builder addJs(String js) {
this.js = js;
return this;
}
public Builder addJavaScript(String ja) {
this.javaScript = ja;
return this;
}
public Builder addHtml(String html) {
this.html = html;
return this;
}
public Programer build() {
return new Programer(this.java, this.c, this.php, this.unity3D, this.js, this.javaScript, this.html);
}
}
}
创建对象如下:
public class Test {
public static void main(String[] args) {
Programer.Builder builder = new Programer.Builder();
Programer programer = builder.addJava("kown").addC("kown").addHtml("kown").build();
System.out.print(programer.toString());
}
}
Builder模式总结如下:
- Builder模式可以对其参数强加约束条件,并且可以有多个可变参数。
- 可以利用单个Buider建造多个对象。
- Builder模式的不足是要先创建Builder对象再创建我们需要的对象造成额外的内存开销,但是我们只在很多个参数的情况下才使用这种模式。
用私有构造器或者枚举类型强化Singleton
Singleton指对象是一个唯一的实例。在Java1.5之前,实现Singleton有两种方式,他们都是把构造器私有,提供一个类的唯一的实例。第一种实现如下:
public static final Person INSTANCE = new Person();
private Person(){
}
客户端通过类名直接拿到唯一的实例对象。
第二种方法提供静态工厂方法实现:
private static final Person INSTANCE = new Person();
private Person(){
}
public static Person getInstance(){
return INSTANCE;
}
使用静态工厂方法模式更加灵活。在不改变其API的前提下,我们可以改变这个类是否为Singleton。当我们实例化的需求改变可以更好的通过这个方法来处理。例如改为每个线程返回一个实例。
如果要使用上述两种方法之一实现Singleton类变成可序列化的,如果简单的加上implements Serializable是不够的。因为在反序列化的时候会重新的创建不同的对象,我们必须把实例域加上transient关键字,不让其实例序列化,并且提供读取序列化的对象的方法返回我们之前创建的实例。
从Java 1.5以后,有一种好用的方法来实现Singleton。只需要编写一个包含单个元素的枚举类型这种方式JVM会保证enum不能被反射并且构造器方法只执行一次。为我们免费处理了序列化。
private Person(){
}
public static Person getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private Person singleton;
Singleton(){
singleton = new Person();
}
public Person getInstance(){
return singleton;
}
}
测试我们两次拿到的对象
public static void main(String[] args) {
Person person1 = Person.getInstance();
Person person2 = Person.getInstance();
System.out.println(person1 == person2);
}
我们返回的结果为true,即我们拿到的是同一个对象。
通过私有构造器强化不可实例化的能力
我们有时候设计我们工具类提供一些工具方法调用,但是我们不希望他们可以被实例化,它的实例没有任何意义。例如java.util.Arrays类,这时提供一个私有的构造方法可以解决这个问题,因为当我们没有写构造方法的时候会有一个默认的无参数构造方法可以被调用,当我们显示的声明一个私有的构造方法可以解决这个问题,但是要注意一点,那么这个类就不能别子类继承了。我们上面提到过这个问题。
避免创建不必要的对象
- 我们写代码的时候会无意中创建一些不必要的对象来,这些对象没有任何实际的作用,但是它消耗了我们的内存占用:例如基本数据的拆箱装箱;对象的实例化引用保存,避免多次创建对象;
- 并不是说我们要避免创建对象,当我们通过维护对象池来避免创建对象的时候要注意,除非对象池中的对象是很重量级的。例如数据库连接池。因为建立对象的代价很大。一般使用对象池会让代码看上去更加复杂,增加内存占用,使用之前要谨慎。
消除过期的对象引用
在Java中我们不需要手动的去对内存去进行管理,我们有垃圾回收机制可以帮助我们释放掉不需要的对象。但是当我们持有一个对象的强引用的时候,垃圾回收器会以为这个对象有人一直要使用,不会进行回收,会造成内存泄漏(内存空间占着茅坑XXX)。
但是我们要注意不是程序不用对象了就把它清空,这样做没有必要。清空对象的引用是一种例外,而不是规范行为。消除对象的引用最好的方法是让包含该对象的引用的变量结束其生命周期。
什么时候清除引用?当程序自己管理内存,持有引用垃圾回收器无法去清楚,那么我们要主动的清除这些数据引用。例如在Android的Activity中在Activity销毁的时候释放一些引用的资源。
避免使用finalize方法
垃圾回收器准备释放内存的时候会调用这个方法。那么灵机一动,咋们上面的那个消除对象的引用可以在这里面做不是最合适的嘛。但是并不能,因为这个方法不能保证及时的执行,有可能你的程序都oom了这个方法还没有执行释放内存。
如果要使用这个方法那么久把它当作一个最后的安全栏,记得调用父类的finalize方法。