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

来源:互联网 发布:数据对接的接口及方式 编辑:程序博客网 时间:2024/05/21 10:05

20170405开始读 effective java
1.使用静态构造方法代替构造器
优点:能够控制类的构造,(多例,单例),有意义的构造名称,精简代码,可以返回任何子类型
缺点:类如果不含有任何公共和受保护的构造方法就不能有子类
2.遇到多个构造参数时,考虑使用构建器
构造器简单实例:

public class  TestBuilder{    private int age;    private String name;    public static class Builder    {        private int age;        private String name;        public Builder age(int age)        {            this.age = age;            return this;        }        public Builder name(String name)        {            this.name = name;            return this;        }        public TestBuilder builder()        {            return new TestBuilder(this);        }        public Builder()        {            super();        }    }    private TestBuilder(Builder builder)    {        this.name = builder.name;        this.age = builder.age;    }}new TestBuilder.Builder().age(1).name("name").builder();

构建器模式依赖static 内部类(与普通类没有区别,唯一不同的就是静态内部类初始化时需要使用外部类名称引用)不能使用外部类的非静态参数,也不能使用非静态方法,因为静态内部类的初始化不依赖外部类,还需要注意静态内部类与静态方法静态参数的区别,静态方法与参数在存储空间中只会有一份实例,但静态内部类可以构造多个实例
3.使用私有构造器或者单个元素的枚举,强化单例模式
单例模式可以使用私有构造器,私有静态常量,公共静态方法初始化的方法构造,也可以使用公共静态常量,私有构造器直接返回单例.但前一种方法我们可以自己定义初始化过程,可以根据需要改变方式(单例,多例,或者为每个线程构造一个(此时需要持有一个含有线程信息与实例一一对应的 map,每次获取实例时从该 map 中获取当前线程对应的实例,没有再初始化(Thread.currentThread()))).注意所有的私有构造器都有可能被使用反射构造多个实例,此时可以在构造方法中增加校验,抛出异常来避免这种情况.
单例模式还需要注意对于序列化,为了保证单例必须写方法readResolve(),才能保证单例

    private Object readResolve()    {        return getInstance();    }

反序列化与反射方法对单例带来的副作用均可以使用单个元素的枚举类型来避免这些现象,demo 代码如下

public enum TestEnumSingle{    INSTANCE;    public void getString()    {        System.out.println("123");    }}

4.通过私有构造器强化不可实例化的能力
对于仅含有静态方法与静态常量的类使用私有构造器来阻止该类被继承与初始化,在构造器内部抛出 error 使得该类不能被内部类初始化,最好在私有构造器上增加注释,明确抛出 error 的作用(throw new AssertionError();)

5.避免创建不必要的对象
尽量重用对象,而不是创建新对象,优先使用基本类型而不是封装类型,避免不必要的自动装箱.但不能认为需要尽量避免新建对象,jvm 对于小的对象的构造与回收速度非常快,但像数据库连接对象这种创建代价很高的对象的尽量使用对象池来提高对象的重复利用

6.消除过期的对象引用
一般而言只要类是自己管理内存,就需要警惕内存泄漏问题.对于缓存对象,可以使用 WeakHashMap,此 map 只有缓存项的键存在外部引用时,该项才会有意义,否则该项会被回收

7.避免使用终结方法
java 终结方法不能保证会被及时执行,也不能保证一定会被执行,不应该依赖终结方法来更新持久化状态.终结方法中如果抛出异常,则终结方法立即终止,且异常会被忽略,不会打印出异常信息,甚至不会出现警告信息.使用终结方法会有非常严重的性能损失.如果类中封装的资源确实需要终止,必须提供一个显示的终止方法,并要求客户端在每个实例不再使用的时候调用这个方法,此时必须有一个私有域记录下该类是否被终止,在该类被终止后如果调用类中方法,需要抛出illegalSateException 异常,显示调用终止方法的一个例子是 IO 类中的 close 方法,显示调用终止方法通常与 try finally 语句结合起来使用,以确保及时终止.
终结方法的一个用法,可以用来防止对象持有者忘记调用显示的终止方法虽然这样做并不能保证资源被及时释放,也不能保证资源一定会被释放,但总比永远无法释放要强,此时终结方法应该在日志中记录,jdk 提供的 IO 类中的终结方法基本都在终止方法未被调用时调用终止方法.
终结方法的另一个用途是本地对等体对象的回收,本地对等体对应的 java 对象被回收时,jdk 并不知道本地对象,在本地对等体不持有关键资源的前提下,使用终结方法回收本地对等体,如果本地对等体永远必须被及时回收的关键资源,则该类必须使用显示的终止方法,此时的处理逻辑与第一种用法一致.
最后需要注意的是,如果子类覆盖了父类的终结方法但没有手工调用超类的终结方法,那么超类的终结方法永远不会被调用,因此需要在子类中显示的使用 super.finalize()来调用父类的终结方法,(父类的终结方法需要放在 finally 语句块中,以防子类终结方法抛出异常永远不会调用父类终结方法),为了防止子类忘记调用父类的终结方法,可以在超类中持有一个私有域,该域持有一个匿名内部类,只用来调用本类的终结方法,这种对象被称为终结守护者(保证终结方法会被调用),实例代码如下:

    private final Object finalizerGuardian = new Object(){        @Override        protected void finalize()            throws Throwable        {            //TODO 此处写终止外部对象的代码        }    };
0 0
原创粉丝点击