[读书笔记]Effictive Java 第2章 创建和销毁对象
1:用静态工厂方法代替构造器 Consider static factory methods instead of constructors
先看JDK中的如下几个静态工厂方法:
// see line 149 in Boolean of Java8
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
// see line 50 in Boolean of Java8
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
// see line 693 in BigInteger of Java8
// 用这个静态工厂函数我们就知道拿到的一定是个素数,而构造函数却无法判定
public static BigInteger probablePrime(int bitLength, Random rnd) {
if (bitLength < 2)
throw new ArithmeticException("bitLength < 2");
return (bitLength < SMALL_PRIME_THRESHOLD ?
smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
}
// see line 109 in EnumSet of Java8
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
// see line 205 in DriverManager of Java8
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
静态工厂方法与设计模式中的工厂方法模式不是一回事
静态工厂方法的优势:
● 和构造器不同的是,静态工厂方法有名称
● 和构造器不同的是,静态工厂方法不必在每次调用它们的时候都创建一个新对象,这可以使不可变类可以使用预先构建好的实例,或将构建好的实例缓存起来进行重复利用,如Boolean.valueOf(boolean),它从来不创建对象
● 和构造器不同的是,它可以返回原返回类型的任何子类型对象
● 所返回的对象的类可以随着每次调用而发生变化,取决于静态工厂方法的参数值。如上的EnumSet,它没有公有的构造器,只有静态工厂方法。它返回两个子类之一的一个实例,具体取决于底层枚举类型的大小:若元素为64个或更少,就像大多数枚举类型一样,静态工厂方法返回一个RegularEnumSet实例,用单个long进行支持;若枚举类型有65个或者更多元素,工厂就返回JumboEnumSet实例,用一个long数组进行支持。
● 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。如上的DriverManager.getConnection(String, Properties)方法,它返回的是个接口,服务提供方实现其接口即可
静态工厂方法的劣势:
● 类如果不含公有的或者受保护的构造器,就不能被子类化
● API文档中不被注明则很难知道有相关的静态工厂方法
2:遇到多个构造器参数时要考虑使用构建器 Consider a builder when faced with many constructor parameters
静态工厂和构造器有共同的局限性:它们都不能很好地扩展到大量的可选参数。当一个有大量可选参数需要的时候,类的创建代码可以有三类:
● 重叠构造器(telescoping constructor)–代码冗余复杂可读性可用性差
● JavaBeans模式,先调用一个无参构造器来创建对象,然后再调用setter方法来设置每个必要的参数,以及每个相关的可选参数–在构造过程中JavaBean可能处于不一致状态,线程安全问题
● 建造者模式,既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象,然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
// Required parameters by constructor
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
// Optional parameters by build
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).build();
Builder模式模拟了具名的可选参数,也适用于类层次结构,总之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择。
3:用私有构造器或者枚举类型强化Singleton属性 Enforce the singleton property with a private constructor or an enum type
4:通过私有构造器强化不可实例化的能力 Enforce noninstantiability with a private constructor
工具类不希望被实例化,因为实例化对它没有任何意义,然而在缺少显示构造器的情况下,编译器会自动提供一个公有的、无参的缺省构造器,由于只有当类不包含显示的构造器时,编译器才会生成缺省的构造器,因此只要让这个类包含一个私有构造器,它就不能被实例化,如Arrays这个工具类:
public class Arrays {
…………
// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {}
}
读书的时候想了一个白痴问题,但值得记录下,我就想:既然Arrays都没有被实例化,那我们是怎么用到里面的方法的呢?比如怎么调的sort?
这很简单啊。。。因为实例化是把类的对象实例化到堆中,这才叫实例化,你类本来就在方法区中,当然可以访问里面的静态方法了。。。sort是static的啊
5:优先考虑依赖注入来引用资源
不要用Singleton和静态工具类来实现依赖一个或多个底层资源的类,而应该将这些资源或者工厂传给构造器(或者静态工厂,或者构建器),通过它们来创建类,这种方式称为依赖注入。
想想spring编写依赖注入时,其实不就是把资源作为一个字段向类中进行了注入,也就是把资源传给了构造器。
6:避免创建不必要的对象
极端反例,每被执行一次就会创建一个对象:
String s = new String("bikini");
正确方式,只会存在一个String实例:
String s = "bikini";
如下一个实验:
public class Test {
public static void main(String[] args) {
final long a = System.currentTimeMillis();
Solution.sumQuick();
final long b = System.currentTimeMillis();
Solution.sumSlow();
final long c = System.currentTimeMillis();
System.out.println("quick: " + (b - a));
System.out.println("slow: " + (c - b));
}
}
class Solution {
public static long sumSlow() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
public static long sumQuick() {
long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
}
输出如下:
quick: 941
slow: 5627
用时5.6秒的函数把sum声明成了Long而不是long,意味着程序构造了大约个232多余的Long对象,所以要优先使用基本类型而不是装箱基本类型,当心无意识的自动装箱
9:try-with-resources优先与try-finally
看两段代码
// try-finally - No longer the best way to close resources
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
// try-with-resources - the best way to close resources
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
// try-finally is ugly when userd with more than one resource
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0) {
out.write(buf, 0, n);
}
} finally {
out.close();
}
} finally {
in.close();
}
}
// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0) {
out.write(buf, 0, n);
}
}
}
何为资源?
实现了Closeable和AutoCloseable的类即为资源