将局部变量作用域最小化。要将局部变量最小化最有力的方法是在第一次使用它的地方声明;每个局部变量声明都应该包含一个初始化表达式。
对于集合的遍历,首选forEach的方式,其次当在循环终止之后不再需要循环变量的内容时,for循环要优于while循环,因为for循环可以将临时变量限制在最小的生存区中。
拓展:对于需要计算的值,如链表的长度,进行循环遍历时,最好使用一个变量来存储其大小,避免每次重复计算。如:
for(int i = 0,n = expensiveComputration();i<n;i++){
doSometing();
}
- for-each优先于传统的for循环。for-each方式产不会造成任何性能的损失,在某些情况下(程序员没有把边界填用变量保存起来,而导致重复计算),比起普通for循环,还有性能上的优势。
拓展:注意求一个枚举的两两组合方式,示例:投两次骰子的所有可能结果。以下代码并不能得出36条记录。正确的方式应该是先把外层循环的结果保存起来。
enum Face {
ONE, TWO, THREE, FOUR, FIVE, SIX
};
...
Collection<Face> faces = Arrays.asList(Face.values());
for (Iterator<Face> i = faces.iterator(); i.hasNext();) {
for (Iterator<Face> j = faces.iterator(); j.hasNext();) {
System.out.println(i.next() + " " + j.next()); // 这样写会有bug
}
}
- 使用标准类库的好处有:可以避免浪费时间在与业务不相关的问题上;可以使代码更易读,更易维护;使用标准类库,它们的性能往往会随时间推移而不断提高。
示例:产生随机数最优的算法是Random.nextInt(int)
- 如果需要精确答案,避免使用float和double。在货币计算上正确的办法是使用BigDecimal、int、long等进行运算。
拓展:使用BigDecimal有两个缺点,一是相较于基本运算,这样做很不方便。二是其运算速度很慢。
相较于使用int和long,BigDecimal有两个优点,一是可以完全控制舍入。二是如果数字超过了18位数字,就只能用BigDecimal
- 基本类型优于装箱基本类型。
java类型系统由基本类型和引用类型两部分组成。
基本类型的装箱类有三个特点:基本类型装箱类表示的值相同,但不一定为同一个对象(引用的地址可能不同);基本类型都是有意义的值,装箱类的值可以为null,并且默认为null; 装箱类通常比基本类更耗时间和空间。
对于基本类型装箱类运行==
操作符几乎总是错误的。
示例:
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42) {
System.out.println("unbelievable");
}
}
}
以上小程序,并不是打印出unbelievalbe,而是抛出NullPointerException异常。原因是i是Integer,默认值是null
自动装箱和拆箱是耗性能的操作,考虑以下程序:
public void testsum() {
Long sum = 0l;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
这个程序运行会非常慢。原因是不断在装箱和拆箱。
必须使用装箱类的场景:集合中的元素、键、值。参数的泛化。
总之,能用基本类型就不用基本类型的装箱类,使用了装箱类就不要使用==
进行比较运算。
- 如果有其他类型更适合,尽量避免使用字符串
数据本应该是什么类型就应该使用该类型表示,而不能一味的使用字符串表示,如在网络中获取的数据往往是文本类型的,此时应该将其中内容转化成对象的类型,比如int,boolean等。在java中对字符串的查找、匹配、连接性能不如基本类型高。
- 当心字符串连接的性能。
使加+
运算符连接字符串,在单独输出一行,字符串较小单次操作中比较合适。但不适合大规模的场景。如果为连接n个字符串而重复使用字符串连接操作符,需要n的平方时间。这是由于字符串不可变的结果。
使用StringBuilder代替String应用于大规模连接场景。同时,如果事先知道需要的最大长度,可以在new StringBuilder(int),传进初始值大小参数,减少扩容开销
- 通过接口引用对象。
如果有合适的接口类型,那么对于参数、返回值、变量和域来说,都应该使用接口类型进行声明。这样会使程序更加灵活。
值类,如String,Integer很少会用接口,通常都是final的
- 接口优于反射机制
核心反射机制,java.lang.reflect提供了“程序来访问关于已装载的类的信息”的能力。给定一个Class实例,你可以获得构造器,方法和属性。Method.invoke()允许你调用任何类的任何对象上的任何方法。反射机制允许一个类使用另一个类,即使当前者被编译时后者根本不存在。也就是动态创建类和类的实例。
反射机制的代价是:
1)丧失了编译时类型检查的好处。如果程序企图用反射调用不存在的或不可访问的方法,会导致运行失败。
2)执行反射访问所需要的代码笨拙和冗长。
3)性能损失。反射方法调用比普通方法调用慢得多。
核心反射机制最初是为了基于组件的应用创建具而设计的。
有些应用如类浏览器、对象监视器、代码分析工具都需要应用到反射机制。在RPC系统中,反射机制也非常合适。
如果有可能,就应该仅仅使用反射机制来实例化对象,而访问对象则使用编译时已知的某个接口或超类。
- 谨慎使用本地方法
Java Native Interface(JNI),本地方法指应用系统本地编译语言编写的方法,调用本地方法,可以将结果返回到Java程序中。本地方法主要有三种用途:
1)提供“访问特定平台的机制”的能力,如访问注册表
2)提供访问遗留代码库的能力
3)提高系统的性能
使用本地方法来提高性能的做法不值得提倡,因为现在JVM优化已经相当好。本地语言不是安全的,与平台相关的,使用本地方法的程序是不可移植的;使用本地方法的应用程序调试非常困难。
谨慎地进行优化。不要为了性能而牺牲合理的结构。
遵守普遍接受的命名惯例。尽量避免缩写。常量使用大写字母,使用下划线分隔单词。
泛化参数类型通常由单个字母表示。一般有五种类型:T表示任意类型,E表示集合的元素类型,K和V表示映射的键和值类型,X表示异常。任何类型可以是T、U、V、或T1、T2、T3
值得一提的是,转换对象类型的方法、返回不同类型的独立对象的方法通常被称为toType,如toString和toArray,返回视图(视图的类型不同于接收对象的类型)的方法通常被称为asType,如asList。返回一个与被调用对象同值的基本类型方法,通常被称为typeValue,如intValue.静态工厂的常用名称为valueOf、of、getInstance、newInstance、getType和newType.