文章摘要
Effective Java第8章 通用程序设计 学习笔记
通用程序设计
45 将局部变量作用域最小化
目的
增强代码的可读性和维护性,并降低 出错的可能性。
方法
- 在局部变量第一次使用的地方声明。
- 几乎每个局部变量的声明都应该包含一个初始化表达式。
- 使方法小而集中
举例
- try-catch
- for循环与while循环对比
46 for-each循环优先于传统的for循环
for循环缺点
迭代器和索引变量在每个循环中出现三次,其中有两次很容易出错,且编译器不能发现错误。
for-each循环的优点
通过完全隐藏迭代器或者索引变量,避免了混乱或者出错的很可能。
- 利用
for-each
循环不会有性能损失,在某些情况下,还稍有性能优势。因其对数组索引的边界值只计算一次。 在对多个集合进行嵌套式迭代时,
for-each
循环能避免for
循环可能出现的问题,同时兼顾代码简洁。传统for循环嵌套迭代
enum Face {ONE, TWO, THREE, FOUR, FIVE, SIX} ... Collection<Face> faces = Array.asList(Face.values()); for (Iterator<Face> i = faces.iterator(); i.hasNext; ) for (Iterator<Face> j = faces.iterator(); j.hasNext; ) Sysytem.out.println(i.next() + " " + j.next());
for-each嵌套迭代
for (Suit suit : suits) for (Rank rank : ranks) deck.add (new Card (suit, rank));
for-each
循环不仅能遍历集合和数组,还能遍历任何实现Iterable
接口的对象。有三种常见的情况无法使用for-each循环
- 过滤——需要遍历集合,并删除选定的元素。
- 转换——需要遍历列表或数组,并取代它部分或全部的元素值。
- 平行迭代——需要并行的遍历多个集合。
47 了解和使用类库
类库中值得注意的两个工具
- java.util包中Collections Framework(集合框架)
- java.util.concurrent包
48 如果需要精确的答案,请避免使用float和double
- float 和 double 执行二进制浮点运算,没有提供完全精确的结果,尤其不适合用于货币计算。
使用BigDecimal、int 或者 long进行货币计算。
- 使用BigDecimal缺点:与基本运算类型相比,不方便,而且很慢;优点: 系统记录十进制小数点,允许完全控制舍入,数值超过18位数字。
- 选用int或者 long取决于所涉及数值的大小,同时自己处理十进制小数点。数值没有超过9位数字用 int,数值没有超过18位数字用float。
49 基本类型优于装箱基本类型
基本类型
举例:int,double,boolean
装箱基本类型
举例:Integer,Double,Boolean
区别
- 基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性(对象引用);
- 基本类型只有功能完备的值,而每个装箱基本类型还有个非功能值:null;
- 基本类型通常比装箱基本类型更节省时间和空间。
注意
对装箱基本类型使用 == 操作符几乎总是错误的;
比较器错误地返回1;
public int compare(Integer first, Interger second) { return first < second ? -1 : (first == sencond ? 0 : 1); }
当一项操作中混合使用基本类型与装箱基本类型时,装箱基本类型会自动拆箱。如果null对象引用被拆箱,则抛出NullPointerException异常。
抛出NullPointerException异常
static Integer i; public static void main(String[] args) { if (i == 42) System.out.println("123"); }
循环中装箱基本类型的变量被反复装箱和拆箱,产生严重性能问题。
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
50 如果其他类型更适合,则尽量避免使用字符串
不应该使用字符串的情形
- 字符串不适合代替其他的值类型
- 字符串不适合代替枚举类型
- 字符串不适合代替聚集类型
- 字符串不适合代替能力表(capabilities)
51 当心字符串连接的性能
由于字符串不可变,在连接字符串时,它们的内容都要被拷贝。如果数量巨大,应使用StringBuilder的 append方法。
52 通过接口引用对象
优先使用接口而不是类来引用对象。如果有合适的接口类型存在,参数、返回值、变量和域都应该用接口类型进声明。
里氏替换原则
//通过接口引用对象 List<String> lists = new ArrayList<String>(); //通过类引用对象 × ArrayList<String> lists = new ArrayList<String>();
- 如果没有合适的接口存在,用类来引用对象。
- 情形一 值类,例如
String
和BigInteger
。值类通常是final的,很少会用多个实现,并且很少有对应的接口。 - 情形二 对象属于框架,框架的基本类型为类不是接口,例如
java.util.TimerTask
。使用相关的基类引用对象,不用实现类。 - 情形三 类实现了接口,但提供了接口中不存在的额外方法,例如
LinkedHashMap
。
- 情形一 值类,例如
53 接口优于反射机制
反射机制的弊端
- 丧失了编译时类型检查的好处。
- 执行反射访问的代码冗长。
- 性能损失。