Effective Java 读书笔记

目录

一、创建和销毁对象

1.用静态工厂方法代替构造器:

2.考虑使用构建器:

3.用私有构造器或枚举类型强化Singleton属性:

4.通过私用构造器强化不可实例化的能力:

5.优先考虑依赖注入来引用资源:

6.避免创建不必要的对象:

7.消除过期的对象引用:

8.避免使用终结方法和清除方法:

9.try-with-resource 优于try-finally:声明需要关闭的资源时,优先使用try-with-resource

二、对所有对象都通用的方法

10.覆盖equals时需遵守通用约定:equals()方法要保证自反性、对称性、传递性、一致性、null不等性

11.覆盖equals必须覆盖hashCode:

12.始终要覆盖toString:

13.谨慎的覆盖clone:

14.考虑实现Comparable接口:

三、类和接口

15.使类和成员的可访问性最小化:

16.要在共有类而非公有域中使用访问方法:

17.使可变性最小化:

18.复合优于继承:

19.要么为继承提供文档说明,要么禁止继承

20.接口优先于抽象类:

21.为后代设计接口:

22.接口只能用于定义类型

23.类层次优于标签类:

24.静态成员类优于非静态成员类:

25.限制源文件为单个顶级类:

四、泛型

26.不要使用原生态类型:

27.消除非受检的警告: 

28.列表优于数组:

29.优先考虑泛型:

30.优先考虑泛型方法:

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

32.谨慎并用泛型和可变参数:

33.优先考虑类型安全的异构容器

五、枚举和注解

34.用enum代替int常量:

35.用实例域代替序数:

36.用EnumSet代替位域:

37.用EnumMap代替序数索引:

38.用接口模拟可扩展的枚举:

39.注解优于命名模式:

40.坚持使用Override注解:

41.用标记接口定义类型:

42.Lambda优先于匿名类:

43.方法引用优先于Lambda:

44.坚持使用标准的函数接口:

45.谨慎使用Stream:

46.优先使用Stream中无副作用的函数

47.Stream要优先用Collection作为返回类型:

48.谨慎使用Sream并行:

七、方法

49.检查参数的有有效性:

50.必要时进行保护性拷贝:

51.谨慎设计方法签名:

52.慎用重载:

53.慎用可变参数: 

54.返回零长度数组或集合,而不是null

55.谨慎返回optional: 

56.为所有导出的API元素编写文档注释

八、通用编程

57.将局部变量的作用域最小化:

58.for-each优先于传统的for循环:

59.了解和使用类库:优先使用最新可靠的代码。

60.如需准确答案,避免使用float和double:

61.基本类型优先装箱类型:

62.如需其他类型更合适,避免使用String类型:

63.了解String的连接性能:

64.通过接口引用对象:

65.接口优先于反射机制:

66.谨慎使用本地方法:

67.谨慎地进行优化: 

68.遵守普遍接受地命名惯例:

九、异常

69.只对异常的情况才使用异常:

70.对可恢复的情况使用受检异常,对编程错误使用运行时异常:

71.避免不必要地使用受检异常:

72.优先使用标准的异常:

73.抛出与抽象对应的异常:

74.每个方法抛出的所有异常都要建立文档:

75.在细节消息中包含失败-捕获信息:

76.努力使失败保持原子性:

77.不要忽略异常:

十、并发

78.同步访问共享的可变数据:

79.避免过度同步:

80.executor、task和stream优先于线程:

81.并发工具优先于wait和notify:

82.线程安全性的文档化

83.慎用延迟初始化

84.不要依赖线程调度器

十一、序列化

85.其他方法优于java序列化

86.谨慎地实现Serializable接口

87.考虑使用自定义的序列化方式

88.保护性地编写readObject方法

89.对于实例控制,枚举类型优先于readResolve

90.考虑用序列化代理代替序列化


Effective Java 读书笔记_第1张图片

一、创建和销毁对象

1.用静态工厂方法代替构造器:

  • 类可以提供一个公有地静态工厂方法,代替构造器。
  • 例如:
    • // Boolean.java 
      public static Boolean valueOf(boolean b) { 
          return b ? Boolean.TRUE : Boolean.FALSE; 
      }
  • 优点:
    • 有确定的名字
    • 不需要每次调用都创建新的对象
    • 可以返回任何子类的对象
    • 返回对象的类型可以根据参数的不同而变化
    • 无需提前考虑返回对象的类是否存在

