《Effective Java》(中文版第二版)读后小结

本文总结了《EffectiveJava》中的关键知识点,包括对象创建与销毁、equals与hashCode的正确实现、接口与类设计、泛型使用、并发编程、序列化注意事项等,为Java开发者提供了实用的编码指南。
摘要由CSDN通过智能技术生成

Effevtive Java

以下是本人阅读Effevtive Java一书后的小结,该书是介绍一些Java编码的一些规范以及一些常见问题的解决放哪。如果没有阅读过本书的可能有些知识点看起来会有点疑惑,建议去阅读一下本书或者是在网上搜索关于知识点的介绍,以便理解。

书籍介绍

《Effective Java》(中文版第二版)

作者 约书亚·布洛克(英语:Joshua J. Bloch)1961年8月28日出生。美国著名程序设计师。他为JAVA平台设计并实作了许多的功能。在google工作,是google的首席JAVA架构师。

一、创建和销毁对象

1.静态方法替代构造方法

  • 优势

    • 有名称,可以使用名称区分不同构造参数的意义

    • 不用在每次调用的时候都创建一个新的对象

    • 可以返回原对象的子类

    • 创建参数化实例,使代码变得简洁

2.使用构建器Builder替代构造方法

  • 使用场景

    • 类中有很多参数不一样的构造方法

  • 如何使用

    • 定义静态类Builder到实体类内部

    • 私有化实体类的构造方法

    • Builder提供构造方法,不同参数生成返回不同的实例对象

    • 实例类.Builder(String xxx)

3.使用单例模式

  • 减少冗余的实例对象的创建

4.私有化构造方法

  • 避免不必要的对象创建,比如工具类

5.在静态代码块实例化不变的类内部对象

避免不必要的对象创建

6.消除过期的对象引用

  • 防止被引用的对象不被垃圾回收器回收,可能造成的内存溢出

7.避免使用终结方法

  • 终结方法导致行为不稳定

    • 不能保证终结方法能被调用

    • 如果要使用,尽量放在try/catch/finally的语法结构中

  • 性能下降

    • 创建一个简单对象,大约为5.6ns,使用finalize时间增加到了2400ns,慢了大约430倍

二、对象的通用方法

1.覆盖equals时遵守约定

  • 自反性

    • 对于非空的引用,x.equals(x) 必须返回true

  • 对称性

    • 如果x.equals(y)返回true,那么y.equals(x) 也必须返回true

  • 传递性

    • 如果x.equals(y)为true,y.equals(z)为true 那么x.equals(z)也必须为true

  • 一致性

    • 对于多次调用同一个对象的比较x.equals(y),返回结果必须都一致

2.高质量编写equals方法

  • 使用==操作符检查“参数是否为这个对象的引用” 如果在比较操作比较昂贵,且符合业务意义的时候

  • 使用instanceof操作符判断类型是否正确,类型不正确提前返回false

  • 把参数类型转换成正确的类型

  • 对于该类的每个“关键域(signification)”,检查参数中的域是否与该对象中对应的域匹配

  • 重写的方法满足自反、对称、传递、一致

  • 覆盖equals方法时,总是要覆盖hashCode方法

    • 不重写hashCode在Map或其他类使用的时候,会被认为是不同的对象

  • 不要让equals方法过于智能化

  • 不要将equals方法的入参改为其他类型 可以使用@override避免这种情况的发生

3.覆盖toString

  • 便于输出关键信息

4.谨慎覆盖clone

  • 克隆对于引用类型的值,是浅拷贝,只会复制引用对象的内部参数对象的地址,不会创建新的参数对象

5.考虑实现Comparable接口

  • 实现了Comparable接口,它就可以跟许多的泛型算法和依赖于该接口的集合进行协作,轻松实现排序功能

三、接口和类

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

2.在公有类中使用访问方法而非公有域

  • 参数不定义为public,而是用get/set方法访问

3.使可变性最小化

  • 不可变类,比如String/Integer

    • 不提供任何会修改类对象状态的方法

    • 保证类不会被扩展

    • 所有的域都是final的

    • 所有的域都为私有的

    • 确保对于任何可变组件的互斥访问

4.复合优先于继承

  • 复合

    • public class A{ private B b; public A(){ b = new B(); }

    private void method(){ b.method(); } }

  • 如果类没有明确的继承关系,仅仅是为了减少代码耦合度,尽量使用复合

5.要么为继承而设计,并提供文档说明,要么就禁止继承

6.接口优于抽象类

  • Java类的单继承特性

7.接口只用于定义类型

  • 接口常量类

    • 只定义静态常量

    • 如果将来常量用不上了,这个常量接口也不能移除,因为子类可能已经继承了该类

    • 如果类是可序列化的,就算没有被继承,也会存在移除后反序列化异常的问题

8.类层次优于标签类

  • 标签类

    • 在一个类中,标识两种抽象对象,并用枚举参数标识,构造方法只用重构方法区分

  • 优化

    • 用继承的方式,抽象出一个父类,两种抽象类分别继承父类,独立出新的构造方法

    • 更易区分功能,代码结构也更清晰

