第45条:将局部变量的作用域最小化
- 要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明。
- 如果在循环终止之后不再需要循环变量的内容,for循环就优先于while循环。
- 使方法小而集中。如果两个操作合并到一个方法中,与其中一个操作相关的局部变量就有可能会出现在执行另一个操作的代码范围之内。解决:把这个方法分成2个,每个方法各执行一个操作。
第46条:for-each循环优先于传统的for循环
先看一个错误的例子:
1
2
3
4
5
| for(Iterator<Suit> i=suits.iterator(); i.hasNext(); ){
for(Iterator<Rank> j=ranks.iterator(); j.hasNext(); ){
deck.add(new Card(i.next(), j.next()));
}
}
|
很容易发现在deck.add()时候,期望的结果不符合我们需求。
此时通过for-each解决:
1
2
3
| for(Suit suit : suits){
for(Rank rank : ranks){
deck.add(suit, rank);
|
注意for-each不能使用的情况:
- 过滤:如果要遍历集合,并删除选定元素,就需要使用迭代器的remove方法
- 转换:如果要遍历集合,并取代它部分元素值,就需要迭代器或者数组索引
- 平行迭代:如果需要并行地遍历多个集合,就需要显示地控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以同步前移。
第47条:了解和使用类库
第48条:如果需要精确的答案,请避免使用float和double
float和double类型尤其不适用于货币计算,想让一个double和float精确的表示一个0.1是不可能的。
如:
1
| System.out.println(1.00 - 0.90);//print 0.09999999999999998
|
所以解决方法:
使用BigDecimal/int/long进行货币计算
其中BigDecimal对舍入操作支持很好,但是它操作不方便。能支持超过18位的数字。
这里一些BigDecimal方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
| public class BigDecimals {
/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精
* 确的浮点数运算,包括加减乘除和四舍五入。
*/
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//这个类不能实例化
private BigDecimals(){
}
/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1,double v2){
return div(v1,v2,DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1,double v2,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的类型转换(Float)
* @param v 需要被转换的数字
* @return 返回转换结果
*/
public static float convertsToFloat(double v){
BigDecimal b = new BigDecimal(v);
return b.floatValue();
}
/**
* 提供精确的类型转换(Int)不进行四舍五入
* @param v 需要被转换的数字
* @return 返回转换结果
*/
public static int convertsToInt(double v){
BigDecimal b = new BigDecimal(v);
return b.intValue();
}
/**
* 提供精确的类型转换(Long)
* @param v 需要被转换的数字
* @return 返回转换结果
*/
public static long convertsToLong(double v){
BigDecimal b = new BigDecimal(v);
return b.longValue();
}
/**
* 返回两个数中大的一个值
* @param v1 需要被对比的第一个数
* @param v2 需要被对比的第二个数
* @return 返回两个数中大的一个值
*/
public static double returnMax(double v1,double v2){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.max(b2).doubleValue();
}
/**
* 返回两个数中小的一个值
* @param v1 需要被对比的第一个数
* @param v2 需要被对比的第二个数
* @return 返回两个数中小的一个值
*/
public static double returnMin(double v1,double v2){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.min(b2).doubleValue();
}
/**
* 精确对比两个数字
* @param v1 需要被对比的第一个数
* @param v2 需要被对比的第二个数
* @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1
*/
public static int compareTo(double v1,double v2){
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.compareTo(b2);
}
}
|
第49条:基本类型优先于装箱基本类型
基本类型:int double boolean等
装箱基本类型:Integer Double Boolean
它们之间区别:
- 第一:基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。(即2个装箱基本类型可以具有相同的值和不同的同一性)
- 第二:基本类型只有功能完备的值,而每个装箱基本类型除了它对应基本类型的所有功能值之外,还有个null功能值。
- 第三:基本类型通常比装箱基本类型更节省时间和空间。
注意:
- 对2个Integer变量执行比较操作(<、>)会导致实例自动拆箱提取出它们的基本类型值在进行比较。但是"=="可以重写equals解决
- 当在一项操作中混合使用基本类型和装箱基本类型是,装箱基本类型就会自动拆箱。如果null对象引用被自动拆箱,就会得到一个NullPointerException异常
- 频繁的装箱/拆箱会造成性能问题
当然装箱基本类型合理用处:
作为集合中的元素、键和值。这是一种更通用的特例,在参数化类型中,必须使用装箱基本类型作为类型参数。
第50条:如果其他类型更合适,则尽量避免使用字符串
如果可以使用更加合理的数据类型,或者可以变现更加适当的数据类型,就应该避免用字符串来表示对象。
第51条:当心字符串连接的性能
字符串连接操作符"+”,它不适合运用在大规模的场景中。
为连接n个字符串而重复地使用'+’,需要n的平方级的时间,这是由于字符串是不可变的,当连接2个字符串的时候,它们的内容都要被拷贝。
解决方法:
使用StringBuilder代替String
注意StringBuffer是线程安全的,而StringBuilder不是。详见
第52条:通过接口引用对象
如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就应当使用接口类型进行声明,这样做可以使程序更加灵活
因为当你决定更换实现时,只需要改变构造器类名。周围的代码都可以继续工作。
如下
1
2
| List<String> list = new ArrayList<String>();
list = new LinkedList<String>();
|
如果没有合适的接口,完全可以用类而不是接口来引用对象。比如:String和BigInteger
总之,为了程序更加灵活,使用接口,或者类层次中最基础的类来引用对象。
第53条:接口优先于反射机制
Java的核心反射机制,提供了通过程序来访问关于已装载的类的信息的能力。即给定一个Class实例,就可以获得它的Constructor、Method、Field。
反射机制允许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在。这种能力带来的损失:
- 丧失了编译时类型检查的好处
- 执行反射访问所需要的代码非常笨拙冗长
- 性能损失
总之,如果在编写的程序要与编译时未知的类一起工作,就应该仅仅使用反射机制来实例化对象(该类必须要有无参数的构造方法),而访问对象时则使用编译时已知的某个接口或者超类(使用接口/超类来引用对象)。
第54条:谨慎地使用本地方法
JNI(Java Native Interface)允许Java程序调用本地方法(Native method)(比如C/C++编写的特殊方法)。
Native Method用途:
- 提供了访问特定于平台的机制
- 提供了访问遗留代码库的能力
- 编写注重性能的部分,提高性能
但是,本地语言是不安全的,与平台相关,难以调试。所以极少数条件下使用本地方法来提高性能,访问底层资源。
第55条:谨慎地进行优化
不要因为性能而牺牲合理的结构。
要努力编写好的程序而不是快的程序
努力避免那些限制性能的设计决策
简而言之,编写好的设计结构,速度会随之而来。
第56条:遵守普遍接受的命名惯例
包的名称:
包的名称应该是层次状的。每个部分包括小写字母。
- 任何将在你的组织之外使用的包:名称要以域名反写开头,如com.sun、edu.cmu
- 标准类库和一些可选的包:以java和javax开头
- 包的其余名称应该描述该包的组成部分
类和接口的名称:
应该包括一个或多个单词,每个单词首字母大写。如Timer、TimerTask。应该避免使用缩写,除非一些通用的缩写(max、min)。如HttpUrl
方法和域的名称:
- 和类与接口名称一样,都遵循相同的字面惯例。但是方法和域的名称的第一个字母应该小写。如remove、ensureCapacity。
- 对于常量域:应该使用全大写,多单词用’_‘隔开。如INSTANCE、NEGATIVE_INFINITY
- 局部变量名称:与上名称类似。只不过它允许缩写。如i、xref、hourseNumber
- 类型参数名称:通常是单个大写字母。如T表示任意的类型;E表示集合元素类型;K和V表示映射的键和值类型,X表示异常;任何类型的序列化可以是T、U、V或者T1、T2、T3
执行动作方法
- 执行某个动作的方法,通常用动词或者短语来命名。如append、drawImage。
- 对于返回boolean值的方法,其名称往往以’is’开头。如isDigit、isEmpty
- get和set作为开头的方法,如含义所示设置和得到。如getNum、setNum
- 转换对象的方法:通常toType。如toString、toArray
- 返回视图的方法:通常asType。如asList。
- 返回一个与调用对象同值的方法:通常typeValue。如intValue
- 静态工厂的名称:通常valueOf、of、getInstance、newInstance、getType。