让对象的创建与销毁在掌控中。

Item 1: 使用静态工厂方法而非使用构造函数

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

优势:

1. 方法名+参数名,相较于构造函数,能更好的描述返回对象;

BigInteger(int, int, Random)
BigInteger.probablePrime(int, int, Random)

2. 不会像构造函数那样,每次调用不一定必须返回新对象;

  利用静态工厂方法可以得到类的单例对象,也可以辅助得到无法直接使用构造函数实例化的类的实例。

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton(){
//在类外部无法用私有构造函数实例化
} public static Singleton getInstance(){
return instance;
} public static void main(String[] args) {
//在类内部可以通过私有构造函数实例化
Singleton instance = new Singleton(); Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
//比较实例地址
System.out.println(instance1 == instance2);
}
}

3. 可以返回子类对象

  相比较于构造函数,静态工厂方法可以返回子类对象。这样的话,就可以将各个子类的具体实现隐藏起来,再通过静态工厂方法将功能暴露出来,使得方法易用而且也不用关心具体实现。理解的有些抽象,可以拿java.util.Collections举例。Collections无法实例化且方法全部为静态式,包含大量内部类且Default访问权。

隐藏子类的具体实现,并通过接口和静态工厂方法暴露功能,这样设计的一大好处就是将API集中起来易于使用和维护。

4. 同一方法调用可以返回不同子类的对象

乍看起来和第3条类似,但第3条指静态方法返回某个子类的对象,第4条指静态方法返回不同子类的对象。比如EnumSet类,有子类RegularEnumSet和JumboEnumSet。根据内部枚举类型,可以返回RegularEnumSet或者JumboEnumSet实例(也是面向接口编程的优势)。

5. 静态工厂方法所在类同静态工厂方法返回对象的类之间可以相互独立

感觉啰嗦,实际就是静态工厂方法返回对象的类可以后续再实现。这也是面向接口编程带来的好处 - 服务提供框架,具体例子可参考《谈谈服务提供框架

缺点:

1. 一般只有静态方法的类,其构造函数会声明为private。这样该类就无法被继承。但可以通过组合模式解决。

2. 在目前的API文档中,没有对静态工厂方法做特殊处理,导致在查阅方法时很容易忽略(个人认为这个是小问题,时常想着先考虑静态工厂方法即可)

Item 2: 当构造参数过多时,应当考虑构建者模式

当构造参数过多时,如果使用构造函数创建对象,需要写很多而且代码大量重复,不易使用;如果采用JavaBean的方式,则会把创建对象的过程分拆到一个个的set方法中,存在线程安全问题(对JavaBean的一点改进)。这个时候要考虑到构建者(Builder)模式。同时,也可以通过Builder模式创建出不可变对象

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; 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; protected Builder(int servingSize, int servings){
this.servingSize = servingSize;
this.servings = servings;
} public Builder calories(int calories){
this.calories = calories;
return this;
} public Builder fat(int fat){
this.fat = fat;
return this;
} public Builder sodium(int sodium){
this.sodium = sodium;
return this;
} public Builder carbohydrate(int carbohydrate){
this.carbohydrate = carbohydrate;
return this;
} public NutritionFacts build(){
return new NutritionFacts(this);
}
} private NutritionFacts(Builder builder){
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
this.fat = builder.fat;
this.sodium = builder.sodium;
this.carbohydrate = builder.carbohydrate;
} public int getServingSize() {
return servingSize;
} public int getServings() {
return servings;
} public int getCalories() {
return calories;
} public int getFat() {
return fat;
} public int getSodium() {
return sodium;
} public int getCarbohydrate() {
return carbohydrate;
}
}

创建对象代码示例:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build()

可以看到,想比于构造函数或者JavaBean的方式,Builder模式利用静态内部Builder类 + 链式调用创建实例。

当构造函数参数 >=4 个时,优先考虑Builder模式创建对象。

Item 3: 通过私有化构造函数或者枚举类来创建单例

创建单例的方式常用的有如下两种(这里不考虑double-lock checking避免多线程的问题),都需要将构造函数私有化。

1. 静态成员变量 + 私有构造函数

public class Elvis1 {

    public static final Elvis1 Elvis1 = new Elvis1();

    private Elvis1(){
System.out.println("private constructor to make instance singleton");
} public void check(){ } }

