JAVA程序员

Effective Java读书笔记

2018-11-03  本文已影响10人  李良逸

一.创建和销毁对象

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.优先考虑泛型方法

  1. 利用有限制通配符来提升API的灵活性

<? extends T> <? super T>

  1. 优先考虑类型安全的异构容器(?)

五.枚举和注解

  1. 用enum组织int常量

31.用实例域代替序数?

32.用EnumSet代替位域?

33.用EnumMap代替序数索引?

  1. 用接口模拟可伸缩的枚举?
  1. 注解优先于命名模式

@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,这样我们才能在运行的时候通过反射获取并做对应的逻辑处理。

  1. 坚持使用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)在进行反射的方法调用时,必须使用装箱基本类型

  1. 不要使用字符串来组织数据?
  1. 当心字符串连接的性能

str1 += str2的底层实现为

str1 = new StringBuilder().append(str1).append(str2).toString();

多个字符串拼接时用StringBuilder代替“+”

+和concat实现原理类似,但+更灵活,concat可能导致NPE

52.使用接口引用对象

53.接口优先于反射机制

反射缺陷:

① 它在编译时不会进行类型检查;

② 实现代码冗长乏味,不易阅读;

③ 性能与一般的方法调用相比,要低下很多;

54.谨慎地使用本地方法

本地方法不安全

55.谨慎地进行优化

1)任何优化都存在风险,有时候弄不好反而带来其他的问题

2)并不是 性能 优先。努力编写好的程序而不是快的程序。

3)对前人,尤其是类似于Java API这样的成熟代码,进行优化,是不明智的(要是能优化,人家早就做了)

  1. 遵守普遍接受的命名惯例

八.异常

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.考虑用序列化代理代替序列化实例

上一篇 下一篇

猜你喜欢

热点阅读