本文讨论一些 Java 语言的具体细节,局部变量的处理、控制结构、类库的用法、各种数据类型的用法等。
1.将局部变量的作用域最小化
将局部变量的作用域最小化的优点:
- 增强代码可读性
- 可维护性
- 降低出错可能性
具体做法:
- 在第一次使用变量的地方声明
- 几乎每个局部变量的声音都应该包含一个初始化表达式
- for循环中,优先使用循环变量
- 优先使用for循环,而不是while循环,因为for循环可以定义循环变量,while循环只能在外部定义变量
- 使方法小而集中
2.for-each循环优先于传统for循环
for-each的优点:
- 可以隐藏迭代器或者索引变量,避免出现混乱和错误
- 不会有性能损失,甚至有性能优势,因为它对数组索引的边界值只计算一次(虽然可以手动将数组索引边界提取到循环外)。
- 对于嵌套多层循环,for-each更有优势,可以避免 Iterable 嵌套循环出现的bug
- for-each不仅可以遍历集合和数组,还可以遍历任何实现 Iterable 接口的对象
for (Iterator i = c.iterator(); i.hasNext();) {
}
for (int i = 0; i < a.length; i++) {
}
for (Element e : elements) {
}
无法使用 for-each 的情况:
- 过滤:如果需要遍历集合,并删除选定的元素,就只能使用显式的迭代器,以便可以调用 remove 方法
- 转换:如果需要遍历列表或者数组,并取代它部分或者全部的元素,就需要列表迭代器或数组索引,以便设定元素的值
- 平行迭代:如果需要并行地遍历多个集合,就需要显式地控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。
3.了解和使用类库
4.如果需要精确的答案,请避免使用 float 和 double
float和double执行的是二进制浮点运算,为了在广泛的数值范围上提供较为精确的快速近似计算而设计。缺点是,让一个float和double精确表示0.1(或者10的任何其他负数次方数值)是不可能的。
替换方案:BigDecimal、int、long
BigDecimal缺点,它不是基本数据类型,这样操作简单问题很不方便。优点可以支持舍入操作
int和long需要自己处理十进制小数点
5.基本类型优于装箱基本类型
基本类型和装箱基恩类型的区别:
- 基本类型只有值,装箱类型则具有与它们的值不同的同一性。两个装箱基本类型可以具有相同的值和不同的同一性
- 基本类型只有功能完备的值,而装箱类型还包括一个非功能值,null
- 基本类型比装箱类型更节省时间和空间
当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,这种情况无一例外,如果null对象引用被自动拆箱,就会得到一个 NulPointerException 异常。
另外,混合使用时,尤其在循环中,变量反复的装箱和拆箱,会导致明显的性能下降。
使用装箱基本类型的场景:
- 作为集合中的元素、key或者value,因为不能将基本类型放到集合中
- 必须使用装箱基本类型作为类型参数,Java 不允许使用基本类型,ThreadLocal<int> 错误,ThreadLocal<Integer> 正确
- 进行反射的方法里,必须使用装箱基本类型
自动装箱减少了使用装箱基本类型的繁琐,但并没有减少它的风险。尤其在使用
==
操作符时要注意
6.如果其他类型更合适,则尽量避免使用字符串
不应该使用字符串的情景:
- 字符串不适合代替其他的值类型,应该使用 int、float或者BitInteger 类型,如果是是或者否,应该转换为boolean
- 字符串不适合代替枚举类型,枚举类型比字符串更加适合用来表示枚举类型的常量
- 字符串不适合代替聚集类型,如果一个实体有多个组件,用一个字符串来表示这个实体通常是不恰当的,而且要想访问组件域,解析过程也很繁琐,可以用一个私有的静态成员类替代
- 字符串不适合代替能力表(capalities)
7.当心字符串连接的性能
字符串连接操作
+
是把多个字符串合并为一个字符串的遍历途径。缺点:不适合运用在大规模的场景中,为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。这是由于字符串不可变性导致的,当两个字符串被连接时,它们的内容都要被拷贝。
具体例子:
String result = "";
for (int i = 0; i < 100; i++) {
result += lineForItem(i);
}
return result;
解决方案:
- 使用StringBuilder替换String,append方法
- 使用字符数组,或者每次只处理一个字符串,而不是将它们组合起来
8.通过接口引用对象
应该使用接口而不会类作为类型的参数。更一般的讲,应该优先使用接口而不是类来引用对象。如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,都应该使用接口类型进行声明。
List<String> stringList = new ArrayList<String>() 正确
ArrayList<String> stringList = new ArrayList<String>() 错误
用接口作为类型的优点:更加灵活,方便更换实现,只需改变构造器中类的名称(或者使用一个不同的静态工厂)
9.接口优先于反射机制
核心反射机制,提供了“通过程序来访问关于已装载的类的信息”的能力。
反射的缺点:
- 丧失了编译时类型检查的好处,包括异常检查,如果程序企图调用不存在或者不可访问的方法,在运行时它将会失败。
- 执行反射访问所需要的代码非常笨拙和冗长
- 性能损失,反射方法调用比普通方法慢很多
使用反射的情况:
- 类浏览器
- 对象监视器
- 代码分析工具
- 解释型的内嵌式系统
- RPC(远程过程调用)
10.谨慎使用本地方法
JNI 允许 Java 应用程序调用本地方法(C或者C++)。本地方法在本地语言中可以执行任意的计算任务,并返回到Java中。
本地方法的用途:
- 访问特定于平台的机制,比如访问注册表和文件锁
- 访问遗留代码块的能力
- 编写应用程序注重性能的部分
本地语言是不安全的,不再能免受内存毁坏错误的影响,本地语言和平台相关,不再是可移植的。随着Java平台的成熟,已经提供了越来越多以前只有在宿主平台才拥有的特性,并且JVM的实现也越来越快。
11.谨慎地进行优化
有三条有关优化的格言。
很多计算机上的过失都归咎于效率(没有必要达到的效率),而不是任何其他的原因——甚至包括盲目的做傻事。
不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源。
在优化方面的两个规则:1.不要优化。2.还是不要优化。
要努力编写好的程序,而不是快的程序