第1条:
第29条:优先考虑类型安全的异构容器
当一个容器(如Map)实现多种类型的key时,可以使用 Class<T>来作为key
Map<Class<?>,Object> map = new HashMap<>();
public <T> void putV(Class<T> type, T v){
map.put(type,v);
}
public <T> T getV(Class<T> type){
return type.cast(map.get(type));
}
这种方式有两种局限性:
第一种,原生态形式使用Class对象,可以通过检查, 例如List<String> 和 List<Char> 的原生态Class对象是 List.class
解决方案,在put时检查值是否是真的type所对应的实例
public <T> void putV(Class<T> type, T v){
map.put(type,type.cast(v));
}
第二种,有些类型不可以具体化,比如List<String>没有对应的Class对象,只有原生态形式擦除了类型 List.class
第30条,用枚举代替int常量
用int类型列举的方式叫做int枚举模式,存在诸多不足,比如可以任意赋值没有任何检查.同样的还有String枚举模式
枚举提供编译时的类型安全
枚举可以重新排列或修改,不用重新编译客户端代码
枚举可以添加方法和域,并实现任意接口
第31条,用实例域代替序数
枚举默认和一个单独的int值关联,所有默认枚举有个ordinal方法,返回枚举常量在类型中的数字位置
public enum Ensemble{
SOLO,DUET
public int getPosition(){
return ordinal()+1000;
}
}
但是如果调整位置,就会导致使用ordinal相关的方法被破坏,这种情况可以通过添加域来保存相关信息
public enum Ensemble{
SOLO(1),DUET(2)
private final int position;
Ensemble(int position){this.position = position}
public int getPosition(){
return position;
}
}
第32条,用EnumSet 代替位域
枚举类型用在集合中时,允许联合使用,int枚举模式如下
public class Text{
public static final int STYLE_BOLD = 1<<0; //1
public static final int STYLE_ITALIC = 1 <<1;
public void applyStyles(int styles){...}
}
//使用
text.applyStyles(STYLE_BOLD|STYLE_ITALIC)
这种可以 使用EnumSet
public class Text{
public enum Style{ BOLD,ITALIC }
public void applyStyles(Set<Style> styles){...}
}
//使用
text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC))
第33条,用 EnumMap代替序数索引
用枚举索引作为数组下标,隐藏这很多问题,这种情况可以用EnumMap来实现
public class Herb{
public enum Type{ ANNUAL,PERENNIAL,BIENNIAL}
private final String name;
private final Type type;
Herb(String name,Type type){
this.name = name;
this.type = type;
}
}
Map<Herb.Type,Set<Herb>> herbsByType = new EnumMap<Herb.Type,Set<Herb>>(Herb.Type.class);
for(Herb.Type t: Herb.Type.values())
herbsByType.put(t,new HashSet<Herb>());
for(Herb h:garden)
herbsByType.get(h.type).add(h);
第34条,用接口模拟可伸缩枚举
枚举在这中语言特性下,是不可以继承扩展的,但有一种方式可以利用接口
public interface Operation{
double apply(double x,double y);
}
public enum BasicOperation implements Operation{
PLUS("+"){
public double apply(double x,double y){return x + y;}
}
MINUS("-"){
public double apply(double x,double y){return x - y;}
}
TIMES("*"){
public double apply(double x,double y){return x * y;}
}
DIVIDE("/"){
public double apply(double x,double y){return x / y;}
}
private final String symbol;
BaseOperation(String symbol){this.symbol = symbol;}
}
public enum ExtendedOperation implements Operation{
EXP("^"){
public double apply(double x,double y){return Math.pow(x,y);}
}
private final String symbol;
ExtendedOperation (String symbol){this.symbol = symbol;}
}
第35条,注解优先于命名
第36条,坚持使用Override注解
第37条,用标记接口定义类型
比如Serializable接口表明此类可以被序列化
第38条,检查参数的有效性
声明的方法,对应的参数要执行检查可以适当的抛出错误
第39条,必要时进行保护性拷贝
将参数复制到方法内的局部变量,再处理,避免方法执行后或过程中参数有修改.
第40条,谨慎设计方法签名
避免过长的参数列表,可采用:
分解方法,建辅助类保存多个参数,采用Builder模式三种方式
第41条,慎用重载
重载不同函数避免参数有继承关系,选择是由参数决定,没有根据具体对象
public void m(String str){}
public void m(CharSequence cs){}
CharSequence[] css = {"str",new CharSequence()}
m(css[0]);//执行的是第二个方法
m(css[1]);//执行的是第二个方法
第42条,慎用可变参数
使用可变参数时注意参数可能为0个,所以要有显示的检查或者改为一个默认参数和可变参数两个参数
第43条,返回零长度的数组或者合集,而不是null
这样做避免调用方进行再次非空判断
第44条,为所有导出的API元素编写文档注释
使用标签
@param @return @throws
第45条,局部变量的作用域最小化
避免局部变量被其他代码错误使用
第46条,for-each循环优先于传统的for循环
比for循环有性能优势,数组边界值值计算一次.
但有些情况不可以使用:
1)删除元素,要用显示的迭代器
2)修改元素要索引下标
3)多个集合并行迭代,要用索引同步
第47条,了解和使用类库
1,随机 Random.nextInt(int maxValue)
常用工具库包名
java.util
java.util.concurrent
java.lang
第48条,精准的结果,避免使用float和double
这些事有损运算,精准的可以使用 BigDecimal
第49条,基本类型优先于装箱基本类型
二者区别:
1,基本类型只有值,二装箱基本类型则具有与它们的值不同的同一性,装箱的是对象.
2,装箱的值可以为null
3,基本类型更节省时间空间.
第50条,如果其他类型更合适,尽量避免使用字符串
第51条,当心字符串连接的性能
要多次字符串拼接时,可以使用StringBuilder替代String
第52条:通过接口引用对象
第53条,接口优先于反射机制
第54条,谨慎使用本地方法JNI
常用情形
1,访问特定于平台的机制,如注册表
2,访问遗留代码库
3,提升性能
缺点:
不安全,不利于阅读,可移植性差
第58条,对可恢复的情况使用受检查异常,对编程错误使用运行的异常
三种可抛出结构:
受检查的异常(checked exception)
运行时异常 (run-time exception)
错误(error)
第78条,考虑用序列化代理代替序列化实例
序列化增加出错和出现安全问题.
代理模式:
为可序列化的类设计一个私有的静态嵌套类,精确地标识外围类的实例的逻辑状态,这个嵌套类被称作序列化代理,它应该有一个单独的构造器,参数类型就是那个外围类,可以防止字节流攻击
public final class Period{
private final Date start;
private final Date end;
public Period(Date start,Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTiem());
}
private startic class SerializationProxy implements Serializable{
private final Date start;
private final Date end;
SerializationProxy(Period p){
this.start = p.start;
this.end = p.end;
}
private startic final long serialVersionUID = ....;
private Object readResolve(){
return new Period(start,end)
}
}
private Object writerReplace(){
return new SerializationProxy(this);
}
privatevoid readObject(ObjectInputStream stream)throws InvalidObjectException{
throw new InvalidObjectException("")
}
}
局限性:
不能与可以被客户端扩展的类兼容,也不能和包含循环的某些类兼容.
开销代价变大