Effective Java 学习笔记(三)

条款5:优先使用依赖注入而非硬连接资源

很多类需要依赖资源。比如一个SpellChecker类检查拼写依赖于字典:

public class SpellChecker {
    private final static Lexicon dictionary = ...;

    private SpellChecker() {} //Noninstantiable

    public static boolean isValid(String word) {
        // Do something
    }
    public static List<String> suggestions(String typo) {
        // Do something
    }
}

但是,每个语言都有自己的字典,而且特殊词汇需要特殊字典,只有这一个字典是不行的。当类的行为由硬连接资源参数决定时,静态工具类和单例类并不适用。一种可以满足需求的方法是依赖注入,即当创建新实例时将资源传入构造器内:

public class SpellChecker {
    private final Lexicon dictionary = ...;

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

    public static boolean isValid(String word) {
        // Do something
    }
    public static List<String> suggestions(String typo) {
        // Do something
    }
}

虽然依赖注入可以显著提高灵活性和可测试性,它也会让有几千个依赖的大型项目变得很杂乱。当然,使用依赖注入框架(Dagger, Guice, Spring等)可以轻松解决此问题。


条款6:避免创建不必要的对象

为了避免资源浪费,我们应尽量重复使用一个对象而非创建一个等价的对象。当对象是不可变对象时总能被重复使用。下面是一个错误的例子:

String s = new String("Hello world");

大家都知道这样是不对的,String不用new,也不应当new出来,不然每次调用都会多出一个多余的没有必要的String实例。正确的做法是:

String s = "Hello world";

这保证了当字面量相同时,String对象的重复使用。

我们可以通过使用静态工厂方法(条款1)来避免创建不必要的对象,比如用Boolean.valueof()替代Boolean()。

对于一些创建开销很大的对象来说,我们最好缓存它以达到复用并节约资源的目的。不幸的是,我们难以察觉到这些对象被创建了。例如:

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})$");
}

看上去没有问题?实际上每次调用该方法时,都会创建一个Pattern实例且用一次就销毁。每次创建Pattern实例都会有巨大的开销,因为需要把正则表达式编译成有穷自动机。所以,上面的方法应当优化为:

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

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

当对象是不可变对象时,显而易见能被安全复用,但还有其他一些情况也能复用,但不是这么的明显,甚至是违反直觉的。比如适配器模式(adapters)或叫视图(view),因为适配器的状态并不会超出原有对象的状态,同一个对象同一个适配器无需创建更多实例。例如,Map的keySet方法返回一个Map对象的Set视图,包含Map的所有key。对于给定的Map对象,每次调用keySet可能返回的是一样的Set实例。

还有一件事——自动装箱也会创建不必要对象。自动装箱模糊了基本类型和装箱类型的界限,但并不是消除了。下面的例子有极大的性能提升空间:

private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; ++i) {
            sum += 1;
    }
    return sum;
}

因为用的是Long而不是long,程序创建了2^31个不必要的Long实例!!所以,优先使用基本类型而非装箱类型,并且要注意意料之外的自动装箱。

本条款主要针对于创建开销很大的对象。开销小的对象创建就创建了,如果能提升程序清晰性、简洁性和功能性反而是很好的事。

除非对象真的是很重量级,开销巨大,否则不要维护自己的对象池!这样对象池只会起到画蛇添足的作用。一个恰当正确地使用对象池例子是数据库链接,这真的足够重量,所以需要提高对象复用。

本条款与条款50 需要时使用保护性拷贝表面上互相冲突,但实际上,一方面,程序中要避免创建冗余的对象,以减少资源占用;另一方面,在必须创建对象时,那就去创建,以保证程序的安全性和健壮性。程序要兼顾性能和安全

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值