关于《effectivity Java》阅读笔记 02

一 、 消除对象的引用

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

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解释器,来启用它们。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值