Chapter 2 Creating and Destroying Objects

item 1:Consider static factory methods instead of constructors

一个static factory method只不过是一个返回这个类的实例的static方法。与设计模式中的factory method是不同的概念。static factory methods有几点好处:

一.它们可以有自己的名字,而不像constructor只能与类名相同。一个好的名字可以增进可读性。

二.不像constructor那样,每次调用static factory methods的时候,并不一定都要创建一个新的对象,比如对immutable classes来说,可以返回那些已存在的对象。比如Boolean.valueOf(boolean)这个static factory method,就从不会创建一个新的对象。这种能多次调用,但都返回相同对象的能力 使得这个类可以对 什么时候有哪些自己的实例 有着严格的控制,我们把这种类叫做instance-controlled。Instance control可以让一个类保证它是一个singleton或者noninstantiable。或者,它可以让一个immutable class能保证自己的所有实例都是不同的,所以a.equals(b)当且仅当ab,那么就可以用ab来代替a.equals(b)来增强性能(Enum types就保证了这一点)。

三.你可以返回一个 是返回类型的子类 的对象。比如你的返回类型是一个interface,并且返回的对象的class都可以是非public的(注意是class,而不是method,class只能是public或者default的访问权,内部类例外)。这样的话,用户只关心interface中定义的API,而类的实现者可以随时更改此类,比如java.util.EnumSet,没有public constructor,只有static factories,如果只有小于等于64个enum types,那么它会返回一个RegularEnumSet实例,内部基于一个long实现;如果大于64个,那么就返回一个JumboEnumSet实例,内部基于一个long数组实现。而RegularEnumSet和JumboEnumSet这两个类对用户都是不可见的。

四.减少代码长度,比如一个static factory method:

public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}

然后就不必写两遍type parameters:

Map<String, List<String>> m = HashMap.newInstance();

而static factory methods不好的地方在于:

一.只有private的constructor的类不能被继承,package级别(简称default)的class或者private的内部class对于client来说也是不能被继承的。(我没看出来哪儿不好,作者也说这可能是因祸得福,因为这鼓励我们use composition instead of inheritance)。我做了个实验:private的内部类即使有个public的constructor,除了在它内部或者它的outer class中,都不能实例化一个这个内部类。

二.static factory methods不能很明显地区别于其他static methods。几种常见的static factory methods的命名:valueOf、getInstance、newInstance、getType(这里的Type应该是某个接口的名字,或者基类)、newType。

Item 2: Consider a builder when faced with many constructor parameters

当一个类的constructor需要很多参数,并且有些参数是可选并带有默认值的时候,最好的办法就是比如C#中的命名参数和默认参数。但Java里面没有怎么办?有一种古老的Telescoping constructor pattern,假设只有servingSize和servings是必选的:

public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings,int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings,int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
}

默认值被分开写在不同的方法里,而且这个例子中的这个类只有四个状态变量,如果有十个那么就吃瘪了。还有一种叫JavaBeans pattern的方法,就是只有一个可以设置必选值的constructor,并把默认值用field initialization写在相关field的后面,然后每个field都有一个public setter。然后当你创建一个对象的时候,必须在调用完构造函数后,马上紧跟着set你需要设置的参数。这种方法的弊端在于:由于要分开调用多个方法来构造这个对象,这个对象可能在构造到一半的时候处于一种不一致的状态。我的理解就是:这样更有可能会使得 还没有被完全构造好的对象被使用,就会出很难找的bug。另外,这样的对象不可能是immutable的,所以要保证线程安全还得另下功夫。那么如何才能模拟其他语言中的命名参数和默认参数?请参考下面的Builder Pattern:

public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat; public static class Builder {
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0; public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = 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;
}
}

然后当你需要一个这个类的实例的时候可以:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).fat(100).build();

我承认client用的时候确实比较爽,当implementor实现的时候也太tm麻烦了吧,我的理解就是:这个多出来的一个叫Builder的类基本就和本来的类一样,它的唯一作用貌似就是设好所有要设的instance fields,然后拷贝到本来的class中,注意这里是static inner class,所以它可以访问外部类的private的方法(但是只能访问static的,还记得constructor是隐式static的吗?)。关于参数验证应该放在哪里我没太看懂。你甚至可以用一个Builder来构造多个对象。另外,用这个Builder可以很好地诠释一个叫Abstract Factory的模式,就是让client传一个Builder给一个方法,让这个方法为client创建一个或多个对象,比如:首先定义一个接口:

// A builder for objects of type T
public interface Builder<T> {
public T build();
}

这里的T就是这个Builder是为谁服务的,拿刚才的例子来说就是NutritionFacts.Builder可以声明成实现了Builder<NutritionFacts>。然后就可以写一个方法接受一个这个接口类型的参数,比如:

Tree buildTree(Builder<? extends Node> nodeBuilder) { ... }

这个方法构造一个Tree对象给你,但是需要client提供的Builder,来构造树的每一个node。