2.考虑使用构建器:

  • 重叠构造器模式在有许多参数的时候,客户端代码会很难缩写,并且仍然较难以阅读 。如果类的可选参数过多,可使用构建器(Builder)代替构造器。
  • Java bean 在构造函数调用后,setter方法调用前,可能会出现线程不安全的情况,需要程序员额外处理
  • 使用构建器,它既保证线程安全,又能提供较好的可读性。 例如:
    • // Builder Pattern
      public class NutritionFacts { 
          private final int servingSize; 
          private final int servings; 
          private final int calories; 
          private final int fat; 
          private final int sodium; 
          private final int carbohydrate; 
      
          public static class Builder { 
              // Required parameters
              private final int servingSize; 
              private final int servings; 
              // Optional parameters - initialized to default values
              private int calories = 0; 
              private int fat = 0; 
              private int sodium = 0; 
              private int carbohydrate = 0; 
      
              public Builder(int servingSize, int servings) { 
                  this.servingSize = servingSize; 
                  this.servings = servings; 
              } 
      
              public Builder calories(int val) { 
                  calories = val; 
                  return this; 
              }
       
              public Builder fat(int val) { 
                  fat = val; 
                  return this; 
              } 
      
              public Builder sodium(int val) { 
                  sodium = val; 
                  return this; 
              } 
      
              public Builder carbohydrate(int val) { 
                  carbohydrate = val; 
                  return this; 
              } 
      
              public NutritionFacts build() { 
                  return new NutritionFacts(this); 
              } 
          } 
      
          private NutritionFacts(Builder builder) { 
              servingSize = builder.servingSize; 
              servings = builder.servings; 
              calories = builder.calories; 
              fat = builder.fat; 
              sodium = builder.sodium; 
              carbohydrate = builder.carbohydrate; 
          } 
      
          public static void main(String[] args) { 
              NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)                 .calories(100).sodium(35).carbohydrate(27).build(); 
          } 
      }

 3.用私有构造器或枚举类型强化Singleton属性:

  • 将构造方法私有化,保证实例可控。
  • 有些Java类我们不想实例化,只想调用它的静态方法,一般是对于一些工具类。为了保证非实例化,我们可以将构造函数设为私有,这样使用者就没法实例化这个类了。
  • 例如:
  • // Noninstantiable utility class public class UtilityClass { 
    public class UtiClass { 
        private UtiClass() { 
            throw new AssertionError(); 
        } 
    } 

4.通过私用构造器强化不可实例化的能力:

  • 将构造方法私有化,保证实例可控。

5.优先考虑依赖注入来引用资源:

  • 类不用在定义时就决定依赖资源的具体实现,只需要在使用时确定具体实现类即可。

6.避免创建不必要的对象:

  • 能不创建新对象就尽量避免资源的浪费。
    • String s = new String("bikini"); ------> String s = "bikini"; 
  • 使用静态工厂方法来避免创建不必要的对象,如:Boolean.valueOf(String)
  • 将昂贵的对象缓存起来:
    •  public class RomanNumerals { 
          private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 
          static boolean isRomanNumeral(String s) { 
              return ROMAN.matcher(s).matches(); 
          } 
      }
  • 优先使用基本类型,避免无意识地使用包装类

7.消除过期的对象引用:

  • 确定不用的对象,可显示的消除对它的引用,尽早释放空间。

8.避免使用终结方法和清除方法:

  • finalizer和cleaner方法能不用就不要用

9.try-with-resource 优于try-finally:声明需要关闭的资源时,优先使用try-with-resource

  • try-finally的问题是,如果在try和finally中都抛错,堆栈信息中finally的报错信息会覆盖try中的报错信息
  • 使用try-with-resources需要资源类实现AutoCloseable接口 

二、对所有对象都通用的方法

10.覆盖equals时需遵守通用约定:equals()方法要保证自反性、对称性、传递性、一致性、null不等性

  • 覆盖equals的情况:需要进行“等值”的判断,并且超类没有覆盖equals
  • 覆盖equals的通用规定
    • 反身性:对于任何非空的参考值 x,x.equals(x) 必须返回 true。
    • 对称性:对于任何非空参考值 x 和 y,x.equals(y) 必须在且仅当 y.equals(x) 返回 true 时返回 true。
    • 传递性:对于任何非空的引用值 x, y, z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,那么 x.equals(z) 必须返回 true。
    • 一致性:对于任何非空的引用值 x 和 y, x.equals(y) 的多次调用必须一致地返回 true 或一致地返回 false,前提是不修改 equals 中使用的信息。
    • 对于任何非空引用值 x,x.equals(null) 必须返回 false
  • 不要企图让equals方法太过智能

