关于《effectivity Java》阅读笔记 02

来源:互联网 发布:js获取html标签属性 编辑:程序博客网 时间:2024/06/04 19:36

一 、 消除对象的引用

下面是一个简单的栈实现的例子:

public class Stack {    private Object[] elements;    private int size = 0;    private static fianl 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];        }    }    public void ensureCapacity(){        if (elements.length == size)            elements = Arrays.copyOf(element, 2* size + 1);    }}

不严格的讲,上面这段程序有一个“内存泄漏”,如果一个栈先是增长,然后在收缩,那么,从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。因为,栈内部维护着对这些对象的过期引用。

解决办法:

public Object pop(){    if(size == 0){        throw new EmptyStackException();    }    Object result = element[--size];    elements[size] = null; // 清空过期引用}

清空过期引用的另一个好处: 如果它们以后又被错误的解除引用,程序就会立即抛出NullPointerException异常,而不是悄悄的错误运行下去。
注意: 只要是类自己管理内存,程序员就应该警惕内存泄露问题。

内存泄露的另一个常见的来源是缓存 : 有几种可能的解决方案。只要在缓存之外存在对某个项的键的引用,该项就有意义,那么可以用WeakHashMap代表缓存,当缓存中的缓存项过期之后,它们就会自动被删除。

内存泄露的第三个常见的来源是:监听器和其他回调。

二 、 复合优于继承

在包的内部使用继承是非常安全的,在那里,子类和超类的实现都处在同一个程序员的控制下。而对于专门为了继承而设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。对于普通的具体类进行跨越包边界的继承,则是非常危险的。

示例: 假设我们需要查询HushSet,看它被创建以来曾添加了多少个元素。

public class InstrumentedHashSet<E> extends HushSet<E> {    private int addCount = 0;    public InstrumentedHashSet() {}    public InstrumentedHashSet(int initCap, float loadFactor) {        super(initCap, loadFactor);    }    @Override public boolean add(E e) {        addCount++;        return super.add(e);    }    @Override public boolean addAll(Collection<? extends E> c) {        addCount += c.size();        return super.addAll(c);    }    public int getAddCount(){        return addCount;    }}

这个类看起来十分合理,但是它不能正常的工作:现在我们创建一个示例并,添加三个元素

InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();s.addAll(Arrays.asList("Snap", "Crack", "Tom"));

这时候我们期望getAddCount方法返回 3 ,可是实际上返回的是6。
这是因为InstrumentedHashSet中的addAll方法首先给addCount方法加三,然后利用super.addAll来调用InstrumentedHashSet覆盖的add方法,每个元素调用一次。总共增加了6。

三 、 列表优先于数组

下面这段代码片段是合法的: 会运行是出错

Object[] objectArray = new Long[1];objectArray[0] = "addString"; //在Long类型中 加入 String ,  运行是出错

下面这段代码则是不合法的: 会编译时出错

List<Object> ol = new ArrayList<Long>; // 编译报错ol.add("addString");

我们当然想在编译时发现错误。

四、 用enum枚举类型代替int常量

示例:int枚举模式

public static final int APPLE_FUJI  = 0;public static final int APPLE_PIPPIN = 1;public static final int APPLE_GRANNY_SMITH = 2;public static final int ORANGE_NAVEL = 0;public static final int ORANGE_TRMPLE = 1;public static final int ORANGE_BLOOD = 2;

这种被称为int枚举模式。它在类型安全性和使用方便性方面没有任何不足。例如:你将apple传入到想要orange的方法中编译器也不会出现警告
采用int枚举模式的程序是十分脆弱的。因为int枚举是编译时常量,被编译到使用它们的客户端中。如果与枚举常量关联的int发生变化,客户端就必须重新编译。

解决办法: Java 1.5 以后提出了一种解决方案:

public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}public enum Orange {NAVEL, TEMPLE, BLOOD}

Java枚举类型背后的想法非常简单: 它们就是通过公有的静态final域为每个枚举常量导出实例的类。因为没有可以访问的构造器,枚举类型是真正的final。因为客户端不能创建枚举类型实例,也不能对其进行扩展。
枚举提供了编译时的类型安全。如果一个参数声明为Apple,就可以保证传入的参数一定是有效的三个Apple值之一。

五、 用实例域代替序数

所有的枚举都有一个ordinal方法,它返回每个枚举常量在类型中的数字位置。

public enum Ensemble {    SOLO, DUET, TRIO, QUATET, QUINTET;    public int numberOfMusicians() {return ordinal() + 1;}}

这个枚举不错,但是维护起来就像一场噩梦。如果变量进行重新排序, numberOfMusicians方法就会遭到破坏。

解决: 永远不用根据枚举的序数导出与它关联的值,而是将它保存在一个实例域中:

public enum Ensemble {    SOLO(1), DUET(2), TRIO(3), QUATET(4), QUINTET(5);    public final int numberOfMusicians;    Ensemle(int size) { this.numberOfMusicians = size; }    public int numberOfMusicians() {return numberOfMusicians;}}

六、 检查参数的有效性

绝大多数的方法和构造器对于传入它们的参数值都会有某些限制。如: 传入对象不能为null,索引值必须为非负数。

01 . 对于公有的方法,要用 Javadoc的@throw标签在文档中说明违反参数值限制会抛出什么异常。
下面是一个典型的例子:

/** *  方法和方法解释 * *  @param m the mudulus,which must be positive *  @return this mod m *  @throw AritheticException if m is less than or equal to 0 */public BigInteger mod(BigInteger m) {    if(m.signum() <= 0){        throm new ArithmeticException("Modulus <= 0: " + m);    // doSomething()....    }}

02 . 非公有方法通常使用断言(assertion)来检查它们的参数,具体做法如下所示:

private static void sort(long a[], int offset, int length) {    assert a != null;    assert offset >=0 && offset <= a.length;    // doSomething().......}

断言不同于一般的有效性检查,断言如果失败,将抛出AssertionError。也不同于一般的有效性检查,如果它们没起作用,本质上也不会有成本开销,除非通过将-ea(或者 -enableassertions)标记(flag)传递给Java解释器,来启用它们。

原创粉丝点击