这种Builder模式的缺点在于:你要构造一个对象前必须先构造一个这个对象的Builder;其次,这个模式的代码更加冗长,最好在你需要的参数确实很多的情况下使用。

Item 3: Enforce the singleton property with a private constructor or an enum type

singleton就是一个只被实例化一次的类。不可能让一个mock对象来取代一个singleton,所以难以测试,除非这个singleton的class实现了某个interface,然后client是通过这个interface来用这个singleton的。一种实现singleton的方法是用一个public的static final的filed:

public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
}

注意构造函数是private的。还有一种是用一个public的factory method:

public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public static Elvis getInstance() {
return INSTANCE;
}
}

这种方法的好处是更灵活,比如说如果你不想让这个类是个singleton了,你可以很轻松地把这个method修改成,比如 对每个线程返回一个实例。而用public field的好处是更简单直观,因为client可以看到这个field的声明,知道它是final的所以肯定是个singleton。如果想让一个singleton的class是Serializable的话,必须做一些额外工作,这个需要的时候再查阅其他资料吧。

还有一种用enum来实现singleton的方法:

public enum Elvis {
INSTANCE;
//这里可以定义其他方法
}

这种方法在功能上和刚才public field的方法一样(因为对client来说都是用Elvis.INSTANCE来得到这个singleton),但是更简洁,免费提供serialization功能,等。

Item 4: Enforce noninstantiability with a private constructor

经常会有一些utility classes,他们可能只包含static methods,这种class应该被弄成 不能被实例化的。方法如下:

public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
}

如果你不指定任何constructor的话,就会默认生成一个public的,所以这里我们要显式地写一个private的constructor,只是为了让这个类不能被实例化。异常是防止在类的内部不小心被实例化。这种方法也可以防止这个类被继承,因为子类需要调用基类的构造函数。

Item 5: Avoid creating unnecessary objects

先举个反例:

String s = new String("stringette"); // DON'T DO THIS!

为什么这样很不好?因为每当这句statement被执行的时候,都会创建一个新的对象,而且传给String构造函数的"stringette"这玩意儿本身也是个String的实例。所以说正确做法应该是:

String s = "stringette";

以这种方式得到或创建的String对象会得到复用。

Item1中讲过,用static factory methods可以避免创建不必要的对象,比如Boolean.valueOf(String)。而构造函数Boolean(String)每次都会创建一个新的对象。书上还举了一个例子就是:一个方法里面有几个local对象,创建起来比较麻烦,每次你调用这个方法都会创建这几个对象,所以作者说更好的办法是把这几个local variable变成类的private field,然后在static initializer中初始化他们(创建对象),于是只需要创建这一次。后面书上说的adapter模式不明觉厉,例子是:Map.keySet只是返回一个view(也就是你修改原来Map中的key也会在这个view中看到改变,反之亦然),每次调用这个方法其实都是返回相同的Set实例。还有要小心autoboxing,比如:Long sum = 0L;sum +=1;这时候貌似就会进行autoboxing,把1提升为一个Long对象,这是没必要的,把sum声明成原始类型long就行了。这里的意思只是:prefer primitives to boxed primitives,但不是说创建对象是很昂贵的所以尽量要避免。现代的JVM都会帮你处理得很好,所以别瞎操心了。Item 39与本条item相反:“Don’t reuse an existing object when you should create a new one”(defensive copy)。

Item 6: Eliminate obsolete object references

一个叫Stack的类里面有下面这么一个方法:

public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}

如果不写有注释的那行,那么就可能导致“内存泄漏”。如果一个元素已经出栈了,那么这个元素对用户来说就是已经obsolete了,可以被回收了,但是由于Stack内部用一个数组实现,这个数组中的一部分元素可能是obsolete的,这时候就需要你手动null掉它们。万一这些引用没有被null掉,它们也许指向一些很大的对象,这些对象又指向更大的对象。还有一个好处就是万一不小心access了某个obsolete的数组元素,那么就会NullPointerException,而不会默默地不报错。那么什么时候需要像这样null掉引用?简单地说就是:当一个类会管理自己的内存的时候,那么你就要注意是不是要null掉引用了。啥意思?拿上面的Stack来说,它内部的数组中的一部分是被allocated,而剩余部分是free的,但GC无法得知这一点,所以你只能通过null掉引用来告诉它。

另一种内存泄漏是缓存,当你把一个引用放入一个缓存中后,可能你就忘了,这时候你可以用WeakHashMap,只有对key的某个外部引用还存活着,这个entry才活着。

还有一种内存泄漏是Listeners以及其他callbacks。矮油,我刚好昨天看了观察者的设计模式,也就是说如果一个对象要求Subject把自己注册到通知列表,但是这个对象之后比如go out of scope了,那么Subject还保持着它的引用,还在不断通知一个已经没用的东西,咋整?用WeakHashMap好了。

Item 7: Avoid finalizers