11.覆盖equals必须覆盖hashCode:

  • 保证基于散列的集合能结合该类正常使用

12.始终要覆盖toString:

  • 以更清晰的方式描述实例数据

13.谨慎的覆盖clone:

实现cloneable接口,并注意是深拷贝还是浅拷贝。

14.考虑实现Comparable接口:

可实现comparable接口,用于后续对该类型的集合排序

三、类和接口

15.使类和成员的可访问性最小化:

迪米特法则:最小知道远原则

16.要在共有类而非公有域中使用访问方法:

如果要对某些属性设置公有属性,提供公有方法,而不是将属性直接设为public

17.使可变性最小化:

保证实例可变性最小。

18.复合优于继承:

优先使用复合,而不是继承:将要复用的类作为一个属性放在扩展类中。

19.要么为继承提供文档说明,要么禁止继承

每个继承都要文档说明吗?ide是可以看到继承关系的,是否可以代替文档?我们的系统里基本上没有这种文档

20.接口优先于抽象类:

优先使用接口定义方法,而不是抽象类

21.为后代设计接口:

为避免新的方法导致的错误,建议在类定义时即设计一些必要的接口。

22.接口只能用于定义类型

接口只应该用来定义类型,而不是用来导出常量

23.类层次优于标签类:

明确类中层次结构,而不是根据某些标签属性设置其他属性的值。

24.静态成员类优于非静态成员类:

静态成员类、非静态成员类、匿名类、局部类。

25.限制源文件为单个顶级类:

一个源文件只应该声明一个顶级类。

四、泛型

26.不要使用原生态类型:

原生态类型(List demo)只是为了保证历史版本兼容性,所有新声明都应该是参数化类型(List demo),如有必要可使用通配符类型(List demo)

27.消除非受检的警告: 

非受检的警告都应该消除,实在消除不了且可保证安全的情况可使用@SuppressWarnings注解

28.列表优于数组:

List list 列表优于String[] 数组,因为数组是协变的,即String[]被认为是Obejct[]的字类,而List不是。故列表能更早的在编译期识别出错误的类型转换。

29.优先考虑泛型:

如果某些类的参数具有不确定性,优先使用泛型支持此参数。

30.优先考虑泛型方法:

如果某些类的方法参数具有不确定性,优先使用泛型支持此方法。

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

约束泛型可变范围

32.谨慎并用泛型和可变参数:

泛型和可变参数不要一起使用,会导致一些不可控的运行时异常。

33.优先考虑类型安全的异构容器

五、枚举和注解

34.用enum代替int常量:

优先使用枚举类定义某些常量信息

35.用实例域代替序数:

不要使用enum自带的ordinal()方法作为排序

36.用EnumSet代替位域:

37.用EnumMap代替序数索引:

38.用接口模拟可扩展的枚举:

39.注解优于命名模式:

命名模式不可靠,使用注解标明具有某些特性的类/方法/属性

40.坚持使用Override注解:

覆盖的方法使用@Override方法,会使方法受检,避免不必要的低级错误。

41.用标记接口定义类型:

标明实现了具有某种属性的接口 六、Lambda和表达式

42.Lambda优先于匿名类:

函数接口指只具有一个方法的接口。优先使用Lambda表达式,而不是new XXX{}格式的匿名类。

43.方法引用优先于Lambda:

String::new 等格式的方法引用,优于s->new String()格式的lambada

44.坚持使用标准的函数接口:

使用java自带的标准函数接口(Function, Consumer等),不要重复造轮子。

45.谨慎使用Stream:

Stream虽然语法简洁,但有时代码逻辑不太清晰,注意结合使用

46.优先使用Stream中无副作用的函数

47.Stream要优先用Collection作为返回类型:

优先返回Collection格式

48.谨慎使用Sream并行:

效率不一定高,且有可能会因为某些并行条件导致假死,以及一些并发问题。

七、方法

49.检查参数的有有效性:

不要相信客户端的输入,校验参数合法性

50.必要时进行保护性拷贝:

深拷贝还是浅拷贝,避免对原实例某些属性进行更改。

51.谨慎设计方法签名:

开闭原则要求对修改关闭,故方法设计之初很重要。

