第一章: 创建和销毁对象

  • 类可以通过静态工厂方法来提供客户端,而不是通过构造器

优点:

  • 自定义工厂名称,提高可读性

  • 可以工厂里搞单例

  • 控制实例类是哪种子类

总之是更加灵活,可读性更高

缺点:

  • 有可能会导致类无法子类化,因为一般搞工厂,就把构造器私有或受保护了

  • 有心的使用者会困惑,总是想看看到底是咋实例化的,单例? 多例? 创建时是否有init一些前置过程?

  • getInstance 约定俗成返回单例

  • newInstance 约定俗成多例

  • getType 一般把工厂方法写在其它类(如专门的工厂类) 可根据Type入参来从工厂拿对应单例

  • newType 一般把工厂方法写在其它类(如专门的工厂类) 可根据Type入参来从工厂拿对应多例

  • 多个构造参数,如果灵活多变,要考虑用构造器

工作中如果构造函数有多个 且 特定 的话,我一般倾向写两三个函数签名不一样构造器。但是如果在构造参数很多且多变,要写一个内部构建器,用builder模式,而不是大量重叠构造器。

优点:

  • builder 可以一次构建实例对象,而JavaBean的方式虽然比构造器可读性好点,但会使对象状态处于不一致的状态,线程安全维护成本太高了。因为总是要setter方法赋值

  • builder方式创建的实例是不可变的,无状态的。

  • builder方式在进行构造时可以加入校验参数的逻辑确保正确的通过builder构建实例

  • builder 可以在真正创建对象之前进行各种参数修改调整,甚至可以自动设置某些域

  • builder 因为是变化的,从抽离变化的角度来看,可以将builder设计成接口
    public interface Builder { public T build(); }

缺点:

  • 静态内部类builder明显代码量增加了

  • 创建实例还得搞个builder 额外的性能开销

总结: 个人感觉构造参数稳定的情况下,即未来不会参数变化频繁 && 参数比较少,还是使用重叠构造器的方式,感觉这也在好多源码中约定俗成的。如果构造类时需要多个参数,特别是当大多数参数都是可选的时候,Builder 不失为一个很好的选择。可读性和安全性都能保障。

  • 再讲单例实现
    常见的有三种 枚举 静态属性或静态块 双重检查锁

  • 不需要实例话的类
    尽量把构造器私有化,比如一些工具类,避免不必要的对象意外创建

  • 对象如果可重用,就少创建点
    但是如果因为多创建了实例而提高了程序的清晰性,间接性和功能性,也是一个好事儿

  • 消除某些过期的对象引用, 因为可能导致内存泄漏
    一种情形是 数组, 还有 缓存, 可以用WeakHashMap解决,但是必须保证所有的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有意义, LinkedHashMap 可以自定义缓存策略,LRU常用实现。监听器和回调的内存泄漏风险。

  • 关于java的 finalize方法
    其实工作中极少用到它,一般都是显示的public关闭资源的方法,让客户端去显示关闭,服务端也可以配合try catch finally 写个确保释放资源的操作(万一客户端脑残不调close方法),子类覆盖finalize方法注意super调用父类的finalize方法。可以搞个private final 内部类 里面有个回收外部类实例资源的方法,外部类私有属性保持对内部类实例的一个引用。内部 外部类现在同生共死了,当外部类死掉的时候,内部类实例也要死,死的时候把外部类资源回收了。

  • 关于覆盖equals的相关事项

  • 类的每个实例都只与他自身相等

  • 类是私有的或者是包级私有的,那么可以确定它的equals方法永远不会被调用,这时候需要覆盖equals方法,防止被意外调用

  • 如果要判断“逻辑相等“,且父类equals做不到这个功能的时候需要覆写equals

  • 枚举值类,因为是“每个值至多只存在一个对象“ 的类, 搜衣不需要覆写

  • equals含义的通用约定一定要遵守!!!没有哪个类是孤立的。

  • equels方法诀窍:

  • == 判断是否是同一个对象的引用

  • instanceof 进行类型检查

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

  • 检查参数的每个域是否一一对应的equals

  • 覆盖equals必须覆盖hashCode,相等的对象必须具有相同的hashCode值~

  • 不要将equals声明中的Object对象替换为其它的类型,应该覆盖Object的 equals方法

  • 要始终覆盖toString方法,打印的信息更加具有可读性

第四章: 类与接口

  • 要区别设计良好的模块与设计不好的模块,最重要的因素在于,这个模块对于外部的其它模块而言,是否隐藏其内部数据和其它实现细节。

  • 尽可能地使每个类或者成员不被外界访问

  • 对于包内顶层地类和接口,要么包级私有要么public, 一旦public开发者有责任永远支持它

  • 如果包级私有地顶层类只被包内的一个类用到,要考虑使它成为那个类的私有嵌套类,使可访问范围更小

  • 实例域和静态域绝不能是公有的

  • 对于final数组域可以这样控制权限
    private static final Thing[] PRIVATE_VALUES = {...};
    public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

