Effective java
创建和销毁对象
考虑用静态工厂方法替代构造器
遇到多个构造器参数时要考虑用构建器
用私有构造器或者枚举类型强化Singleton属性
enum SinfletonDemo{
INSTANCE;
public void otherMethod{
System.out.println("Something");
}
}
enum Color{
RED(1),GREEN(2),BLUE(3);
private int code;
Color(int code){
this.code = code;
}
public int getCode(){
return code;
}
}
通过私有化构造器强化不可实例化能力
避免创建不必要的对象
消除过期的对象引用
避免使用终结方法
对于所有对象都通用的方法
覆盖equals时请遵守通用的约定
覆盖equals时总要覆盖hasCode
始终要覆盖toString
谨慎的覆盖clone
考虑实现Comparable接口
类和接口
使类和成员的可访问性最小化
第一规则:尽可能的使每个类或者成员不被外界访问
对于顶层的(非嵌套的)类和接口,只有两种可能的访问级别:包级私有的和共有的
对于成员(域、方法、嵌套类和嵌套接口)有四种可能访问级别:
私有的—至于在声明该成员的顶层类内部才可以访问这个成员
包级私有的—声明该成员的包内部的任何类都可以访问这个成员。(default)
受保护的—声明该成员的类的子类可以访问这个成员,并且声明该成员的包内部的任何类也可以访问这个成员
共有的—在任何地方都可以访问该成员
实例域决不能是共有的
在公有类中使用访问方法而非公有域
//Degenerate classes like this should not be public
class Point{
public double x;
public double y;
}
//Encapslulation of data bu accessor methods and mutators
class Point{
public double x;
public double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
使可变性最小化
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。
遵循五条规则:
不要提供任何会修改对象状态的方法
保证类不会别扩展
使所有的域都是final的
使所有的域都成为私有的
确保对于任何可变组件的互斥访问
“`java
public final class Complex{
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
@Contract(pure = true)
public double getRe() {
return re;
}
@Contract(pure = true)
public double getIm() {
return im;
}
public Complex add(Complex c){
return new Complex(re+c.re,im+c.im);
}
“`
除了将类声明为final之外,第二种将不可变的类编程final的方法就是,让类的所有构造器都编程私有的或者包级私有的并添加共有的静态工厂来替代共有的构造器。
public class Complex{
private final double re;
private final double im;
private Complex(double re, double im){
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im){
return new Complex(re,im);
}
}
复合优先于继承
public class Junit<E> extends HashSet<E>{
private int count;
public Junit(int count) {
this.count = count;
}
@Override
public boolean add(E e) {
count++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c){
count+=c.size();
return super.addAll(c);
}
public int getCount(){
return count;
}
}
//测试代码
Junit<String> list = new Junit<>(0);
list.addAll(Arrays.asList("span","add","la"));
System.out.println(list.getCount());
这里最终的输出结果是6,因为在HashSet中的addAll方法又调用了子类的add方法。
复合:在新类中增加一个私有域,他引用现有类的一个实例
转发:新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法。
class InstrumentedSet<E> extends ForwardingSet<E>{
private int addCount = 0;
public InstrumentedSet(Set<E> s){
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(@NotNull Collection<? extends E> c) {
addCount+=c.size();
return super.addAll(c);
}
public int getAddCount(){
return addCount;
}
}
class ForwardingSet<E> implements Set<E>{
private final Set<E> s;
public ForwardingSet(Set<E> s){
this.s = s;
}
@Override
public int size() {
return s.size();
}
@Override
public boolean isEmpty() {
return s.isEmpty();
}
@Override
public boolean contains(Object o) {
return s.contains(o);
}
@NotNull
@Override
public Iterator<E> iterator() {
return s.iterator();
}
@NotNull
@Override
public Object[] toArray() {
return s.toArray();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
return s.toArray(a);
}
@Override
public boolean add(E e) {
return s.add(e);
}
@Override
public boolean remove(Object o) {
return s.remove(o);
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
return s.containsAll(c);
}
@Override
public boolean addAll(@NotNull Collection<? extends E> c) {
return s.addAll(c);
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
return s.retainAll(c);
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
return s.removeAll(c);
}
@Override
public void clear() {
s.clear();
}
}
因为每一个InstrumentedSet实例都把另一个Set实例包装起来了,所以InstrumentedSet类被称为包装类。这也正是Decorator模式,因为Instrumentedset类对一个集合进行了修饰,为他增加了技术特性。有时候,复合和转发的组合也被错误的称为“委托’。从技术的角度分析,这不是委托,除非包装对象把自身传递给被包装的对象。
要么为继承而设计,并提供文档说明,要么就禁止继承
接口优先于继承类
现有的类可以很容易被更新,以实现新的接口。
接口是定义mixin(混合类型)的理想选择
接口允许我们构造非层次结构的类型框架
接口一旦被公开发行,并且已被广泛实现,在想改变这个接口几乎是不可能的。
接口只用于定义类型
常量接口模式是对接口的不良使用
类层次优于标签类
标签类过于冗长,容易出错,并且效率低下。
标签类
class Figure{
enum Shape{RECTANGLE,CIRCLE};
final Shape shape;
double length;
double width;
double radius;
Figure(double radius){
shape = Shape.CIRCLE;
this.radius = radius;
}
Figure(double length,double width){
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area(){
switch (shape){
case CIRCLE:
return Math.PI*radius*radius;
case RECTANGLE:
return length*width;
default:
throw new AssertionError();
}
}
}
类层次
abstract class Figure{
abstract double area();
}
class Circle extends Figure{
final double radius;
Circle(double radius){
this.radius = radius;
}
@Override
double area() {
return Math.PI*radius*radius;
}
}
class Rectangle extends Figure{
final double length;
final double width;
Rectangle(double length,double width){
this.length = length;
this.width = width;
}
@Override
double area() {
return length*width;
}
}
用函数对象表示策略
优先考虑静态成员类
嵌套类:是指定义在另一个类内部的类。
嵌套类种类:静态成员类、非静态成员类、匿名类、局部类
泛型
请不要再新生代码中使用原生态类型
如果使用原生态类型,就失掉了泛型在安全性和表属性方面的所有优势。
如果使用list这样的原生类型,就会失掉类型安全性,但是如果使用像List这样的参数化类型,就不会。
两个使用原生类型的例外:
在类文字中必须使用原生态类型。
由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符上使用instanceof是非法的。
消除非受检警告
要尽可能的消除每一个非受检警告
如果无法消除警告,同时可以证明引起警告的代码时类型安全的,(只有在这种情况下才可以用一个@SuppressWarnings)(“unchecked”)注解来禁止这条警告
应该在尽可能小的范围内使用SuppressWarnings注解。
列表优先于数组
数组于泛型相比有两个不同:首先:数组是协变的。第二数组是具体化的。
为什么创建泛型数组是不安全的?因为他不是类型安全的。要是他合法,编译器在其他正确的程序中发生的转换就会在运行时失败,并出现一个ClassCastException异常。这就违背了泛型系统提供的基本保证。
不可具体化类型是指运行时表示法包含的信息比他的编译时的信息要少。
优先考虑泛型
优先考虑泛型方法
泛型方法有一个显著特点是,无需明确指定类型参数的值,不想调用泛型构造器的时候是必须指定的。编译器通过检查方法参数的类型来计算类型参数的值。:类型推导
递归类型限制:通过某个包含该类型参数本身的表达式来限制类型参数。
利用有限制通配符来提升API灵活性
优先考虑类型安全的异构容器
枚举和注解
用enum代替int常量
枚举类型是指一组固定的常量组成合法值的类型。
策略枚举 类似于策略模式
用实例域代替序数
用EnumSet代替位域
用EnumMap代替序数索引
最好不要采用序数来索引数组而是采用enumMap。
用接口模拟可伸缩的枚举
注解优先于命名模式
坚持使用override注解
用标记接口定义类型
方法
检查参数的有效性
必要时进行拷贝设计
谨慎设计方法签名
慎用重载
慎用可变参数
返回零长度数组或者集合,而不是null
为所有导出的API元素编写文档注释
通用程序设计
将局部变量的作用域最小化
for-each循环优先于传统的for循环
了解和使用类库
如果需要精确的答案,请避免使用float和double(浮点数的储存?)
正确的方式是使用BigDecimal、int或者long进行货币计算。
基本类型优先于装箱基本类型
对装箱基本类型进行==操作基本是错误的。
使用装箱基本类型的时候:
第一作为集合中的元素、键和值。第二就是在参数化类型的时候。最后在进行反射方法调用的时候,必须使用装箱基本类型。
如果其他类型适合,则尽量避免使用字符串
字符串不适合代替其他的值类型。
字符串不适合代替枚举类型
字符串不适合代替聚集类型。
字符串也不适合代替能力表
当心字符串连接的性能
为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。
为了获得可以接受的性能,请使用StringBuilder替代String
通过接口引用对象
如果有适合的接口类型存在,那么对于参数,返回值、变量和域来说,就都应该使用接口类型进行声明。
接口优先于反射机制
谨慎的使用本地方法
谨慎的进行优化
遵守普遍接受的命名惯例
异常
只针度异常情况才使用异常
异常应该只用于异常的情况下;他们永远不应该用于正常的控制流。
对可恢复的情况使用受检查异常,对编程错误使用运行时异常
Java语言提供饿了三种可抛出结构:受检查异常、运行时异常和错误。
避免不必要的使用受检的异常
优先使用标准的异常
抛出与抽象对应的异常
每个方法抛出的异常都要有文档
在细节消息中包含能捕获失败的消息
努力使失败保持原子性
一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性
最常见的方法就是使用不可变对象。
对于可变对象上操作的方法,获得失败原子性最常见的方法是,在执行操作之前进行参数的有效性检查。
第三种就是 编写一段恢复代码,由他来拦截操作过程中可能发生的失败。
在对象的一份临时拷贝上执行操作,当执行操作完成之后在用临时拷贝中的结果替代对象的内容。
不要忽略异常
并发
同步访问共享的可变数据
同步可以阻止一个线程看到对象处于不一致状态之中,还可以保证进入同步方法或者代码块的每个线程,都看到由同一个锁保护的之前所有修改效果。
Java语言规范保证读一个或者写一个变量是原子的。除非这个变量的类型是long或者double。
避免过度同步
为了避免活性失败和安全性失败,在一个被同步的方法后者代码块中,永远不要放弃对客户端的控制。
executor和task优先于线程
并发工具由于wait和notify
线程安全性文档化
慎用延迟初始化
lazy initialization holderclass 模式
private static class FieldHolder{
static final FieldType field = computerFieldValue();
}
static FieldType getField(){
return FieldHolder.field;
}
不要依赖于线程调度器
避免使用线程组
序列化
将一个对象编码成一个字节流
谨慎地实现Serializable接口
实现Serializable接口而付出的最大的代价是,一旦一个类被发布,就大大降低了“改变这个类实现的灵活性”。
实现Serializable的第二个代价是,他增加了出现bug和安全漏洞的可能性。
实现Serializable的第三个代价是,随着类发行的版本,相关的测试负担也增加饿了。
为了继承让我如涉及的类应该尽可能少的去实现Serializable接口,用户的接口也应该尽可能少的继承Seializable接口。
考虑使用自定义的序列化形式
当一个对象的物理表示法与他的逻辑数据内容有实质性区别时,使用默认序列化形式会有以下4个缺点:
他使用这个类的导出API永远地束缚在该类的内部表示法上。
他会消耗过多的空间
他会消耗过多事件
他会引起栈溢出
保护性的编写readObject方法