关于《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解释器,来启用它们。
- 关于《effectivity Java》阅读笔记 02
- 关于《effectivity Java》阅读笔记 01
- 学习笔记之:Effectivity Type
- 关于《Java并发编程实战》 -- 第一部分的阅读笔记
- 关于《Java并发编程实战》 -- 第二部分的阅读笔记
- Java多线程阅读笔记
- Effective Java 阅读笔记
- Effective Java阅读笔记
- 阅读笔记 > 关于代码注释
- java编程思想阅读笔记
- Java编程思想阅读笔记
- <Java 提高篇>阅读笔记
- 《Java编程优化》阅读笔记
- yarn-resource.java阅读笔记
- java.lang包阅读笔记
- Java 并发编程阅读笔记
- Java集合框架阅读笔记
- 《Java编程思想》阅读笔记
- 深入HBase架构解析(一)
- logback springmvc 把日志输出到指定文件中所遇到的小坑
- [4]78. Subsets/90. Subsets II(Java)
- mysql七表查询实例(一)
- javaScript怎么实现双向数据绑定
- 关于《effectivity Java》阅读笔记 02
- MPSOC系列基于ZCU102的linux的kernel的编译
- USB包格式解析
- 51nod 1378 夹克老爷的愤怒(树DP+贪心)
- hotspot vm调优 资料
- 手动添加jar包到本地仓库
- Java 枚举(enum) 详解7种常见的用法
- 数据结构之数组、单链表和双链表的介绍
- 超简单的js 5星评论代码