用函数表示策略, 策略抽象成接口,如果实现接口的具体策略只使用一次,用匿名类,否则应该定义一个静态final函数, 返回类型为策略接口

public static > Comparator> comparingByValue() {
            return (Comparator> & Serializable)
                (c1, c2) -> c1.getValue().compareTo(c2.getValue());
        }
  • 静态成员类是外围类的一个成员, 常见用法是作为共有类的辅助类, 比如一个类里面有个静态枚举类;

  • 非静态成员类的每个实例都隐含着与外部类一个外部实例相关联(影响外部类的垃圾回收)。当非静态成员类的实例被创建的是时候,它和外围实例之间的关联关系也随之建立起来&不能被修改

第七章: 关于方法

  • 每当编写方法和构造器的时候,应该考虑他它的参数有哪些限制,应该把限制写到文档中,并在方法的开头出加上限制逻辑,私有方法assert断言

  • 我们要保护性的去设计程序,如果API设计的不好,客户端很容易误解,并导致不可预期的行为,所以编写面对客户的不良行为时仍能保持健壮的类,这是非常值得投入时间去做的事情。要注意是否允许调用者修改其内部的组件,

  • 关于方法签名的设计:

  • 方法名称尽量要风格一致,并选择大众认可的名称

  • 类的方法设计太多,会使类难以学习,使用,文档化,测试以及维护

    • 避免过长的参数列表,目标参数个数4个以内,太长不好记,容易乱序
  • 拆分参数子集为多个方法入参

  • 将多个频繁出现的参数序列封装成静态成员类,并考虑使用builder方法构建

  • 对于参数类型,要优先使用接口而不是类

  • 对于boolean参数,要优先使用两个元素的枚举类型,例如在一个静态工厂中newInstance(PayType.WX)

  • 易于阅读和编写

  • 易于扩展

  • 枚举常量内易于增加方法

  • 对于多个具有相同参数数目的方法来说,应该尽量避免重载方法,重载是编译期确定调用哪个重载方法,覆写是在运行时

  • 返回类型为数组或集合的方法应该返回一个零长度的数组或者集合

  • 为了正确地编写API文档,必须在每个被导出的方法,类,接口,构造器和字段声明之前增加文档注释

  • 方法的文档注释应该描述它与客户端的约定,而不是说这个方法是怎么干到的

  • 前置条件 后置条件? 副作用 以及方法的线程安全性

    @param @return @throw (if xxx then yyy) @code 
     @code 
  • 文档注释的第一句话,是该注释所属元素的概要描述

  • 要使局部变量的作用域最小化,最佳实践是在第一次使用它的地方声明它

异常

  • 只针对异常的情况才使用异常, 不能利用异常来做其它投机取巧的逻辑

  • 对于可恢复的情况且允许调用者能够进行适当的恢复使用受检异常, 其它异常使用运行时异常

  • 优先使用jdk里的标准的异常,对于这些常见的可重用的异常会降低API的学习成本

  • .更高层的实现应该捕获低层的异常, 同时抛出可以按照高层抽象进行解释的异常,叫做异常转译, 这样避免了方法抛出的异常与它所执行的任务没有明显的联系, 让人不知所措

try {
} catch(LowerLevelException e) {
    throw new HigherLevelException(...);
}
  • 底层的异常被传到高层的异常, 高层的异常提供访问方法(Throwable.getCause)来获取底层的异常

  • 不过我们应该在底层方法调用的时候尽量确保它们会执行成功,从而避免它们抛出异常,比如通过严格的检查高层传递到底层的参数。次选方案是,让高层悄悄的绕开异常, 将高层方法的调用者与底层问题隔离起来。(底层catch异常打错误日志)

  • 一般而言,失败的方法调用应该使对象保持在被调用之前的状态
    异常要打印关键信息,禁止忽略异常

还没关注我的公众号?

  • 扫文末二维码关注公众号【小强的进阶之路】可领取如下:
  • 学习资料: 1T视频教程:涵盖Javaweb前后端教学视频、机器学习/人工智能教学视频、Linux系统教程视频、雅思考试视频教程;
  • 100多本书:包含C/C++、Java、Python三门编程语言的经典必看图书、LeetCode题解大全;
  • 软件工具:几乎包括你在编程道路上的可能会用到的大部分软件;
  • 项目源码:20个JavaWeb项目源码。
    Effective Java要点笔记_第1张图片