effective-java-3rd学习笔记2

第三条:使用私有构造方法或枚类实现 Singleton 属性

讲到这里,单例模式分别有几种,本文不再阐述,本文解决映射,反序列化导致不能单个实例问题。
简单的单例模式

// Singleton with public final field  (Page 17)
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

静态工厂

// Singleton with static factory (Page 17)
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    public static Elvis getInstance() { return INSTANCE; }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.getInstance();
        elvis.leaveTheBuilding();
    }
}

如果单例需要存储,实现Serializable,声明所有实例属性为transient,并提供readResolve 方法。

// readResolve method to preserve singleton property
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}

第三种,枚举的方式

// Enum singleton - the preferred approach (Page 18)
public enum Elvis {
    INSTANCE;
    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

这种方式类似于公共属性方法,但更简洁,提供了免费的序列化机制,并提供了针对多个实例化的坚固保证,即使是在复杂的序列化或反射攻击的情况下。这种方法可能感觉有点不自然,但是单一元素枚举类通常是实现单例的最佳方式。注意,如果单例必须继承 Enum 以外的父类 (尽管可以声明一个 Enum 来实现接口),那么就不能使用这种方法。


第四条:使用私有构造方法执行非实例化

应用场景:工具类

实例代码

// Noninstantiable utility class (Page 19)
public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
    // Remainder omitted
}

因为显式构造方法是私有的,所以在类之外是不可访问的。 AssertionError 异常不是严格要求的,但是它提供了一种保证,以防在类中意外地调用构造方法。它保证类在任何情况下都不会被实例化。这个习惯用法有点违反直觉,好像构造方法就是设计成不能调用的一样。因此,如前面所示,添加注释是种明智的做法。这种习惯有一个副作用,阻止了类的子类化。所有的构造方法都必须显式或隐式地调用父类构造方法,而子类则没有可访问的父类构造方法来调用。

第五条:依赖注入优于硬连接资源

许多类依赖于一个或多个底层资源。例如,拼写检查器依赖于字典。将此类类实现为静态实用工具类并不少见。

// Inappropriate use of static utility - inflexible & untestable!
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // Noninstantiable
public static boolean isValid(String word) { ... }
public static List suggestions(String typo) { ... }
}

依赖注入的方式

// Dependency injection provides flexibility and testability
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
} p
ublic boolean isValid(String word) { ... }
public List suggestions(String typo) { ... }
}

总之,不要使用单例或静态的实用类来实现一个类,该类依赖于一个或多个底层资源,这些资源的行为会影响类的行为,并且不让类直接创建这些资源。相反,将资源或工厂传递给构造方法(或静态工厂或 builder 模式)。这种称为依赖注入的实践将极大地增强类的灵活性、可重用性和可测试性。

第六条:避免创建不必要的对象

  1. 实例一
String s = new String("bikini"); // DON'T DO THIS!
String s = "bikini";// do this
  1. 实例二
// Performance can be greatly improved!
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})$");
}

应该这样,当多次使用ROMAN时,应该缓存起来

// Reusing expensive object for improved performance
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();
}
}

实例三

// Hideously slow! Can you spot the object creation?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}

这种写法,导致不必要的装箱,拆箱,应该这样写

// Hideously slow! Can you spot the object creation?
private static long sum() {
long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}

第七条:消除过期的对象引用

堆栈的实现,如下代码:

// Can you spot the "memory leak"?  (Pages 26-27)
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
    public static void main(String[] args) {
        Stack stack = new Stack();
        for (String arg : args)
            stack.push(arg);

        while (true)
            System.err.println(stack.pop());
    }
}

这个程序没有什么明显的错误。 你可以对它进行详尽的测试,它都会成功地通过每一项测试,但有一个潜在的问题。 笼统地说,程序有一个“内存泄漏”,由于垃圾回收器的活动的增加,或内存占用的增加,静默地表现为性能下降。 在极端的情况下,这样的内存泄漏可能会导致磁盘分页(disk paging),甚至导致内存(OutOfMemoryError)的失败,但是这样的故障相对较少。那么哪里发生了内存泄漏? 如果一个栈增长后收缩,那么从栈弹出的对象不会被垃圾收集,即使使用栈的程序不再引用这些对象。 这是因为栈维护对这些对象的过期引用(obsolete references)。 过期引用简单来说就是永远不会解除的引用。 在这种情况下,元素数组“活动部分(active portion)”之外的任何引用都是过期的。 活动部分是由索引下标小于 size 的元素组成。
改进代码如下:

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

清空对象引用应该是例外而不是规范。
当一个类自己管理内存时,程序员应该警惕内存泄漏问题。
另一个常见的内存泄漏来源是缓存。

你可能感兴趣的:(effective-java-3rd学习笔记2)