当前位置: 首页 > news >正文

Effective Java(第三版) _ 创建和销毁对象

一、前言

Effective Java》 这本书,在刚从事 Java 开发的时候就被老师推荐阅读过,当时囫囵吞枣的看了一部分,不是特别的理解,也就搁置了,现在已经更新到第三版了,简单翻阅了一下,发现有些条例和现实开发中的场景呼应上了,开始意识到,确实应该好好的认真读一遍。应该会对自己有很大的帮助。通过这个专题,记录我的一些心得体会。

二、创建和销毁对象

本章围绕以下三个问题展开

  1. 何时以及如何创建对象?
  2. 何时以及如何避免创建不必要的对象?
  3. 如何保证对象能够在合适的时间安全的销毁?

第一条:用静态工厂方法代替构造器

构造器方式就是我们常用的 new 一个对象,注意这里的静态工厂方法,不同予设计模式中的静态工厂,其实指的是类中提供一个返回当前实例的静态方法。参考 Boolean 包装类的源码

public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);
}

这么做,既有优势,也有劣势

优势:

  • 静态方法具有名称,我们可以通过名称很清楚的知道我们想要返回的实例类型。

书中解释有点啰嗦,无非就是通过具体方法名可以提高代码的自释性,举得反例我都不是很认同,通过构造函数重载本身就是一个被允许的操作,如果容易混淆我们应该添加注释,谨慎使用。

  • 静态方法可以实现缓存,避免每次创建新的对象。

这个比较好理解,我们通过构造函数每次都要 new 一下,就是创建新的对象,如果通过静态方法,那这个取对象的操作就被隔离开了,不局限于新创建一个,可以通过各种手动只要能返回预期的对象实例即可。

  • 可以返回原返回类型的任何子类型的对象。

这样可以时返回类型更加灵活,同时可以隐藏具体的实现类。

  • 可以通过传入不同的参数,返回不同对象实现类的实例。

其实就是对上一点的补充说明。

  • 静态方法返回的对象所属的类,在编写方法的时候可以不存在。

基于上一点,通过不同的参数返回不同的类型的实例,这个类型甚至可以是一个 **classz,**静态方法内通过反射在调用的时候返回指定类型的实例对象,这种方式可以极大的提供灵活性。

劣势:

  • 类如果不含有公有的或者受保护的构造器,就不能被子类化。

这个也不能算是缺点吧,构造器私有化本来就不允许被继承。

  • 程序员很难发现一个类是不是有静态方法,而忽略使用。

相比于构造方法,本能的 new 一个对象,我们潜意识会认为这是合理合规的,但是不会第一反应去找一个类是不是提供了静态方法,因为这不是强制要求的。

总结,静态工厂方法和构造器各有用处,但是静态工厂方法经常更加合适,所以要纠正一下固有想法,不要第一反应就是提供或者使用构造器,而忽略了静态工厂。

第二条、遇到多个构造器参数时要考虑使用构建器

在平时开发的时候经常会碰到这样的场景,当碰到一个大类(字段很多的类)在创建的时候,如果通过构造器去创建,我们需要传入一大串的参数 new User(name, age, ....),有些不是必须的也要设置默认值,如果不想在创建的时候传入,就需要创建一个特定参数列表的构造器,最后就导致类构造器混乱,失去控制,使用起来也非常繁琐。其实就是书中提到的 **重叠构造器 **的方式。

但是往往我们的常规做法是通过无参构造器创建一个对象,new User(),然后通过 setter 方法来赋值,这种方式更容易被接受,也很清晰,但是缺点就是每个字段都要去设置,可能出现缺漏的情况。这其实就是书中说的 JavaBeans 模式,这种方式还有两个严重不足:

  1. 创建实例对象过程,被分到了多个 setter 调用中, 这样在整个构造过程中 JavaBean 可能处于不一致的状态,试图使用这种不一致状态的对象将会导致异常,这种错误不容易排查。
  2. 通过暴露 setter方法破坏了类的封装性,使得想把类做成不可变的可能性不复存在。

