Effective Java 第三版读书笔记(创建和销毁对象2)

第3条.用私有构造器或者枚举类型强化Singleton属性

Singleton是指仅仅被实例化一次的类。

实现Singleton的两种常见方法:

1.公有静态成员是final域

package com.example.ownlearn;

public class Elvis {
    private static int count = 0;
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){count++;}

    public static int getCount() {
        return count;
    }

    
}

2.公有成员是静态工厂方法:

package com.example.ownlearn;

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

    public static int getCount() {
        return count;
    }

    public static  Elvis getInstance(){return INSTANCE;}
}

对于静态方法 Elvis.getInstance 的所有调用,都会返回同一个对象引用。

但是这两种方法都有一个相同的问题,那就是我们依然可以通过反射的方法,调用到构造函数,创建新实例:

package com.example.ownlearn;

import java.lang.reflect.*;

import static com.example.ownlearn.NyPizza.Size.SMALL;

public class Test {

    public void get() {
        Elvis elvis = Elvis.INSTANCE;
        int count = elvis.getCount();

        Constructor[] constructors = elvis.getClass().getDeclaredConstructors();
        AccessibleObject.setAccessible(constructors, true);

        Constructor<Elvis> ce = constructors[0];
        try {
            elvis = ce.newInstance();
            count = elvis.getCount();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

}

为了将上述方法实现的Singleton类变成可序列化的,仅仅在声明中加上implements Serializable是不够的。为了维护和并保证Singleton,必须声明所有实例域都是瞬时的,并提供一个readResolve方法,否则每次反序列化一个序列化的实例时,都会创建一个新的实例。

private Object readSolve(){
    return INSTANCE;
}

3.声明一个包含单个元素的枚举类型:

public enum Elvis{
    INSTANCE;
}

单元素的枚举类型经常成为实现Singleton的最佳方法。

第4条.通过私有构造器强化不可实例化的能力

当类不包含显示的构造器时,编译器才会生成缺省的构造器,因此只要让这个类包含一个私有的构造器,他就不能被实例化:

public class UtilityClass{
    private UtilityClass(){
        throw new AssertionError();
    }
}

第5条.优先考虑依赖注入来引用资源

满足该需求的最简单的模式是,当创建一个新的实例时,就将该资源传到构造器中。这是依赖注入的一种形式。

public class SpellChecker{
    private final Lexicon dictionary;
    public SpellChecker(Lexicon dictionary){
        this.dictionary = Objects.requireNonNull(dictionary);
    }
}

第6条.避免创建不必要的对象

一般来说,最好能重用单个对象,而不是在每次需要的时候就创建一个相同功能的新对象。

对于同时提供了静态工厂方法和构造器的不可变类,通常优先使用静态工厂方法而不是构造器,以免创建不必要的对象。

比如我们想要编写一个方法,用它确定一个字符串是否为一个有效的罗马数字。

static boolean isRomanNumeral(String s){
    return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
    +"(x[CL]|L?X{0,3})(I[XV]|V?I{0,3})$)");
}

但是它依赖String.matches方法。该方法内部会创建一个Pattern实例,却只用了一次,之后就会进行垃圾回收。创建Pattern实例的成本很高。为了提升性能,应该显示将正则表达式编译成一个Pattern实例,让它成为类初始化的一部分,并将它缓存起来。

 

    public class RomanNumerals{
        private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
                +"(x[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

        static boolean isRomanNumeral(String s){
            return ROMAN.matcher(s).matches();
        }
    }

另一种创建多余对象的方法,称作自动装箱。

要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。

通常通过维护自己的对象池来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的,比如数据库连接池。

注意:在提倡使用保护性拷贝的时候,因重用对象而付出的代价要远远大于因重复对象而付出的代价。必要时如果没能实施保护性拷贝,将会导致潜在的Bug和安全漏洞;而不必要地创建对象只会影响程序的风格和性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值