Effective java 总结8 - 通用编程
第57条 将局部变量的作用域最小化
java 允许在任何可以出现表达式的地方声明变量
在第一次使用它的地方进行声明
每一个局部变量都应该包含一个初始化表达式
在循环种植后不再需要循环变量的内容,for优先于while, 在for循环中定义变量,作用域即被限定在循环中,避免“剪切-粘贴”错误
使方法小而集中
第58条 for-each循环优于传统的for循环
for-each: 增强的for语句,完全隐藏迭代器或者索引变量, “:”读作在…里面
与传统的for相比没有性能惩罚, 更加简洁,灵活且预防出错
enum Suit{A, B, C, D}
enum Rank{E, F, G, H, I, J, K}
static Collection<Suit> suits = Arrays.asList(Suit.values());
static Collection<Rank> ranks = Arrays.asList(Rank.values());
List<Card> lists = new ArrayList<>();
for(Iterator<Suit> i = suits.iterator(); i.hasNext();)
for(Iterator<Rank> j = ranks.iterator(), j.hasNext();)
lists.add(new Card(i.next(), j.next()));
// bug : i.next()执行了N^2次
// 修改一, 用局部变量 Suit i = i.next(),在for之后, 再for之前
// 修改二: for-each
for(Suit s: suits)
for(Rank r: ranks)
list.add(new Card(s, r));
三种情况无法使用for-each:
- 解析过滤 – 遍历集合,并删除选定元素
- 转换 – 遍历列表数组,替换部分或全部元素
- 平行迭代 – 平行地遍历多个集合
for-each可以遍历实现Iterator接口的任何对象
public interface Iterator<E> {
Iterator<E> iterator();
}
第59条 了解和使用类库
使用标准类库的优势
-
可以充分利用编写类库的专家知识和他人使用经验
-
时间专注在应用实现,而不是底层实现
-
性能会不断提高
-
功能不断丰富
-
可以使自己的代码融入主流
一些应该熟悉的类库
-
java.lang java.util java.io 及其子包中的内容
-
Collections Framework 集合框架 和 Stream 类库
-
Google优秀的开源 Guava 类库
第60条 如果需要精确的答案,请避免使用float和double
float和double类型主要是为了科学计算和工程计算而设计的,它们执行二进制浮点运算,为了在广泛的数值范围上提供较为精确的快速近似计算而设计,但是并没有提供完全精确的结果
float和double尤其不适合用于货币计算
double 表示1.1 实际为 1.100000000000000088817841970012523233890533447265625
BigDecimal
// String 构造方法
BigDecimal b1 = new BigDecimal("1.1");
// Double 构造方法
BigDecimal b2 = BigDecimal.valueOf(1.1);
= new BigDecimal(Double.toString(1.1));
// BigDecimal 与 BigInteger都是不可变得,因此每一步运算都会产生一个新的对象
BigDecimal a = new BigDecimal("1.1");
BigDecimal b = new BigDecimal("2.2");
a.add(b);
sout(a); // 结果 1.1
a = a.add(b);
sout(a); // 结果3.3
缺点:
- 速度慢,性能不高
- 与基本类型运算相比,复杂且不方便
int : 不超过9位10进制数字计算
long : 不超过18位10进制数字计算
BigDecimal :超过18位
第61条 基本类型优先于装箱类型
基本类型与装箱类型的区别
- 基本类型只有值,装箱类型则具有与值不同的同一性
- 基本类型只有函数值,装箱类型除了有函数值,还有null
- 基本类型比装箱类型更节省时间空间
同一性问题
// 一般情况下没有问题,自动进行装箱拆箱
Comparator<Integer> cp = (i, j) -> i < j ? -1 : (i == j ? 0 : 1);
// 特殊情况 结果期望为 0 但实际为 1
op.Compare(new Integer(42), new Integer(42));
// 对装箱基本类型运用 == 操作符几乎总是错误的,要避免装箱类型的同一性比较
//优化
Comparator<Integer> cp = (iBoxed, jBoxed) -> {
int i = iBoxed; // 自动拆箱
int j = jBoxed;
i < j ? -1 : (i == j ? 0 : 1)
};
NULL值问题
public class Unbelievable{
static Integer i;
public static void main(String args[]){
if (i == 42){...}
}
}
// 程序抛出 NPE
// 一项操作中混合使用基本类型和装箱基本类型时,装箱类型会自动拆箱
// 如果null对象引用被自动拆箱,会抛出NullPointerException异常
// 修改 static int i; 即可
装箱类型性能问题
public static void main(Stirng[] args){
Long sum = 0L;
for(long = 0; i < Integer.MAX_VALUE; i++){
sum += i; // 不断地自动拆箱装箱
}
}
// 应该使用基本类型,装箱类型会明显导致性能下降
装箱基本类型合理的用处
- 作为集合中的元素、键、值
- 参数化类型和方法中必须使用装箱类型,java不允许使用基本类型
- 反射方法调用是必须使用装箱基本类型
总结
自动装箱减少了使用装箱基本类型的繁琐,但是没有减少风险,可以选择的时候,基本类型要优先于装箱基本类型
第62条 如果其他类型更适合,则尽量避免使用字符串
字符串不适合代替其他的值类型,不适合代替枚举类型,不适合代替聚合类型 (class),不适合代替能力表
public final class ThreadLocal<T>{
public ThreadLocal(){}
public void set(T value){}
public T get(){}
}
如果可以使用更加合适的数据的类型,或者编写更加适当的数据类型,就应该避免使用字符串来表示对象
第63条 了解字符串连接的性能
为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级时间
public String statement(){
String result = "";
for (int i = 0; i < num; i++)
result += arr(i);
return result;
}
// 使用StringBuilder 代替 操作符 “+”
public String statement(){
StringBuilder result = new StringBuilder(num * line_width);
for (int i = 0; i < num; i++)
result.append(arr(i));
return result;
}
不要使用字符串连接符来合并多个字符串
第64条 通过接口引用对象
如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明,程序会更加灵活,只有当利用构造器创建的时候需要引用这个对象的类
Set<Person> sp = new HashSet<>();
如果没有合适的接口,完全可以用类而不是接口来引用对象
- 值类(String , BigInteger…)
- 基于类的框架(java.io.OutputStream)
- 类实现了接口,但也提供了接口中不存在的额外方法(类只应该用来引用实例,不能作为参数类型)
如果没有合适的接口,就用类层次结构中提供了必要功能的最小的具体类来引用对象
第65条 接口优先于反射机制
核心反射机制,java.lang.reflect包,提供了通过程序访问任意类的功能
Constructor, Method, Field实例可以通过反射访问底层类的构造方法, 方法和域
反射的代价
- 损失了编译时类型检查的优势
- 执行反射访问所需要的代码笨拙且冗长
- 性能损失
如果只是以非常有限的形式使用反射机制,用少许代替获得更多收益
public static void main(String[] args) throws ...{
Class<? extends Set<String>> c = null;
c = (Class <? extends Set<String>>)Class.forName(args[0]);
Constructor<? extends Set<String>> cons = null;
cons = c.getConstructor();
Set<String> s = cons.newInstance();
s.add("a");
s.add("b");
System.out.println(s);
}
// args[0] : java.util.TreeSet
第66条 谨慎地使用本地方法
本地方法:本地编程语言编写的方法(C、C++)
JNI(java native interface) 允许java应用程序调用本地方法,提供了访问特定于平台的机制的能力,访问注册表等,此外,编写应用程序中注重性能的部分,能提高性能
但不建议使用本地方法来提高性能:
- JVM越来越快
- 本地方法有严重的缺陷,不安全
- 程序不再能跨平台,不能轻松移植
- 可能降低性能,因为垃圾回收不是自动的
- 本地程序的一个Bug都可能破坏整个程序
第67条 谨慎地进行优化
不成熟的优化才是一切问题的根源
- 努力编写好的程序,而不是快的程序,好的程序,速度自然随之而来
- 努力避免限制性能的设计决策,设计API、交互层协议,永久数据结构要考虑性能因素
- 要考虑API设计决策的性能后果,选择好的算法更为重要
- 每次试图优化前后,要对性能进行测量,可以借助性能剖析器
第68条 遵守普遍接受的命名惯例
包和模块
名称应该是层次状的,用句号分割,一般都为小写字母, eg: cn.com.sge; com.google。 不能以java、javax开头
通常不超过8个字符,可以接受缩写,eg: awt, util
类和接口
包含一个或多个单词,每个单词首字母大写,尽量避免使用缩写, eg: List, FutureTask
方法和域
第一个字母应该小写, eg: remove, add。
常量域例外,应该是一个或多个全大写字母(<唯一推荐用下划线>下划线连接)组成,值是不可变的 eg: VALUES, A_B_C
局部变量名称允许缩写,需要联系上下文
类型参数
- T:任意类型
- K,V:映射的键值
- E: 集合的元素类型
- X: 异常
- R: 返回类型
语法命名惯例
可以被实例化的类用一个名词或名词短语:Thread, PriorityQueue
不可实例化的工具类用复数名词命名: Collectors, Collections
接口命名与类类似,或以able,ible结尾:Runnable, Iterable, Accessable
指定某个动作的方法用用词或动词短语:append, add
返回boolean值的方法:is开头,isDigit, isEmpty
转换对象类型的实例方法:toType : toString, toArray
返回视图: asType: asList
返回与调用对象同值的基本类型方法: typeValue: intValue
静态工厂的常用名称: from, of, valueoOf, instance, getInstance, newInstance, getType, newType
的工具类用复数名词命名: Collectors, Collections
接口命名与类类似,或以able,ible结尾:Runnable, Iterable, Accessable
指定某个动作的方法用用词或动词短语:append, add
返回boolean值的方法:is开头,isDigit, isEmpty
转换对象类型的实例方法:toType : toString, toArray
返回视图: asType: asList
返回与调用对象同值的基本类型方法: typeValue: intValue
静态工厂的常用名称: from, of, valueoOf, instance, getInstance, newInstance, getType, newType