当前位置: 首页 > 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 里面去关闭资源。

相关文章:

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

一、前言 《Effective Java》 这本书,在刚从事 Java 开发的时候就被老师推荐阅读过,当时囫囵吞枣的看了一部分,不是特别的理解,也就搁置了,现在已经更新到第三版了,简单翻阅了一下,发现有些条例…...

你的EA无法运行的几种常见原因

大多数情况下,EA正常运行是指其能够自动开仓交易,毕竟EA的主要目的是根据某种策略自动进行交易。如果从网上下载或其他途径获得的EA在开始时能够正常交易,但在修改参数后却不再交易,可能的问题是什么呢?下面列举了一些…...

通过自定义指令实现图片懒加载

前提:使用到了VueUse插件。 先创建自定义插件文件夹 // 定义懒加载插件 import { useIntersectionObserver } from vueuse/core // 这个是VueUse里的一个方法export const lazyPlugin {install(app) {// 懒加载指令逻辑 定义全局指令app.directive(img-lazy, {mo…...

QT项目-仿QQ聊天(带宠物系统)

目录 一,项目介绍 二,开发环境 三,涉及技术 四,项目效果示例图 1,登录界面 2,主界面 3,聊天界面 4,功能界面 5,宠物界面 一,项目介绍 这是一个基于u…...

前端算法题:3216. 交换后字典序最小的字符串(力扣每日一题)

今日题目为:3216. 交换后字典序最小的字符串 题目详情: 给你一个仅由数字组成的字符串 s,在最多交换一次 相邻 且具有相同 奇偶性 的数字后,返回可以得到的字典序最小的字符串。 如果两个数字都是奇数或都是偶数,则…...

29.1 时序监控和日志监控的对比,分析日志监控的核心诉求

本节重点介绍 : 监控系统分类时序监控和日志监控的对比轻量日志监控系统的诉求 监控系统分类 监控系统按照原理和作用大致可以分为三类 日志类(Log)调用链类(Tracing)度量类(Metrics) 日志类&#xff08…...

git仓库分支

操作 切换分支 git checkout 1.2.5 git checkout 1.3.0 使用命令切换分支之后,代码内容加载过后也是切换好的...

多模态机器学习在精准健康中的应用--九五小庞

这篇综述文章探讨了将多模态数据融合应用于医疗诊断和预后预测的最新研究进展。 本文作者们确定了三个主要的研究问题:多模态数据融合在健康领域的文献特征是什么?用于分析多模态健康数据的不同分析技术、方法和策略是什么?不同类型的异构数…...

提升网站速度与性能优化的有效策略与实践

内容概要 在数字化快速发展的今天,网站速度与性能优化显得尤为重要,它直接影响用户的浏览体验。用户在访问网站时,往往希望能够迅速获取信息,若加载时间过长,轻易可能导致他们转向其他更为流畅的网站。因此&#xff0…...

MySQL索引从基础到原理,看这一篇就够了

https://developer.aliyun.com/article/841106 https://zhuanlan.zhihu.com/p/29118331 索引创建使用总结 因为索引对于改善查询性能的作用是巨大的,所以我们的目标是尽量使用索引。 1. 索引的创建 • 1、在用于 where 判断 order 排序和 join 的(on)字段上创…...

普通高考预报名上传蓝底证件照手机自拍方法详解