finalizers无法保证他们会及时地被执行(我记得是一个对象要被GC的时候,看看他有没有finalize方法,有的话就让他再活一轮)。所以说你根本不知道它什么时候会被执行,而且不同的JVM也不一样。给一个Class写一个Finalize方法会延迟它的对象被reclaim,所以可能造成OutOfMemoryError。甚至有可能这个Finalizer根本就不会被执行都有可能。System.gc和System.runFinalization方法只是增加了finalizer被执行的几率,但并不保证。而且在Finalize方法里面有个uncaught exception,那么会被直接忽略掉,连stacktrace都不会打印。而且有Finalize的对象性能会大大受损。所以说如果一个对象封装了某些需要被terminated的资源,就在它的类里面显式提供一个回收资源的方法,然后需要让client用try-finally来调用它。顺便一提,这个对象必须记录自己的资源是不是已经被terminate掉了,如果是,那么再调用自己的某些方法的话就要throw IllegalStateException。那么finalizer有什么用?一是可以试着看一下用户是不是忘记terminate资源了,better late than never,如果是,那么就terminate资源并log一个warning,告诉用户他有bug。二是有些不太critical的native object可以在Finalizer中被回收,如果是critical的就必须像上面说的那样让client显式处理一下,那么问题就来了,子类的Finalizer最后必须用finally手动调用父类的Finalizer,万一忘了咋办?书上提供了一个叫finalizerGuardian的方法,但我不写了,因为实际中估计根本不会用到Finalizer,应该避免用它。

《Effective Java》读书笔记 - 2.创建和销毁对象的更多相关文章

  1. 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器

    类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...

  2. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  3. Effective Java 学习笔记之创建和销毁对象

    一.考虑用静态工厂方法代替构造器 1.此处的静态工厂方法是指返回指为类的对象的静态方法,而不是设计模式中的静态工厂方法. 2.静态工厂方法的优势有: a.使用不同的方法名称可显著地表明两个静态工厂方法 ...

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

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

  5. effective java 第2章-创建和销毁对象 读书笔记

    背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...

  6. Effective Java 读书笔记之二 对于所有对象都通用的方法

    尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1. ...

  7. Effective Java——(一)创建和销毁对象

    第一条 考虑用静态工厂方法代替构造器 什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化. 比如说 public class StaticFactory { private String na ...

  8. Effective Java(一)—— 创建和销毁对象

    在客户端(调用端)获取自身实例的方法: 公有的构造器: 类的静态工厂方法: 1. 使用静态工厂方法代替构造器 Boolean 是对基本类型 boolean 的包装类: public final cla ...

  9. Effective Java 读书笔记(二):对象通用方法

    1 重写equals方法时请遵守通用约定 (1)无需覆盖equals方法的情况 要求独一无二 不要求逻辑相等 超类已经覆盖equals方法,对其子类也适用 一个类是私有的或者是包私有(可以重写后抛出异 ...

随机推荐

  1. event.target和event.currentTarget的区别----0605加深理解

    target:触发事件的元素.currentTarget:事件绑定的元素.两者在没有冒泡的情况下,是一样的值,但在用了事件委托的情况下,就不一样了,例如: <ul id="ulT&qu ...

  2. HDU 1789 Doing Homework again(排序,DP)

    Doing Homework again Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  3. npm学习(九)之README.md文件

    包括文档(readme.md) npm建议您包含一个readme文件来记录您的包.自述文件必须有文件名readme.md.文件扩展名.md表示该文件是一个标记(markdown)文件.当有人发现您的包 ...

  4. LNMP完整安装教程

    软件下载地址   https://lnmp.org/install.html 本环境与外网生产环境一致(MySQL 5.6 + PHP 7.1 + CentOS + Nginx 1.12 ) 上图红色 ...

  5. vue项目1-pizza点餐系统3-路由知识点补充

    1.可以通过tag修改router-link的默认标签 <!--router-link标签默认是a标签,tag标签可以修改默认标签 --> <li><router-lin ...

  6. Vue+ElementUI学习总结(转载)

    Vue框架简介 Vue是一套构建用户界面的框架, 开发只需要关注视图层, 它不仅易于上手,还便于与第三方库或既有项目的整合.是基于MVVM(Model-View-ViewModel)设计思想.提供MV ...

  7. IntelliJ IDEA 2017 提示“Unmapped Spring configuration files found.Please configure Spring facet.”解决办法

    当把自己的一个项目导入IDEA之后,Event Log提示“Unmapped Spring configuration files found.Please configure Spring face ...

  8. SpringBoot项目中遇到的BUG

    1.启动项目的时候报错 1.Error starting ApplicationContext. To display the auto-configuration report re-run you ...

  9. MHA ssh检查,repl复制检查和在线切换日志分析

    一.SSh 检查日志分析 执行过程及对应的日志: 1.读取MHA manger 节点上的配置文件 2.根据配置文件,得到各个主机的信息,逐一进行SSH检查 3.每个主机都通过SSH连接除了自己以外的其 ...

  10. centos7 部署zabbix服务器端

    zabbix服务器端搭建与部署: 1.部署LAMP环境由于zabbix提供集中的web监控管理界面,因此服务在web界面的呈现需要LAMP架构支持.php 连接mysql服务,因为7版本mysql要收 ...