一.创建和销毁对象
1.静态工厂方法代替构造器
(1)不必每次都创建新的对象,可以使用==代替eauqls()返回缓存中已有的对象。
(2)可以返回子类实例
(3)可根据不同目的为静态工厂方法起不一样的名称,不必像构造方法写死
2.运用构造器处理多个参数时使用Builder
三种方式:构造器、Bean、Builder
(1)参数多时构造器调用不方便
(2)Bean开发者同时操作同一对象时线程不安全
(3)Builder缺点在于开销
Builder示例如下:
public class Test1 {
private int height;
private int weight;
private int size;
public Test1(Builder builder) {
this.height = builder.height;
this.weight = builder.weight;
this.size = builder.size;
}
public static class Builder {
private final int height;
private int weight;
private int size;
public Builder(int height) {
this.height = height;
}
public Builder weight(int weight) {
this.weight = weight;
return this;
}
public Builder size(int size) {
this.size = size;
return this;
}
public Test1 build() {
return new Test1(this);
}
}
public static void main() {
Test1 test1 = new Test1.Builder(0).weight(0).size(0).build();
}
}
3.用私有构造器或者枚举类型强化Singleton属性
(1)私有构造器,公有静态final域
(2)私有构造器,公有静态工厂方法:缺点一:AccessibleObject.setAccessible可调用私有构造器,缺点二:反序列化一个序列化的实例时会重新创建对象,要实现Serializable接口,提供readResolve方法避免这个问题
(3)单元素枚举类型(首选)
4.通过private构造器使类不可实例化
(1)应用场景:某些只提供静态方法或变量的工具栏,开发者并不希望其被继承
(2)使用private构造器要通过注释声明为什么使用
(3)缺点:该类不能再被子类化
5.避免创建不必要的对象
类初始化的顺序:先初始化父类的静态代码 —> 子类的静态代码 —> 父类的非静态代码 —> 父类构造函数 —> 子类非静态代码 —>子类构造函数
(1)自动装箱与拆箱
(2)使用static代码块
6.消除过期的对象引用
内存泄露常出现在:
(1)类是自己管理内存。
(2)缓存,由于缓存没有及时清除无用的条目而出现,可以使用weakHashMap来避免这种情况
(3)监听器和其他回调,确保回调立即被当做垃圾回收的最佳方法是只保留它们的弱引用
7.避免使用finalize方法
如果使用了一定要调用super.finalize
原因:
(1)finalize不是一定能执行
(2)该方法存在严重的性能问题
8.覆盖equals时请遵守通用约定
不覆盖:
(1)类的每个实例都唯一。
(2)不关心类是否提供了“逻辑相等”的测试功能。
(3)超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的
(4)类是私有的或是包级私有的,可以确定它的equals方法永远也不会被调用。
覆盖:
类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为
高质量equals:
(1)使用==操作符检查“参数是否为这个对象的引用”。ref1 == ref2
(2)使用instanceof操作符检查“参数是否为正确的类型”。o instanceof ColorPoint
(3)把参数转换成正确的类型。ColorPoint cp = (ColorPoint) o;
(4)对于该类中的每个“关键域”,徐检查参数中的域是否与对象的域相匹配。
return cp.point.equals(point) && cp.color.equals(color)
(5)当编写完equals方法后,应该检查是否满足对称性,传递性以及一致性。
9.覆盖equals时总要覆盖hashCode
10.始终覆盖toString
11.谨慎覆盖clone
clone方法设想提供一种不需要构造器就可以创建对象的方法,但是自身存在很多问题,专家一般都不会去调用它
12.实现Comparable接口
实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序
二.类和接口
13.使类和成员的可访问性最小
实例域决不能是共有的,包含公有可变域的类即便是final的也不是线程安全的
优势:
(1)更好的解除各个模块之间的耦合关系
(2)最大化并行开发
(3)性能优化和后期维护
(4)代码的高可复用性
14.在公有类中使用访问方法而非公有域
(1)不因通过类名访问类属性,而应该通过类似getter或setter等方法
(2)公有类不应该直接暴露数据域(可变的域)
15.使可变性最小化
不可变对象本质上是线程安全的。
(1)不要提供任何会修改对象状态的方法
(2)保证类不会被扩展,即声明为final类,或将构造函数定义为私有
(3)使所有的域都是final的
(4)使所有的域都成为私有的
(5)确保对于任何可变组件的互斥访问。
16.复合优先于继承
(1)类中增加一个私有域(要继承的父类)
(2)实现一个带父类公有方法的接口
17.要么为继承而设计,并提供文档说明,要么就禁止继承
构造器决不能调用可悲覆盖的方法。
为继承而设计的类应提供文档说明,应实现Serializable接口
禁止子类化:(1)把类声明为final的;(2)把所有的构造器变为私有的,或者包级私有的,并增加一些共有的静态工厂来替代构造器。
18.接口优先于抽象类
抽象类是对事物的抽象,接口是对行为的抽象。
三.深入理解Java的接口和抽象类
19.接口只用于定义类型
不应该使用接口来导出常量, 直接使用一个不能初始化的类来导出就可以了。
20.类层次优于标签类
标签类拆分成继承结构的类层次
21.用函数对象表示策略
函数对象:调用操作符的类,其对象常称为函数对象
22.优先考虑静态成员类
(1)非静态成员类的每一个实例有隐含着与外围类的一个外围实例
(2)如果声明的成员类不需要访问外围实例,则将其声明为static , 如果省略了 static ,那么这个类的每个实例都包含了一个指向外围对象的引用。
嵌套类有四种: A.静态成员类 B.非静态成员类 C.匿名类 D.局部类
四.泛型
E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等
S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样
23.请不要在新代码中使用原生态类型
原生态类型在取出时会有不安全性
24.消除unchecked warnings
特别确认没问题时可以用@SuppressWarnings("unchecked")消除警告
25.List列表优先于数组
数组是协变的,List是不可变的
26.优先考虑泛型
泛型实现原理是类型擦除
27.优先考虑泛型方法
- 利用有限制通配符来提升API的灵活性
<? extends T> <? super T>
- 优先考虑类型安全的异构容器(?)
五.枚举和注解
- 用enum组织int常量
31.用实例域代替序数?
32.用EnumSet代替位域?
33.用EnumMap代替序数索引?
- 用接口模拟可伸缩的枚举?
- 注解优先于命名模式
@interface是用于自定义注解的,它里面定义的方法的声明不能有参数,也不能抛出异常,并且方法的返回值被限制为简单类型、String、Class、emnus、@interface ,和这些类型的数组。
注解@Target也是用来修饰注解的元注解,它有一个属性ElementType也是枚举类型,值 为:ANNOTATION_TYPE,CONSTRUCTOR ,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER和TYPE,如 @Target(ElementType.METHOD) 修饰的注解表示该注解只能用来修饰在方法上。
@RetentionRetention注解表示需要在什么级别保存该注释信息,用于描述注解的生命周期,它有一个RetentionPolicy类型的value,是一个枚举类型,它有以下的几个值:
(1)用@Retention(RetentionPolicy.SOURCE)修饰的注解,指定注解只保留在源文件当中,编译成类文件后就把注解去掉;
(2)用@Retention(RetentionPolicy.CLASS)修饰的注解,指定注解只保留在源文件和编译后的class 文件中,当jvm加载类时就把注解去掉;
(3)用@Retention(RetentionPolicy.RUNTIME )修饰的注解,指定注解可以保留在jvm中,这样就可以使用反射获取信息了。
默认是RUNTIME,这样我们才能在运行的时候通过反射获取并做对应的逻辑处理。
- 坚持使用Override注解
帮助编译器检查更精确
37.用标记接口定义类型
使用注解替代标记接口
六.方法
38.检查参数的有效性
在方法入口使用assert,assert需要显式开启
39.必要时进行保护性拷贝
构造器内拷贝传入参数,防止在参数被传入后内部进行改变导致的不一致性
40.谨慎设计方法的签名
拆分长参数列表策略:
(1)长参数列表分为多个短参数方法
(2)创建帮助类用于传入多个参数
(3)使用Builder设置多个参数(类似第2条)
(4)优先使用接口代替类作为参数数据类型
(5)优先使用仅包含两个枚举量的枚举类代替boolean参数类型 ?
41.慎用重载
42.慎用可变参数
43.返回零长度的数组或者集合,而不是null
Collections.emptyList()
Collections.emptySet();
Collections.emptyMap();
零长度数组:
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheese(){
return cheesesInStack.toArray(EMPTY_CHEESE_ARRAY);
}
避免取数据时进行再次判空
44.为所有导出的API元素编写文档注释
七.通用程序设计
45.将局部变量的作用域最小化
(1)for循环优先于while,for能够避免“剪切-粘贴”错误,能够更早发现程序中的错误
(2)类似for中变量尽量在for中声明
46.for-each循环优先于for循环
47.了解和使用类库
48.如果需要精确的答案,请避免使用float和double
用BigDecimal
49.基本类型优先于装箱基本类型
(1)基本类型比装箱节省时间和空间
(2)装箱类型还有一个非功能值null
(3)装箱类型不能用==作比较
什么时候使用装箱类型:
(1)作为集合中的元素、键和值
(2)在参数化类型中,必须使用装箱类型作为类型参数,因为java不允许使用基本类型(参数化类型:List<类型(例如String)> list = new ArrayList<String>();)
(3)在进行反射的方法调用时,必须使用装箱基本类型
- 不要使用字符串来组织数据?
- 当心字符串连接的性能
str1 += str2的底层实现为
str1 = new StringBuilder().append(str1).append(str2).toString();
多个字符串拼接时用StringBuilder代替“+”
+和concat实现原理类似,但+更灵活,concat可能导致NPE
52.使用接口引用对象
53.接口优先于反射机制
反射缺陷:
① 它在编译时不会进行类型检查;
② 实现代码冗长乏味,不易阅读;
③ 性能与一般的方法调用相比,要低下很多;
54.谨慎地使用本地方法
本地方法不安全
55.谨慎地进行优化
1)任何优化都存在风险,有时候弄不好反而带来其他的问题
2)并不是 性能 优先。努力编写好的程序而不是快的程序。
3)对前人,尤其是类似于Java API这样的成熟代码,进行优化,是不明智的(要是能优化,人家早就做了)
- 遵守普遍接受的命名惯例
八.异常
57.只针对异常情况才使用异常
(1)异常没有发生时,try catch一般不会影响性能
(2)try的作用范围区别就是异常表中的开始地址和结束地址,作用范围也是不会影响性能的
58.对可恢复的情况使用受检异常,对编程错误使用运行时异常
受检异常:FileNotFoundException,ClassNotFoundException,SQLException,IOException
非受检异常:RunTimeException(运行时异常),Error(错误)
59.避免不必要的使用受检异常
60.优先使用标准异常
异常类越少,意味着内存占用越小,并且转载这些类的时间开销也越小。
61.抛出与抽象相对应的异常
异常转译
62.每个方法抛出的异常都要有文档
使用@throws标签说明异常情况
63.在细节中包含能捕获失败的信息
fillInStackTrace()输出细节消息
64.努力使失败保持原子性
(1)在对其进行处理之前,先做参数有效性的检查
(2)编写一段恢复代码,发生失败时,可以使对象回滚到操作开始之前的状态
(3)在对象的一份临时拷贝上执行操作,当操作正确结束后,再把临时拷贝中的结果复制给原来的对象。如果一旦失败,不进行这个复制,也就保持了原对象的状态
65.不要忽略异常
关闭fileinputstream时可以忽略异常,其他情况下空的catch块都应该警钟长鸣
九.并发
66.同步访问共享的可变数据
使用volatile更方便
67.避免过度同步
68.executor和task优先于线程
Executors.newSingleThreadExecutor().execute
69.并发工具优先于wait和notify
concurrent包提供了并发工具
70.线程安全性的文档化
71.慎用延迟初始化
实例域,使用双重检查模式初始化
静态域,使用内部类初始化
72.不要依赖于线程调度
不要依赖Thread.yield
73.避免使用线程组
ThreadGroup
十.序列化
74.谨慎地实现Serializable接口
(1)大多数的Data和BigInteger这样的的值集合类应该实现Serializable
(2)代表活动实体的类,如线程池,一般不应该实现Serializable
为实现Serializable而付出的代价是:
(1)一旦一个类被发布,就大大降低了"改变这个类的实现"的灵活性
(2)实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性
(3)随着类发行新的版本,相关的测试负担也增加了
75.考虑使用自定义的序列化形式
readObject,writeObject
76.保护性的编写readObject方法
77.对于实例控制,枚举类型优先于readResolve
78.考虑用序列化代理代替序列化实例
网友评论