推荐做法使用 构建器,先看一下示例代码:

 Demo user = Demo.builder().username("zhangsan").age(10).build();

相信大家都看过类似的写法,本质上是在 User 类中定义了一个静态 Builder 类,通过重叠的方式提供设置每一个属性值的方法,最后通过 build 完成对象的构建。

public static class DemoBuilder {private String username;private Integer age;DemoBuilder() {}public DemoBuilder username(final String username) {this.username = username;return this;}public DemoBuilder age(final Integer age) {this.age = age;return this;}public Demo build() {return new Demo(this.username, this.age);}public String toString() {return "Demo.DemoBuilder(username=" + this.username + ", age=" + this.age + ")";}}

再结合之前的静态方法方式提供给外部一个 builder 方法, 类似下面示例

public static DemoBuilder builder() {return new DemoBuilder();
}

这么做从一定程度上保证了对象的封装性,也保留了类似于 JavaBeans 模式 的可读性。

但是有个不足的地方就是,我们创建对象的时候,必须先创建他的构建器,这其实也是一部分开销,而且就代码量上来说比重叠构造器模式更加冗长。好在现在基本都有现成的插件可以一键生成,或者使用 lombok插件,通过 @Builder 注解自动为我们生成即可。

当有多个参数,超过 4 个的时候,使用构建器模式是个不错的选择。

第三条、用私有构造器或者枚举类型强化 Singleton 属性

这里首先理解一下 Singleton , 指本质上唯一的实例对象,比如一些配置类。这里可以看一下设计模式里的单例模式。

通常实现有两种方式,一种直接通过提供公共的静态成员方式,通过 public final 定义一个 final 域,我们知道 final修饰后,即表示指定的为一个地址,初始化后不可变。再将构造器私有化,这样从某种程度上来说,在初始化后可以保证全局唯一,见示例代码

public class Demo {public static final Demo demo = new Demo();private Demo () { ... }...
}

但是通过反射可以破坏这种方式的唯一性。

第二种方式

public class Demo {private static final Demo demo = new Demo();private Demo () { ... }public static Demo getInstance() { return demo; }...
}

这种其实同第一种类似,只是相比较第一种更加灵活,也更容易被理解。

接下来看一下使用 **枚举 **方式

public enum Singleton {INSTANCE;// 可以在这里定义其他方法和字段public void someMethod() {// 方法的实现System.out.println("Executing some method.");}
}
  1. 线程安全:枚举类型是线程安全的,Java 会自动保证只会创建一个实例。
  2. 防止反序列化:枚举类型的反序列化会确保返回唯一的实例,从而避免了多次创建实例的问题。
  3. 简洁性:实现简单,易于理解,符合 Java 的设计理念。

第四条、通过私有构造器强化不可变实例化的能力

书中提到像一些工具类,我们不希望被实例化,一般工具类中的方法都是静态方法,通过类名直接调用即可,但是有些错误使用案例,因为默认会提供一个无参的构造器,所以有人会无意识的 new 这个工具类对象,然后通过对象去调用,这其实是错误的。但是又无法避免,书中还提到,有些人会** 通过将类定义成抽象类来强制不可实例化**,这种做法也是行不通的,而且会误导用户,以为故意设计成抽象类让用户去继承,然后实例化子类,最后反而适得其反。

通常做法,提供一个私有的构造器,并且抛出异常,提醒调用者。为了避免使用者无法理解最好加上注释。

public class PasswordEncoderUtil {// 工具类不允许实例化private PasswordEncoderUtil() {throw new IllegalStateException("Utility class");}...
}

第五条、优先考虑依赖注入来引用资源

这个得益于 Spring的生态发展,大家基本上也都这干的。

第六条、避免创建不必要的对象

