45.将局部变量的作用域最小化
简介
和
13
条,使类和成员的可访问性最小化,是一个道理,可以采取如下几种办法:
- 在第一次使用它的地方声明.
- 几乎每个局部变量的声明都应该包含一个初始化的表达式,否则(没有足够的信息来对一个变量进行有意义的初始化),就应该延迟这个声明.
- for循环优于while循环,而且防止了“剪切-粘贴”错误,且更加简短可读
- 最后一种方法是
将局部变量的作用域最小化的方法是使方法小而集中
46.for-each优于for循环
简介
利用
fro-each
循环,对数组的索引边界值只会计算一次.
并且 看起来很 简洁下面是一个示例:
for(Iterator<Suit> i = suits.iterator();i.hasNext()){
for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
deck.add(i.next(),j.next());
}
}
这段代码,看似没问题,其实在内层循环中,会多次调用
i.next()
,有可能抛出NoSuchElementException
,正确的用法应该是;
for(Iterator<Suit> i = suits.iterator();i.hasNext()){
Suit suit = i.next();
for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
deck.add(suit,j.next());
}
}
而用
for-each
,就会变成这样
for(Suit suit:suits){
for(Rank rank: ranks){
deck.add(suit,rank);
}
}
小结
建议如果 编写的类型表示的是一组元素, 应该让它实现 Iterable
不能用for-each 的场景
在需要获取 数组或者迭代器索引
的时候.
47.了解和使用类库
不正确实例
产生位于
0
和某个上界
之间的随机整数.常见代码,如下:
private static final Random rnd = new Random();
static int random(int n){
return Math.abs(rnd.nexInt())%n;
}
这个方法有三个缺点:
- 如果n 是一个比较小的 2 的盛放,经过一段周期,随机数序列会重复
- 如果n 不是2 的平方,平均起来,有些数比其他数出现的更为频繁
- 极少数情况下,会失败
幸运的是,在Java标注库
中已经有了相关实现,nextInt(int)
,因此可以充分利用这些标准类库.
小结
总而言之,不要重新发明轮子,如果要做的事情十分常见,有肯能标准类库中某个类已经完成了这样的工作.
48.如果需要精确的答案,避免使用float和double
简介
float 和 double 是为了提供较为精确的 快速近似计算而精心设计的.
不适合用于货币计算
解决办法:
- 使用
BigDecimal
,与使用基本运算类型相比,BigDecimal
不是很方便,而且很慢. - 除了使用
BigDecimal
,还用一种办法是使用int
或者long
,者取决于所涉及的数据的大小
小结
- 精确计算避免使用
float
和double
,要使用BigDecimal
,这样可以控制舍入, - 如果不介意自己记录十进制小数点,可以使用
int
或者long
- 如果数值超过 18位数字,则必须使用
BigDecimal
49.基本类型优先于装箱基本类型
简介
Java
类型系统包含 基本类型
和 引用类型
,每一种基本类型
都有一个对应的引用类型
,称为装箱基本类型
,Java1.5 版本提供了自动装箱
和自动拆箱
基本类型与装箱类型的区别
- 基本类型只有值,而装箱基本类型则具有
与他们的值不同的同一性
- 基本类型只有功能完备的值,而装箱类型除了功能值之外,还有非功能值
null
- 基本类型通常比装箱基本类型更
节省时间和空间
.
警醒
- 对
装箱基本类型
运用==
操作符 几乎总是错误的
.
Comparator<Integer> naturalOrder = new Comparator<Integer>() {
public int compare(Integer first, Integer second) {
return first < second ? -1 : (first == second ? 0 : 1);
}
};
naturalOrder.compare(new Integer(42),new Integer(42));//返回1
- 基本类型和装箱类型混合使用,会自动拆箱,而
null
的拆箱操作会抛出NullPointException
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42)//抛出异常
System.out.println("Unbelievable");
}
}
- 包装类型,会创建过多的对象,造成性能问题
//这是之前第5个item中的代码
public static void main(String[] args) {
Long sum = 0L;//这里每次都会构造一个实例,多创建了2^31个实例.
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum); }
小结
- 基本类型优先于 装箱类型,基本类型更加简单快速
- 使用装箱类型,要考虑其自动拆箱的问题
- 警惕
==
50.如果其他类型更适合,则尽量避免使用字符串
简介
字符串 常被用来表示文本 .因为其通用性,就出现了如下一些不适合的场景
字符串不适合代替其他的值类型.
- 当一段数据从网络,文件或者输入设备进入到程序之后,就应该转化为其本来的类型.而不是继续使用String
- 字符串不适合代替枚举类型(
枚举类型比字符串更加适合用来表示枚举常量
) - 字符串不适合代替聚集类型(
拼接字符串场景
) - 字符串不适合代替能力表(
ThreadLocal
的例子)
小结
- 如果可以使用更加合适
的数据类型,就 应该避免使用
字符串`来表示 - 字符串如果使用不当,比其他类型更加笨拙,速度慢.
51.当心字符串连接的性能
简介
字符串连接符 是将
多个字符串
合并为一个字符串
的遍历途径.
为连接 n 个字符串而重复地使用连接符,需要n 的平方级的时间
(这是由于字符串不可变导致的)
为了获得可以接受的性能,请使用StringBuilder
考虑如下两种情况:
public String statement() {
String result = "";
for (int i = 0; i < numItems(); ++i) {
result += lineForItem(i);
}
return result;
}
public String statement() {
StringBuilder stringBuilder = new StringBuilder(numItems * LINE_WIDTH);
for (int i = 0; i < numItems(); ++i) {
stringBuilder.append(lineForItem(i));
}
return stringBuilder.toString();
}
后者
比 前者
快 85
倍之多.
原因:
第一种随项目数量而呈平方增加,第二种则是线性增加
第二种,默认分配一个足够容纳结果的StringBuilder
,即使使用默认大小,也比第一种快50
倍之多.
52.通过接口引用对象
简介
第
40 条
中,建议使用接口
作为参数类型
其实,应该是 优先使用接口
,而不是类
来引用对象.
如果有合适的接口类型存在,对于参数
,返回值
,变量
,域
都应该使用接口类型
来声明.
示例
// 使用接口引用指向子类对象,good
List<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
// 子类引用指向子类对象,bad
Vector<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
//使用接口的好处之一,就是可以方便的更换实现类
List<SubscriptSpan> mSubscriptSpen = new ArrayList<SubscriptSpan>();
- 如果没有合适的接口存在,完全可以用类而不是接口来引用对象.
不存在相应接口的情况:
- 值类(很少会有多个实现),
final
类 - 对象属于一个框架,而框架的基本类型是类.
- 类实现了接口,但是提供了接口中不存在的额外方法.
53.接口优先于反射机制
简介
反射提供了一种能力: 通过程序来访问已装载的类的信息的能力,即使被编译的时候后者还不存在,然而,
反射的代价:
丧失了编译时类型检查
的好处(包括异常检查). 如果程序企图 用反射调用不存在或者不可访问的方法,将会运行失败.- 执行反射访问所需要的
代码
非常笨拙和冗长
: 编写乏味,阅读也困难 - 性能损失: 反射的 执行比普通方法调用要
慢一些.
反射的场景
- 反射通常只在
设计时被用到
,普通应用在运行时不应该以反射方式
访问对象. - 以反射的形式创建实例,然后通过它们的接口或者超类,已正常方式访问这些实例.
小结
反射机制是一种功能强大的机制,但也有一些缺点.
如果编写的程序必须要与编译时 未知的类 一起工作,如 有可能,应该仅仅使用反射机制实例化对象
,访问对象
时则使用 编译时 已知的接口或者超类
54.谨慎的使用本地方法
简介
Java Native Interface(JNI)
允许 java
程序调用 本地方法
.
本地方法主要有 三种用途:
- 提供了
访问特定于平台
的机制 - 访问老的
C/C++
版本的库,访问遗留代码的能力 - 通过本地方法,编写应用中注重性能的部分
JNI的劣势
- 不安全,内存管理不受
JVM
控制了 - 与平台相关,自由移植变得困难
- 难以调试
Java和native
层的交互是有开销的,如果本地代码只做少量工作,则会降低性能
小结
总而言之,使用本地代码要三思,
- 极少数情况下需要使用本地方法来提高性能.
- 如果必须要使用,也要尽可能全面的测试.
55.谨慎的进行优化
简介
很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他的原因-- 甚至包括盲目的做啥事
---
不要去计较效率上的一些小小得失,在 `97%`的情况下,不成熟的优化才是一切问题的根源
---
在优化方面,应该遵循两条规则
规则1 : 不要进行 优化.
规则2 (仅针对专家) : 还是不要进行优化 -- 也就是说,在你还没有绝对清晰的未优化方案之前,请不要进行优化.
这些格言,比Java 出现早了
20
年,他们讲述了优化的深刻真理:
1. 优化的弊大于利,特别是不成熟的优化
2. 要努力表写好的程序,而不是快的程序
3. 努力避免那些限制性能的设计决策
4. 要考虑`API`设计决策的性能后果
5. 为了获得好的性能对`API`进行包装是一个不好的想法
6. 在每次试图做优化之前和之后,豆芽对性能进行测量.
小结
总而言之.,不要费力去编写快速的程序 – 应该努力编写好的程序,速度自然会随之而来. 当 构建完系统
后,要测量
它的性能
.
56.遵守普遍的命名规则
简介
Java 平台建立了一 整套很好的命名惯例.分为两大类:
字面的
和语法的.
- 包名要体现出组件的层次结构,全小写
- 公布到外部的,包名以公司/组织的域名开头,如 com.xxx.xxx
- 执行某个动作的方法长用动词或者动词短语来命名,如 append
- 转换对象类型的方法,
小结
把命名规则当做一种内在的机制来看待,并且学者用他们作为第二特征.