创建和销毁对象
# 静态工厂方法代替构造器
对于一个类而言,如果想要获得它的实例,有两种方法:
- 构造方法
- 静态工厂方法
其中构造方法是大家熟悉的,但是构造方法存在一些问题:
- 构造方法的名称必须和类名相同
- 构造方法的参数复杂,一个类可能有多个构造函数,如果要具体参数的内容就需要去代码里面查看
静态工厂方法如下(Boolean为例):
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
静态工厂方法能够拥有自己的名字,便于开发者更好调用。
静态工厂方法不需要每次去返回一个新的对象。只需要把返回的对象进行静态实例化,避免了对象的多次创建。例如在Boolean中就设置了两个静态实例。
/** * The {@code Boolean} object corresponding to the primitive * value {@code true}. */ public static final Boolean TRUE = new Boolean(true); /** * The {@code Boolean} object corresponding to the primitive * value {@code false}. */ public static final Boolean FALSE = new Boolean(false);
如果程序经常请求创建相同的对象,并且创建的对象代价很高,通过这样的技术极大地提升了性能。
# 多个构造器参数时要考虑使用构造器
现在假设存在一个Person类,其中这个类中name
和sex
属性是必须的,其他属性age
、hight
、weight
不是必须的,甚至随着业务的发展,这个类以后可能还有其他的属性。
public class Person {
private final String name;
private final Integer sex;
private final Integer age;
private final Integer height;
private final Integer weight;
public Person(String name, Integer sex) {
this(name, sex, 0);
}
public Person(String name, Integer sex, Integer age) {
this(name, sex, age, 0);
}
public Person(String name, Integer sex, Integer age, Integer height) {
this(name, sex, age, height, 0);
}
public Person(String name, Integer sex, Integer age, Integer height, Integer weight) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
this.weight = weight;
}
}
这样的构造器通常需要许多我本来不想设置,但是又不得不设置的值,同时参数会随着业务的增长变得越来越多,整个代码会特别复杂。这时候会去采用一个建造者的方法,通过链式来创建对象。
public class Person {
private final String name;
private final Integer sex;
private final Integer age;
private final Integer height;
private final Integer weight;
public static class Builder {
private final String name;
private final Integer sex;
private Integer age = 0;
private Integer height = 0;
private Integer weight = 0;
public Builder(String name, Integer sex) {
this.name = name;
this.sex = sex;
}
public Builder setAge(Integer val) {
age = val;
return this;
}
public Builder setHeight(Integer val) {
height = val;
return this;
}
public Builder setWeight(Integer val) {
weight = val;
return this;
}
public Person build() {
return new Person(this);
}
}
private Person(Builder builder) {
name = builder.name;
age = builder.age;
sex = builder.sex;
height = builder.height;
weight = builder.weight;
}
}
具体使用:
public class Main {
public static void main(String[] args) {
Person person = new Person.Builder("wxx", 0)
.setWeight(100)
.setAge(24)
.setHeight(180).build();
}
}
通过这样构建的代码,有如下好处:
- 阅读性强
- 必须参数和非必须参数能够进行分离
# 通过私有构造器强化不可实例化的能力
现在我有一个工具类,它只需要编写静态方法和静态域的类,但是我并不像让其实例化:
public class TestUtils {
public static void test1() {
System.out.println("test1");
}
public static void test2() {
System.out.println("test2");
}
public static void test3() {
System.out.println("test3");
}
}
上面的代码在缺少显示构造器的情况下,编译器会自动提供一个公有的、无参的缺省构造器。这样客户端是可以通过new
的方式来创建对象。为了让这个工具类无法实例化,只需要让这个类包含一个私有构造器。
public class TestUtils {
private TestUtils() {
}
public static void test1() {
System.out.println("test1");
}
public static void test2() {
System.out.println("test2");
}
public static void test3() {
System.out.println("test3");
}
}
这类思想多处用于很多经典工具源码中:
public class Arrays {
// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {}
}
# 避免创建不必要的对象
有如下简单的代码:
String test = new String("123456");
该语句在每次运行的时候都会创建一个新的String实例,如果这个用法是在一个循环中,或者是在一个频繁调用的方法中,那么就会创建许多个不必要的实例。
改进后的语句如下:
String test = "123456";
针对这种不必要的对象创建,需要在两个地方注意:
- 静态工厂方法
- 自动装箱
# 静态工厂方法避免创建不必要的对象
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
如果这段代码需要经常执行,我们需要根据传入的参数,返回True
和FALSE
,可以发现这两个返回结果每当执行这个方法一次,就需要进行返回。
如果每次返回我们都去创建一个新的对象,那么性能就很低下,所以对这种需要经常创建的对象,采用静态实例来避免创建不必要的对象。
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code true}.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);
# 自动装箱造成多余对象创建
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE ; i++) {
sum += i;
}
System.out.println(sum);
}
上述代码只是一个简单的累加,但是因为i是基本类型long
,但是sum是包装类型Long
。累加的过程就会涉及到自动装箱,每次往sum中增加long
时就会去构造一个实例。
改进后的代码如下(只需要改进sum为基本类型)
public static void main(String[] args) {
long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE ; i++) {
sum += i;
}
System.out.println(sum);
}
记住:要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
# try-with-resources 优于 try-finally
对于一些I/O相关的代码操作,为了确保资源最终关闭,我们会采用try-finally
方法:
public static void main(String[] args) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader("123456");
try {
bufferedReader.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
bufferedReader.close();
}
}
这样的代码会存在两个问题:
- 如果当调用的IO资源变多的时候,
finally
代码块就会有许多关闭资源的代码 try
和finally
都可能因为底层物理设置异常,抛出异常,如果第二个异常覆盖了第一个异常,就不便于异常的排查。
使用try-with-resources
进行代码改进,把需要关闭的资源放入try
中即可:
public static void main(String[] args) throws FileNotFoundException {
try(BufferedReader bufferedReader = new BufferedReader(new FileReader("123456"))) {
bufferedReader.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在处理必须关闭的资源时,始终要优先考虑用try-with-resources