52.慎用重载:

  • 重载之前若对原方法调用过程不了解,可能导致某些意想不到的错误。
  • 由于重载方法的选择是静态的,所以在使用时可能会引起混淆。特别是当传递的参数是某个类的子类时,可能不会调用你期望的重载方法,而是调用与参数的编译时类型相匹配的方法。这可能会导致程序的行为与预期不符。

53.慎用可变参数: 

String...args格式的可变参数

54.返回零长度数组或集合,而不是null

  • 避免因为忘记判空而出现NPE
  • 容易出现NPE的几种情况,参考阿里规约嵩山版
    • 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
    • 数据库的查询结果可能为 null。
    • 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
    • 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
    • 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
    • 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。

55.谨慎返回optional: 

还是要额外处理没有值的情况。

56.为所有导出的API元素编写文档注释

八、通用编程

57.将局部变量的作用域最小化:

  • 避免局部变量作用域过大及过早声明。
  • 最有力的方法就是在第一次使用他的地方声明。
  • 几乎每个局部变量的声明都应该包含一个初始化表达式,如果还没有足够的信息来对一个变量进行有意义的初始化,就应该推迟这个声明(使用try-cache的时候除外)

58.for-each优先于传统的for循环:

优先使用forEach语句,避免一些再次获取单元素item的引用以及有可能造成的低级错误。

59.了解和使用类库:优先使用最新可靠的代码。

每个程序员都应该熟悉java.lang   java.util,某种程度上还有java.io中的内容

60.如需准确答案,避免使用float和double:

  • 0.1等无法使用float、double准确表示的值,会导致某些精度丢失。可使用BigDecimal
  • float和double不适合用于货币计算,bigdecimal可以,但他有两个缺点:不方便、很慢
  • 一般使用int/long +单位的方式来进行计算

61.基本类型优先装箱类型:

避免不必要的对象操作,简化操作。

62.如需其他类型更合适,避免使用String类型:

使用更准确的类型。

63.了解String的连接性能:

str1+str2这种格式的操作虽然简单,但性能很差,如有必要可使用StringBuilder

64.通过接口引用对象:

  • Interface1 interface1 = new InterfaceImpl()格式。
    • ArrayList list = new ArrayList<>(): 
      // 改为 
      List list = new ArrayList<>():

  • 好处是,当你决定更换实现时,所要做的就只是改变构造器中类的名称,周围的所有代码都可以继续工作 ,周围的代码并不知道原来的实现类型,所以它们对于这种变化并不在意

65.接口优先于反射机制:

反射代码臃肿且性能不高。

66.谨慎使用本地方法:

尽量避免直接调用native方法

67.谨慎地进行优化: 

考虑性能与优化的成本。

68.遵守普遍接受地命名惯例:

遵守通用的命名格式,减少沟通理解成本

九、异常

69.只对异常的情况才使用异常:

不要用试图异常处理正常流程的扭转。

70.对可恢复的情况使用受检异常,对编程错误使用运行时异常:

明确异常的类型。

71.避免不必要地使用受检异常:

能捕获处理就处理,尽量避免重复及不必要的检查

72.优先使用标准的异常:

捕获的异常要尽量标准且准确

73.抛出与抽象对应的异常:

74.每个方法抛出的所有异常都要建立文档:

类似的文档真的有必要吗?

75.在细节消息中包含失败-捕获信息:

当异常发生时,记录信息,有迹可查

76.努力使失败保持原子性:

保证程序异常时的原子性,回滚某些状态变化

77.不要忽略异常:

捕获到异常需要处理,或记录、或处理,不要不作为

十、并发

78.同步访问共享的可变数据:

可变数据在并发下,要保证能同步访问,synchronized、violate

79.避免过度同步:

细化需要同步的代码块

80.executor、task和stream优先于线程:

避免创建消毁线程的性能

81.并发工具优先于wait和notify:

优先使用concurrent包下的工具类,而不是重量级加锁工具。

82.线程安全性的文档化

83.慎用延迟初始化

84.不要依赖线程调度器

十一、序列化

85.其他方法优于java序列化

86.谨慎地实现Serializable接口

  • 实现serializable的代价
    • 一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性  
    • 它增加了出现 Bug 和安全漏洞的可能性
    • 随着类发行新的版本,相关的测试负担也会增加  
  • 为了继承而设计的类应该尽可能少地去实现 Serializable 接口,用户的接口也应该尽可能少继承 Serializable 接口  

87.考虑使用自定义的序列化方式

88.保护性地编写readObject方法

89.对于实例控制,枚举类型优先于readResolve

90.考虑用序列化代理代替序列化

你可能感兴趣的:(java,读书笔记,java)