2. 静态工厂方法 + 私有构造方法

public class Elvis2 {

    private static final Elvis2 ELVIS_2 = new Elvis2();

    private Elvis2(){
System.out.println("private constructor to make instance singleton");
} public void check(){ } public static Elvis2 getInstance(){
return new Elvis2();
}
}

相比之下,静态工厂方法更加灵活些,可以控制方法返回的对象。

需要注意的一点,如果一个单例类需要序列化,那只implements Serializable是不够的,还需要以下2点:

(1) 字段声明 transient

(2) 提供readResolve()方法,否则在反序列化后会创建新的实例,而不是单例了。

public class Elvis implements Serializable{

    private static final Elvis ELVIS = new Elvis();

    private Elvis(){
System.out.println("private constructor to make instance singleton");
} public static Elvis getInstance(){
return ELVIS;
} private Object readResolve(){
// Return the one true Elvis and let the garbage collector take care of the Elvis impersonator.
return ELVIS;
}
}

测试代码如下:

public static void main(String[] args) {
try {
//serializable and deserializable
Elvis elvis = Elvis.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("t.tmp"));
oos.writeObject(elvis);
oos.close();
System.out.println("object serialized");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("t.tmp"));
Object deElvis = ois.readObject();
ois.close();
System.out.println("object de-serialized");
System.out.println(elvis);
System.out.println(deElvis); } catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

如果没有实现readResolve(),输出如下。对象地址不同,也就是反序列化创建了新对象。实现readResolve()方法保证序列化和反序列化得到对象唯一。

private constructor to make instance singleton
object serialized
object de-serialized
com.chris.item3.code.Elvis@6d6f6e28
com.chris.item3.code.Elvis@135fbaa4

3. 单元素枚举

这种方式保证实例唯一,反序列化后仍然唯一,而且最简洁。

public enum Elvis {
INSTANCE; public void sayHello(){
System.out.println("hello, a single-element enum!");
}
}
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.sayHello();
}

Item 4: 构造函数私有化,使得类无法实例化

当类不需要有实例时(比如工具类),最好将构造函数私有化。

public class Utility {

    private Utility() {
throw new AssertionError("tool class, can't be instanced");
} public static String toUpperCase(String s){
return s.toUpperCase();
} }

Item 5: 依赖注入而不是将依赖写死在类中

Item 9: 用 try-with-resource代替try-finally

类实现AutoCloseable后,为保证最终调用close()以释放资源,会使用try-finally的方式。

public static String firstLineOfFile2(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}

但这样存在2个问题,

1. 当方法中涉及到多个资源对象时,需要finally中分别调用close()

2. 当try块和finally块中同时抛出异常时,try块中的异常将被掩盖,往往try块中的异常是我们想要的。

使用try-with-resource方式可以避免以上问题,

public static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}

资源对象的close()方法将隐式进行,并且当try块中抛出异常时会抑制(suppress) close()方法抛出的异常,这样我们就得到了感兴趣的异常。

在实际业务场景中,更多的是try-catch-finally方式,即捕获感兴趣的异常并处理。

