在保证程序清晰性和简洁性的基础下,重用具有相同功能的对象,避免创建新的对象
当该对象的状态是不变化的,新创建的对象具有的功能与原来对象相同的,那么就避免创建新的对象,直接使用原来的对象。
除了重用不可变的对象之外,也可以重用那些已知不会修改的可变对象。
如果代码中发现这种情况,就做出调整,调整为使用一个对象,避免使用新的对象。
1、通过使用静态工厂方法,可以避免创建不需要的对象
对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。例如,静态工厂方法Boolean.valueOf(String)几乎总是优先于构造器Boolean(String)。构造器在每次被调用的时候都会创建一个新的对象,而静态工厂方法则从来不要求这样做,实际上也不会这样做。
package com.czgo.effective;
/**
* 用valueOf()静态工厂方法代替构造器
* @author AlanLee
* @version 2016/12/01
*
*/
public class Test {
public static void main(String[] args) {
// 使用带参构造器
Integer a1 = new Integer("1");
Integer a2 = new Integer("1");
//使用valueOf()静态工厂方法
Integer a3 = Integer.valueOf("1");
Integer a4 = Integer.valueOf("1");
//结果为false,因为创建了不同的对象
System.out.println(a1 == a2);
//结果为true,因为不会新建对象
System.out.println(a3 == a4);
}
}
可见,使用静态工厂方法valueOf不会新建一个对象,避免大量不必要的对象被创建,实际上很多类默认的valueOf方法都不会返回一个新的实例,比如原文提到的Boolean类型,不仅仅是Java提供的这些类型,我们在平时的开发中如果也有类似的需求不妨模仿Java给我们提供的静态工厂方法,给我们自己的类也定义这样的静态工厂方法来实现对象的获取,避免对象的重复创建,但是也不要过度迷信使用静态工厂方法的方式,这种方式也有它的弊端(有关静态工厂方法的知识可以看看《Effective Java》第一条),个人很少使用这种方式,平时的类多创建个对象也不会有太大的影响,只要稍微注意下用法就ok了。
2、除了重用不可变的对象之外,也可以重用那些已知不会修改的可变对象
例子1:
反面例子:
package com.czgo.effective;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtilBad {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
private static final String UNAME = "root";
private static final String PWD = "root";
public static Connection getConnection() {
Connection conn = null;
try {
// 1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 2.获得数据库的连接
conn = DriverManager.getConnection(URL, UNAME, PWD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}
该类提供的getConnection方法获取JDBC数据库连接对象,每次调用该方法都会新建一个conn实例,而我们知道在平时的开发中数据库连接对象往往只需要一个,也不会总是去修改它,没必要每次都去新创建一个连接对象,每次都去创建一个实例不知道程序会不会出现什么意外情况,这个我不知道,但有一点是肯定的,这种方式影响程序的运行性能,增加了Java虚拟机垃圾回收器的负担。我们可以对它进行改进。
改进版本:
package com.czgo.effective;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
private static final String UNAME = "root";
private static final String PWD = "root";
private static Connection conn = null;
static {
try {
// 1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 2.获得数据库的连接
conn = DriverManager.getConnection(URL, UNAME, PWD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
return conn;
}
}
我们使用了静态代码块来创建conn实例,改进后只有在类加载初始化的时候创建了conn实例一次,而不是在每次调用getConnection方法的时候都去创建conn实例。如果getConnection方法被频繁的调用和使用,这种方式将会显著的提高我们程序的性能。除了提高性能之外,代码的含义也更加的清晰了,使得代码更易于理解。
例子2:
反面例子:
方法isBabyBoomer,用来检查一个Person实例,该实例是不是出生在婴儿期(1946年1月到1965年1月)。Person类的每个实例,每次调用该方法,都会创建相同的boomStart对象,boomEnd对象。一次调用,创建如下对象,一个Calendar,一个TimeZone,两个Date对象。当然,它们都是局部变量,每次方法调用完后,就被销毁。
改进版本:
因为boomStart和boomEnd在所有实例中功能相同,而且还没有被修改,那么,就可以提出来,让它成为静态变量。为所有实例共用。
3、有一种创建多余对象的新方法,称作自动装箱(autoboxing)
package com.czgo.effective;
public class TestLonglong {
public static void main(String[] args) {
Long sum = 0L;
for(long i = 0; i < Integer.MAX_VALUE; i++){
sum += i;
}
System.out.println(sum);
}
}
所以整个过程运行下来,在执行过程中,创建了多余的Integer.MAX_VALUE个Long对象。Integer的最大值是2的31次方,32位可以用来表达最大的数字是2的31次方,其中1位用来做符号位
使用自动装箱时,要注意它有可能创建新的对象。
4、总结
最后,不要错误地认为"创建对象的代价非常昂贵,我们应该尽可能地避免创建对象"。相反,由于小对象的构造器只做很少量的显示工作,所以小对象的创建和回收动作是非常廉价的,特别是在现代的JVM实现上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。
反之,通过维护自己的对象池(Object pool)来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价是非常昂贵的,因此重用这些对象非常有意义。而如今的JVM(Java虚拟机)具有高度优化的垃圾回收器,如果是轻量的对象池可能还不如垃圾回收器的性能。
这里我们说到“当你应该重用现有对象的时候,请不要创建新的对象”,反之我们也应该考虑一个问题“当你应该创建新对象的时候,请不要重用现有的对象”。有时候重用对象要付出的代价要远远大于因创建重复对象而付出的代价。必要时,如果没能创建新的对象实例将会导致潜在的错误和安全漏洞;而不必要地创建对象则只会影响程序的风格和性能。
参考: