本章主要讨论语言的具体内容。它讨论了局部变量的处理、控制结构、库的使用、各种数据类型的使用,以及使用反射和本地方法。最后,讨论了优化和命名约定
Item 45:最小化局部变量作用域
作用域:一个花括号{}包裹起来的区域
此条例同Item13相似:最小化类和成员变量的访问权限
Java允许你在任何地方声明变量,但是最重要的是在首次使用的地方声明变量,并初始化
循环提供了一种实现此种方式的机制,而且for循环比while循环好,如
for (Element e : c) {
doSomething(e);
}
//before JDK1.5
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething((Element) i.next());
}
为什么for比while好呢?
//bad
Iterator i = c.iterator();
while (i.hasNext()) {
doSomething(i.next());
}
...
Iterator i2 = c2.iterator();
while (i.hasNext()) { // BUG! 应该是i2
doSomethingElse(i2.next());
}
当我们写一个差不多的代码,从一个地方copy过来的时候,很有可能忘记修改某个变量值(如i2),它不会在编译期报错,我们很可能长时间遗留这个bug
使用for循环可以避免这个bug
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething(i.next());
}
...
// Compile-time error - cannot find symbol i
for (Iterator i2 = c2.iterator(); i.hasNext(); ) {
doSomething(i2.next());
}
这就是最小化作用域的好处
Item 46: Prefer for-each loops to traditional for loops
对于不需要下标来做特殊操作的遍历,推荐使用增强for循环
你能发现下面的bug吗?
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
NINE, TEN, JACK, QUEEN, KING }
...
Collection suits = Arrays.asList(Suit.values());
Collection ranks = Arrays.asList(Rank.values());
List deck = new ArrayList();
for (Iterator i = suits.iterator(); i.hasNext(); )
for (Iterator j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(i.next(), j.next()));
...
...
...
...
...
...
发现不了也不要难过,很多有经验的程序员也会犯这个错误
原因在于i.next()会被重复调用,导致结果异常
可以修复如下
for (Iterator i = suits.iterator(); i.hasNext(); ){
Suit suit = i.next();
for (Iterator j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(suit, j.next()));
}
虽然解决了问题,但是很丑,更简洁的写法如下
for (Suit suit : suits)
for (Rank rank : ranks)
deck.add(new Card(suit, rank));
尽可能的使用增强for循环
Item 47: Know and use the libraries
不要重复造轮子
如果需要一个常用的功能,去发现一个库并使用它,往往比自己写的要好
因为库会随着时间推移更新迭代越来越好,自己也节约了时间
这并不会评论一个人的能力
Item 48: Avoid float and double if exact answers are required
当需要一个精确的答案时,避免使用float或double类型的变量
float或double是为了科学和工程计算而设计的,特别不适用于货币计算
推荐使用int或long代替
Item 49: Prefer primitive types to boxed primitives
使用原始类型替代装箱类型
byte short char int long float double boolbean等是Java中的基本类型,也有对应的装箱类型,如 Integer, Double, and Boolean.应当谨慎对待这两者之间的差别
首先第一个差别,原始类型仅仅包含对应的值,装箱类型既包含对应的值也有对应的引用,第二个差别是装箱类型有可能为null,第三个差别是原始类型在时间和空间消耗中更高效
Item 50: Avoid strings where other types are more appropriate
如果有更合适的类型,避免使用String
string被设计成描述文本类型的数据,而且干得很好,本章主要讨论将string用于其它情况的错误用法
string不能替代值类型 如果我们正在等待键盘的输入,或者从网络获取某个值,我们很方便的使用string作为接收类型,但是如果我们输入的是数字或者真假值的话,对应的int或boolean能更好的标识输入 虽然这条规则很明显,但是经常被违反
string不能替代枚举 如Item30:枚举讨论的那样,对于静态常量使用枚举
string不能替代聚合字符
String compoundKey = className + "#" + i.next();//bad
我们经常写上述代码,这样的写法有很多缺点.如果我们想使用某一部分字段需要解析字符串,很耗时而且容易出错.String提供的equals,compareTo等方法也不能使用
比较好的方式是写一个静态内部类来表示聚合字符
static class CompoundKey{
private String className;
private String next;
...
}
Item 51: Beware the performance of string concatenation
考虑字符连接(+)的性能
使用(+)能很方便的拼接若干个字符串,但是我们也要考虑到开销
如下两个代码都是拼接字符
//方式一
public String statement() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // String concatenation
return result;
}
//方式二
public String statement() {
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}
当numItems()==100;lineForItem(i)返回80个长度的字符时,在作者的机器上方式二比方式一快85倍
如果我们需要拼接大量字符时,使用StringBuilder代替
Item 52: Refer to objects by their interfaces
使用接口代替对象引用
如果有一个合适的接口来描述当前类的时候使用这个接口来引用,如
// Good - uses interface as type
List subscribers = new Vector();
// Bad - uses class as type!
Vector subscribers = new Vector();
如果你养成了这个习惯,那么你的代码将会更灵活
有几种情况不能使用接口
1.没有合适的接口声明
2.接口中没有想要的某个方法
3.使用的类集成自framework,并且是一个抽象类
Item 53: Prefer interfaces to reflection
使用接口代替反射
反射核心类java.lang.reflect提供了对加载类信息的访问
例如,Method.Invoke允许在任何类的任何对象上调用任何方法(受通常的安全约束)。 即使编译后的类不存在,也允许一个类使用另一个类。然而,这种力量是有代价的。
你不能在编译时检查出异常 如果你使用了对应的反射操作,在发生异常时,只有在运行时才能发现
阅读性极差
性能有影响 使用反射比普通调用要慢些,因为受很多因素的影响,慢多少很难说,在作者的机器上,速度差在两倍到五十倍不止
反射核心用在基于组件设计的应用,为了按需加载类,使用反射找到对应的类构造与否;普通应用尽量不要使用反射,找到代替的接口或者父类对象
Item 54: Use native methods judiciously
明智地使用本地方法
JNI允许Java调用C或C++写的本地方法
从历史上看,本地方法有三种主要用途。
1.它们提供了对特定于平台的设施的访问,例如注册表和文件锁。
2.他们提供了对旧代码库(Java想使用历史上C或C++写的库)
的访问,可以反过来提供对旧数据的访问。
3.使用本地方法用本地语言编写应用程序关键部分,以提高性能。
Java平台在不断发展,访问特定平台设施,使用Java提供的工具类就可做到,而且也不建议使用本地方法来提升程序性能
使用本地方法有严重的缺点
1.本地语言是不安全的,会受机器内存错误的影响
2.依赖于平台,不便于移植
3.本地代码很难调式
4.访问本地代码开销很大
5.本地代码不宜阅读
总之,要再三思考是否使用本地代码,如果需要使用以前的代码库,请尽可能减少本地代码片段并加强测试,很小很小的本地代码错误将破坏你的整个程序
Item 55: Optimize judiciously
明智的优化
有三个人人都应该知道的优化格言
More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason—including blind stupidity.
—William A. Wulf [Wulf72]
更多的计算罪恶是以效率的名义犯下的(不一定要达到这一目的),而不是因为任何其他单一的原因-包括盲目的愚蠢
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
—Donald E. Knuth [Knuth74]
我们应该忽略小效率,大约百分之97的情况下,邪恶之源就是过早优化
We follow two rules in the matter of optimization:
Rule 1. Don’t do it.
Rule 2 (for experts only). Don’t do it yet—that is, not until you have a
perfectly clear and unoptimized solution.
—M. A. Jackson [Jackson75]
关于优化遵循如下两个原则:
原则1.别这样做
原则2.(仅对专家而言).还不要做,直到你找到一个清晰的解决方案或者一个未优化的解决办法
不要为了性能而损坏架构,致力于写出好的程序而不是快的程序,如果一个好的程序还不够快,它的架构会允许优化.
这并不意味着当你的程序完后不需要优化,你应该在设计阶段就考虑到性能
尽量避免影响性能的设计,已经实现好的组件很难在改变,尤其是API,数据结构,多方约定好的协议
考虑好API使用效果,设计一个可变的类可能会导致后续使用中出现过多的深拷贝,造成对象分配的额外开销
API的设计对性能有很真实的影响,如java.awt.Component中的getSize()方法,每调用一次就会返回一个新的 Dimension 实例(JDK1.2版本已经修复),虽然分配一个实例的开销很小,但是成百上千次调用也会对程序有严重影响
幸运的是,好的API设计自然带来了好的性能
总之,致力于写出好的程序,快随之而来 当做出一部分改变后就要测量代码的性能,对于Android来说内存,卡顿,anr等方面
参考DDMS性能调优
Item 56: Adhere to generally accepted naming conventions
遵循公共的命名规范,参考阿里巴巴发布的Android手册
上一章:方法
下一章:异常