普通高考预报名过程中,上传一张合规的蓝底证件照是必不可少的一步。本文将详细介绍如何使用手机自拍并使用工具来制作符合要求的蓝底证件照。注意,目前仅有广东等个别省份允许特定类型考生使用自拍照上传(例如普高预报名阶段、学考报名&#…...

Webserver(2.3)exec函数族

目录 exec函数族介绍execl函数execlp函数 exec函数族介绍 c语言中没有重载,因为不允许同名函数 一系列功能相似的函数称为函数族 exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容。 程序在运行的时候,fork一…...

LeetCode Hot100 - 子串篇

前言 挑战一个月刷完力扣的hot100,记录一下每题的思路~ 这次是子串相关的题目 (1)560. 和为 K 的子数组 ①暴力枚举,使用一个变量sum记录以l开头r结尾的情况 class Solution {public int subarraySum(int[] nums, int k) {int r…...

【Android】Convenient ADB Commands

Install adb install -r <path>Uninstall adb uninstall <pkg>Start adb shell am start -n <pkg>/.SplashActivityStop adb shell am force-stop <pkg>Reset adb shell pm clear <pkg>Reboot adb rebootShutdown adb reboot -p...

elementUI 时间控件控制时间选择

选择时间大于当前月或小于2024年一月禁止选择 <el-form-item label"成交月份:" label-width"105px" ><div class"block"><el-date-pickerv-model"formData.deal_month"type"month":picker-options"pick…...

什么是x86架构,什么是arm架构

什么是 x86 架构&#xff1f; x86 架构是一种经典的指令集架构&#xff08;ISA&#xff09;&#xff0c;最早由英特尔在 1978 年推出&#xff0c;主要用于 PC、服务器等领域。 它是一种复杂指令集计算&#xff08;CISC&#xff09;架构&#xff0c;支持大量的复杂指令和操作&…...

c语言水仙花,超简单讲解

效果 3或者大于3位数 每一位的位数次方相加等于 自身 例如 153 13 53 33 结果为112527 153 1634&#xff1a; 计算过程&#xff1a; 14643444112968125616341^4 6^4 3^4 4^4 1 1296 81 256 16341464344411296812561634 过程 求得单独将所有位数的正数拎出来…...

Flutter 13 网络层框架架构设计,支持dio等框架。

在移动APP开发过程中&#xff0c;进行数据交互时&#xff0c;大多数情况下必须通过网络请求来实现。客户端与服务端常用的数据交互是通过HTTP请求完成。面对繁琐业务网络层&#xff0c;我们该如何通过网络层架构设计来有效解决这些问题&#xff0c;这便是网络层框架架构设计的初…...

Python小白学习教程从入门到入坑------第二十课 闭包修饰器(语法基础)

一、递归函数 1.1 基本信息 递归函数是指一个函数在其定义中直接或间接地调用了自身 递归在解决许多问题&#xff08;如树的遍历、图的搜索、数学中的分治算法等&#xff09;时非常有用 在Python中&#xff0c;递归函数可以通过简单的语法来实现 然而&#xff0c;使用递归…...

Vue+element-ui实现网页右侧快捷导航栏 Vue实现全局右侧快捷菜单功能组件

Vue+element-ui实现网页右侧快捷导航栏 Vue实现全局右侧快捷菜单功能组件 可视区域没超过当前屏幕高度时候只显示三个菜单效果 可视区域超过当前屏幕高度时,显示可回到顶部菜单的,当然这个菜单显示条件可以自定义,根据需求设置 然后将这个整体功能创建为一个全局组件 代…...

如何配置,npm install 是从本地安装依赖

在 Node.js 中&#xff0c;要使npm install从本地安装依赖&#xff0c;可以按照以下步骤进行配置&#xff1a; 一、准备本地依赖包 确保你有本地的依赖包。这个依赖包可以是一个包含package.json文件的文件夹&#xff0c;或者是一个已经打包好的.tgz文件。 二、使用相对路径…...

Python画图3个小案例之“一起看流星雨”、“爱心跳动”、“烟花绚丽”

源码如下&#xff1a; import turtle # 导入turtle库&#xff0c;用于图形绘制 import random # 导入random库&#xff0c;生成随机数 import math # 导入math库&#xff0c;进行数学计算turtle.setup(1.0, 1.0) # 设置窗口大小为屏幕大小 turtle.title("流星雨动画&…...

Knife4j配置 ▎使用 ▎教程 ▎实例

knife4j简介 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试.参数和格式都…...

电子电气架构 --- 车载芯片现状

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…...

Unity 二次元三渲二

三渲二 注意&#xff1a;Unity必须是2022.3LTS及以上和URP项目&#xff01;&#xff01;&#xff01; 下载三渲二插件 【如何将原神的角色导入Unity】全网最细致教程&#xff0c;全程干货。不使用任何收费插件&#xff0c;使用Spring Bone对头发和衣服进行物理模拟。_原神 步…...

echart实现地图数据可视化

文章目录 [TOC](文章目录) 前言一、基本地图展示2.数据可视化 总结 前言 最近工作安排使用echarts来制作图形报表&#xff0c;记录一下我的步骤&#xff0c;需求呈现一个地图&#xff0c;地图显示标签&#xff0c;根据业务指标值给地图不同省市填充不同颜色&#xff0c;鼠标放…...

网关三问:为什么微服务需要网关?什么是微服务网关?网关怎么选型?

文章整体介绍 本文旨在解答关于微服务网关的三个核心问题&#xff1a; 1&#xff09;为什么需要网关&#xff1f;也即在何种场景下应采用微服务网关以优化系统架构&#xff1b; 2&#xff09;什么是微服务网关&#xff1f;主要讲构成微服务网关的关键能力&#xff0c;包括但…...

Mybatis-plus解决兼容oracle批量插入

本博客借鉴网上很多大佬的答案&#xff0c;东拼西凑&#xff0c;最终在项目中完成批量插入&#xff0c;仅供参考~~~ 1. 自定义SQL注入器 新建一个名为EasySqlInjector的类&#xff0c;继承DefaultSqlInjector。 public class EasySqlInjector extends DefaultSqlInjector {O…...

Kaggle竞赛——灾难推文分类(Disaster Tweets)

目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测…...

SC2601音频编解码器可pin to pin兼容ES8311

SC2601 是一款低功耗单声道音频编解码器&#xff0c;具有全差分输出&#xff0c;支持在全差分配置下可编程模拟输入。可pin to pin兼容ES8311。 录音路径包含一个全差分输入&#xff0c;低噪声可编程增益放大器和自动增益控制&#xff08;ALC&#xff09;。在录音过程中,通过内…...

本地升级wordpress/关键词你们懂的

HTTP 的 Keep-Alive&#xff0c;是由应用层&#xff08;用户态&#xff09; 实现的&#xff0c;称为 HTTP 长连接&#xff1b; TCP 的 Keepalive&#xff0c;是由 TCP 层&#xff08;内核态&#xff09; 实现的&#xff0c;称为 TCP 保活机制 HTTP 的 Keep-Alive HTTP 是基于…...

怎么修改公司网站图片/免费网站软件推荐

如果直接添加调用的话会报 xxxis not defined at HTMLAnchorElement.onclick HTMLAnchorElement:接口表示超链接元素&#xff0c;并提供一些特别的属性和方法&#xff08;除了那些继承自普通 HTMLElement对象接口的之外&#xff09;以用于操作这些元素的布局和显示。 这时HTMLA…...

东莞网站建设定制/seo有哪些作用

深入理解Class---常量池一、概念1、jvm生命周期启动&#xff1a;当启动一个java程序时&#xff0c;一个jvm实例就诞生了&#xff0c;任何一个拥有main方法的class都可以作为jvm实例运行的起点。运行&#xff1a;main()函数作为程序初始线程起点&#xff0c;其它线程由该线程启动…...

网络运维工程师薪酬/徐州seo代理计费

1、单片机向上位机LabVIEW VISA串口不断发送数据&#xff0c;只要存在帧间隔&#xff0c;VISA就会每次只上来一帧&#xff0c;之后只要调用解析一帧的子VI程序就可以。关键在于如何保存解析过的每一帧数据。簇数组/数组簇&#xff1f;...

网站劫持必须做系统嘛/大数据精准客户

python将print的内容输出到txt文件 - 子唯 - 博客园想起昨天天调试某程序时&#xff0c;打印出的内容太多不方便看&#xff0c;在网上找了个法子将其输入到文件。 import sysclass Logger(object): def __init__(self, fileN&https://www.cnblogs.com/henry2019/p/14313948…...

做c2b的网站/百度搜索排名与点击有关吗

​ 如同写作&#xff0c;写代码也是有代码风格(code style)。不过代码风格和文风不一样&#xff0c;一个人的文风可以随心所欲&#xff0c;代码风格最好是“千篇一律”。它的核心就是&#xff1a;便于阅读。 如果你的代码风格也要随心所欲&#xff0c;那么多年后&#xff0c;你…...