public static void copy2(String src, String des){
//try-catch-finally
InputStream in = null;
OutputStream os = null;
try {
in = new FileInputStream(src);
os = new FileOutputStream(des);
byte[] buffer = new byte[BUFFER_SIZE];
int num = 0;
while ((num = in.read(buffer)) >= 0){
os.write(buffer, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null){
in.close();
}
if (os != null){
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

用try-with-resource方式可以简化以上代码(省去finally块),更加简洁。

public static void copy(String src, String des){
//try-with-resource
try (InputStream in = new FileInputStream(src); OutputStream os = new FileOutputStream(des)) {
byte[] buffer = new byte[BUFFER_SIZE];
int num = 0;
while ((num = in.read(buffer)) >= 0){
os.write(buffer, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
}
}

总之,当类实现AutoCloseable后,为保证close()的调用,使用try-with-resource方式。

Effective Java - [2. 创建与销毁对象]的更多相关文章

  1. 和我一起学Effective Java之创建和销毁对象

    前言 主要学习创建和销毁对象: 1.何时以及如何创建对象 2.何时以及如何避免创建对象 3.如何确保它们能够适时地销毁 4.如何管理对象销毁之前必须进行的清理动作 正文 一.用静态工厂方法代替构造器 ...

  2. 《Effective Java》—— 创建与销毁对象

    本篇主要总结的是<Effecticve Java>中关于创建和销毁对象的内容. 比如: 何时以及如何创建对象 何时以及如何避免创建对象 如何确保及时销毁 如何管理对象销毁前的清理动作 考虑 ...

  3. 【读书笔记】《Effective Java》——创建和销毁对象

    Item 1. 考虑用静态工厂方法替代构造器 获得一个类的实例时我们都会采取一个共有的构造器.Foo x = new Foo(): 同时我们应该掌握另一种方法就是静态工厂方法(static facto ...

  4. Effective java笔记2--创建于销毁对象

    一.创建对象的两种方式 1.提供公有的构造器. 2.提供一个返回类实例的静态方法. 二.使用静态方法创建对象 优势: 1.静态工厂方法的一个好处是,与构造函数不同,静态工厂方法具有名字.产生的客户端代 ...

  5. Java进阶 创建和销毁对象

    最近准备写点Javase的东西,希望可以帮助大家写出更好的代码. 1.给不可实例化的类提供私有构造器 比如:每个项目中都有很多工具类,提供了很多static类型的方法供大家使用,谁也不希望看到下面的代 ...

  6. Effective Java笔记一 创建和销毁对象

    Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...

  7. effective java读书小记(一)创建和销毁对象

    序言 <effective java>可谓是java学习者心中的一本绝对不能不拜读的好书,她对于目标读者(有一点编程基础和开发经验)的人来说,由浅入深,言简意赅.每一章节都分为若干的条目, ...

  8. Effective Java(1)-创建和销毁对象

    Effective Java(1)-创建和销毁对象

  9. 《Effective Java》读书笔记(一)之创建和销毁对象

    最近在研读<Effective Java>一书,读书不做点笔记,感觉很容易就忘掉,于是用本篇博客来记录阅读此书的笔记. 郑重声明: 由于是<Effective Java>一书的 ...

随机推荐

  1. ACM程序设计选修课——1044: (ds:队列)打印队列(queue模拟)

    问题 A: (ds:队列)打印队列 时间限制: 1 Sec  内存限制: 128 MB 提交: 25  解决: 4 [提交][状态][讨论版] 题目描述 网络工程实验室只有一台打印机,它承担了非常繁重 ...

  2. 刷题总结——(一道很妙的题)Resistance(ssoj 欧几里得 )

    题解: 题目背景 151006 T1 题目描述 Picks 喜欢电路.这天他在研究元电路的时候,需要一个阻值为 (p/q)Ω 的电阻,然而他家中只有一大堆电阻为 1Ω 电阻.由于技术问题,Picks  ...

  3. TYVJ 1305 最大子序和 ++ 烽火传递

    描述 输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大. 例如 1,-3,5,1,-2,3 当m=4时,S=5+1-2+3=7当m=2或m=3时,S=5+1=6 输入 ...

  4. 【Visual Studio】VS2013的Release模式下进行调试(转)

    原文转自 http://blog.csdn.net/haizimin/article/details/50262901 在有的情况下,我们可能不能直接利用Debug模式进行程序调试,那么如何在Rele ...

  5. 15深入理解C指针之---内存释放

    一.手动申请的内存,必须及时进行内存释放,否则容易造成内存泄露.主要代码形式为: #include <stdio.h> #include <stdlib.h> int main ...

  6. udp 多播

    先来了解下UDP UDP 是UserDatagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一 ...

  7. Windwos2008如何关闭IE增强的安全配置

    如题 方法:

  8. AC日记——狼抓兔子 bzoj 1001

    Description 现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的, 而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一 ...

  9. PHP实现自定义中奖和概率算法

    最近玩<QQ飞车手游>,出了一款点券A车,需要消耗抽奖券抽奖,甚是激动,于是抽了几次,没想到中的都是垃圾道具,可恨可叹~~ 这几天项目中也涉及到了类似的概率操作,于是思考了一下,简单分装了 ...

  10. POJ - 2135最小费用流

    题目链接:http://poj.org/problem?id=2135 今天学习最小费用流.模板手敲了一遍. 产生了一个新的问题:对于一条无向边,这样修改了正向边容量后,反向边不用管吗? 后来想了想, ...