9.用函数对象表示策略

  • 允许程序把特殊函数的能力存储起来并传递这种能力的接口,比如Comparator

10.优先考虑静态成员

  • 嵌套类

    • 静态成员类

    • 非静态成员类

    • 匿名内部类

    • 局部类

  • 使用非静态类,会额外创建该类的实例类,增加时间和内存消耗

  • 由于引用的存在,还会在非静态没有使用后,使垃圾回收延后

四、泛型

1.不要在新代码中使用原生态类型

  • 比如List,不带< >

2.消除非受检警告

  • @SuppressWarnming(“unchecked”)

  • 尽量消除需要添加非受检的情况

  • 如果不能消除,需要添加好注解说明

3.列表优先于数组

  • Object[ ] arr = new Long[1]; arr[0] = “String Type”;//合法不抛错

List<Object> list = new ArrayList<Long>( ); list.add(“String Type”);//编译不通过

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

  • <? extends List>

五、枚举和注解

1.使用enum代替int常量

  • 易读、安全、功能更强大

2.用实例域代替序数

  • 将排序参数作为枚举类的构造方法参数传入

3.用EnumSet代替位域

4.用EnumMap代替序数索引

5.用接口模拟可伸缩的枚举

6.注解优先于命名模式

  • 可以在编译阶段提前检测发现问题,而不是到运行阶段暴露出问题,而且问题可能不容易被排查发现

7.坚持使用Override注解

  • 确保类重写了父类的方法

8.用标记接口定义类型

  • 标记接口,没有定义方法实现的接口,比如Serializable

六、方法

1.检查参数的有效性

2.必要时进行保护性拷贝

  • 方法传入的对象,只是引用,如果调用方改变了对象的值,可能对当前方法产生影响,这时候就需要拷贝一份数据供当前方法使用

3.谨慎设计方法签名

  • 遵循方法标准命名习惯

    • 驼峰式

    • 看其名知其义

    • 风格一致

  • 不要过于追求提供便利的方法

    • 接口方法定义不要过多,除非必要

  • 避免过长的参数列表

    • 尽量不超过4个,IDEA的建议是不超过7个

4.慎用重载

  • 如果参数数量相同,并且重载方法的参数有继承关系的特别需要注意

5.慎用可变参数

  • 如果没有传参数,不会在编译时暴露出来,会到运行期才会抛出错误

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

  • 有效避免调用方使用时没有判空而报错

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

七、通用程序设计

1.将局部变量的作用最小化

  • 在第一次需要使用它的地方声明,让读者不分散注意力

  • 不让后面的代码块误用

  • 让垃圾回收器在变量使用完后能及时的回收处理

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

  • 嵌套循环的时候避免使用下标出错

  • 三种情况无法使用foreach循环

    • 需要对元素进行remove操作

    • 需要更新对应位置的元素

    • 需要并行遍历多个集合

3.了解和使用类库

  • 对于可以使用类库的方法,通常稳定性、性能都要比自己再开发好

  • 减少重复造轮子的工作

4.如果要精确的数字类型结果,避免使用float和double

  • 使用BigDecimal

5.基本类型优先于装箱基本类型

  • 对象类型可能为空

  • 如果需要计算,装箱拆箱消耗时间成本

6.如果其他类型更适合,避免使用字符串

7.注意字符串连接的性能

  • 使用StringBuilder等其他动态拼接的工具类

8.通过接口引用对象

  • List<Long> list = new Vector<>();

  • 使用接口接住实现类,而不是直接使用实现类 更易与扩展修改

9.接口优先于反射机制

  • 反射机制问题

    • 丧失了编译时类型检查的功能

    • 执行反射方法需要的代码更笨拙冗长

    • 性能损耗

10.谨慎使用本地方法

  • 使用本地方法的程序不再免受内存毁坏错误的影响

  • 不再是可自由移植的,因为本地方法可能只在特定平台起作用

  • 可读性降低

八、并发

1.尽量不定义可访问的共享数据

2.避免过度同步

  • 同步会降低性能

  • 可能产生死锁

3.executor和task优先于线程

  • 有利于线程管理

4.并发工具优先于wait和notify

  • 使用java.until.concurrent中的工具类实现线程通讯

    • Executor Framework

    • Concurrent Collection 并发集合

    • Sychronizer 同步器

5.线程安全性的文档化

6.慎用参数延迟初始化

  • 尽量正常初始化

  • 对于实例域可以使用双重检查,加锁初始化

  • 对于静态域,可以使用lazy initialization holder class idiom

九、序列化

1.谨慎实现Serializable接口

  • 降低了改变这个类的灵活性,代表这个类必须一直实现序列化

  • 增加了出现BUG和安全漏洞的可能性

    • 反序列化是一个隐藏的构造器,可能绕过原先构造器的约束

    • 序列化版本号不一致,反序列化会报错

    • 随着新版本迭代,测试负担和风险也会增加

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值