道理大家都知道,但是问题是不知道有哪些地方可以避免。这里整理一下书里提到的。

  • 对于一些不可变类,一定一定不要通过 **new** 的方式创建

极端反例 String s = new String("hahaha"); , 构造器参数 “hahaha”本身就是一个 String 实例, 如果这个方法出现在循环里,会频繁的创建出成千上万个 String实例,正确做法应该是 String s = "hahaha";,这样就只会创建一个实例。

  • 有提供静态工厂方法,使用静态工厂方法

这个比较好理解,使用构造器,必定会创建新的对象,但是使用静态工厂方法,则不一定。

  • 对于创建成本很高的对象,建议先缓存,再重用。

书中例子,使用 正则匹配的时候,将 Pattern 对象通过 final 域缓存下来,重用。

  • 优先使用基本类型进行计算,当心无意识的自动装箱行为导致对象的创建

作者指出,这里并不是要告诉大家创建对象的代价非常大,我们应该尽可能的去避免创建对象。适当的选择更为合适的做法,小对象的创建和销毁对于虚拟机来说并没有太大压力,一味的追求复用反而会使代码更加复杂。

第七条、消除过期的对象引用

我们都知道 Java的特点之一就是垃圾回收机制,我们不需要过多的关注对象的回收,通常开发中也是这样的。这里作者说,清空对象引用应该是一种例外,而不是一种规范行为。并给出了几个可能出现内存问题的场景。

  1. 自己管理内存的类,要警惕内存泄漏的问题。
  2. 使用本地缓存的场景下,但是这取决于缓存项的生命周期是否有意义,一些长期不用的缓存应该被定时清除掉。
  3. 监听器和其他回调。

第八条、避免使用终结方法和清除方法

finalizercleaner 通常不可预测,比较危险,一般情况下是不必要的。我写业务从来没用过。

第九条、try-with-resources 优先于 try-finally

这个现在基本上也是这么做的,只有在一些很久以前的博客中还能看到通过在 finally 里面去关闭资源。

http://www.lryc.cn/news/473359.html

相关文章:

  • 你的EA无法运行的几种常见原因
  • 通过自定义指令实现图片懒加载
  • QT项目-仿QQ聊天(带宠物系统)
  • 前端算法题:3216. 交换后字典序最小的字符串(力扣每日一题)
  • 29.1 时序监控和日志监控的对比,分析日志监控的核心诉求
  • git仓库分支
  • 多模态机器学习在精准健康中的应用--九五小庞
  • 提升网站速度与性能优化的有效策略与实践
  • MySQL索引从基础到原理,看这一篇就够了
  • 普通高考预报名上传蓝底证件照手机自拍方法详解
  • Webserver(2.3)exec函数族
  • LeetCode Hot100 - 子串篇
  • 【Android】Convenient ADB Commands
  • elementUI 时间控件控制时间选择
  • 什么是x86架构,什么是arm架构
  • c语言水仙花,超简单讲解
  • Flutter 13 网络层框架架构设计,支持dio等框架。
  • Python小白学习教程从入门到入坑------第二十课 闭包修饰器(语法基础)
  • Vue+element-ui实现网页右侧快捷导航栏 Vue实现全局右侧快捷菜单功能组件
  • 如何配置,npm install 是从本地安装依赖
  • Python画图3个小案例之“一起看流星雨”、“爱心跳动”、“烟花绚丽”
  • Knife4j配置 ▎使用 ▎教程 ▎实例
  • 电子电气架构 --- 车载芯片现状
  • Unity 二次元三渲二
  • echart实现地图数据可视化
  • 网关三问:为什么微服务需要网关?什么是微服务网关?网关怎么选型?
  • Mybatis-plus解决兼容oracle批量插入
  • Kaggle竞赛——灾难推文分类(Disaster Tweets)
  • SC2601音频编解码器可pin to pin兼容ES8311
  • 通用AT指令