Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Effective-java-读书笔记之创建和销毁对象

Effective-java-读书笔记之创建和销毁对象

原创
作者头像
特特
发布于 2022-10-08 10:38:42
发布于 2022-10-08 10:38:42
4220
举报
文章被收录于专栏:特特的专栏特特的专栏

第1条 考虑用静态工厂方法代替构造器

对于类而言, 最常用的获取实例的方法就是提供一个公有的构造器, 还有一种方法, 就是提供一个公有的静态工厂方法(static factory method), 返回类的实例.

(注意此处的静态工厂方法与设计模式中的工厂方法模式不同.)

提供静态工厂方法而不是公有构造, 这样做有几大优势:

  • 静态工厂方法有名称. 可以更确切地描述正被返回的对象. 当一个类需要多个带有相同签名的构造器时, 可以用静态工厂方法, 并且慎重地选择名称以便突出它们之间的区别.
  • 不必在每次调用它们的时候都创建一个新对象. 可以重复利用实例, 进行实例控制. 如果程序经常请求创建相同的对象, 并且创建对象的代价很高, 这项改动可以提升性能. (不可变类, 单例, 枚举).
  • 可以返回原类型的子类型对象. 适用于基于接口的框架, 可以隐藏实现类API, 也可以根据参数返回不同的子类型. 由于在Java 8之前, 接口不能有静态方法, 因此按照惯例, 接口Type的静态工厂方法被放在一个名为Types的不可实例化的类中. (Java的java.util.Collections).
  • 返回对象的类型可以根据输入的参数而变化. 比如EnumSet类的静态工厂, 根据元素的多少返回不同的子类型.
  • 返回对象的类型不需要在写这个方法的时候就存在. 服务提供者框架(Service Provider Framework, 如JDBC)的基础, 让客户端与具体实现解耦. Java 6开始提供了java.util.ServiceLoader.

静态工厂方法的缺点:

  • 类如果不含public或者protected的构造器, 就不能被子类化. (鼓励程序员: 组合优于继承).
  • 不容易被程序员发现, 因为静态工厂方法与其他的静态方法没有区别. 在API文档中没有像构造器一样明确标识出来. 可以使用一些惯用的名称来弥补这一劣势:
    • from: 类型转换方法.
    • of: 聚集方法, 参数为多个, 返回的当前类型的实例包含了它们.
    • valueOf: 类型转换方法, 返回的实例与参数具有相同的值.
    • instancegetInstance: 返回的实例通过参数来描述(并不是和参数有一样的值). 对于单例来说, 该方法没有参数, 返回唯一的实例.
    • createnewInstance: 像getInstance一样, 但newInstance能确保返回的每个实例都与其他实例不同.
    • getType: 和getInstance一样, Type表示返回的对象类型, 在工厂方法处于不同的类中的时候使用.
    • newType: 和newInstance一样, Type表示返回的对象类型, 在工厂方法处于不同的类中的时候使用.
    • type: getTypenewType的简洁替代.

第2条 遇到多个构造器参数时要考虑用Builder

静态工厂和构造器有一个共同的局限性: 它们都不能很好地扩展到大量的可选参数.

重载多个构造器方法(telescoping constructor pattern)可行, 但是当有许多参数的时候, 代码会很难写难读.

第二种替代方法是JavaBeans模式, 即一个无参数构造来创建对象, 然后调用setter方法来设置每个参数.

这种模式也有严重的缺点, 因为构造过程被分到了几个调用中, 在构造过程中JavaBean可能处于不一致的状态.

类无法通过检验构造器参数的有效性来保证一致性.

另一点是这种模式阻止了把类做成不可变的可能.

第三种方法就是Builder模式. 不直接生成想要的对象, 而是利用必要参数调用构造器(或者静态工厂)得到一个builder对象, 然后在builder对象上调用类似setter的方法, 来设置可选参数, 最后调用无参的build()方法来生成不可变的对象.

这个Builder是它构建的类的静态成员类.

Builder的setter方法返回Builder本身, 可以链式操作.

Builder模式很适合在继承中使用. 子类build()方法返回自己的类型(covariant return typing).

Builder模式的优势: 可读性增强; 可以有多个可变参数; 易于做参数检查和构造约束检查; 比JavaBeans更加安全; 灵活性: 可以利用单个builder构建多个对象, 可以自动填充某些域, 比如自增序列号.

Builder模式的不足: 为了创建对象必须先创建Builder, 在某些十分注重性能的情况下, 可能就成了问题; Builder模式较冗长, 因此只有参数很多时才使用.

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

Singleton(单例)指仅仅被实例化一次的类. 通常用来代表那些本质上唯一的系统组件.

使类成为Singleton会使得它的客户端代码测试变得困难, 因为无法给它替换模拟实现, 除非它实现了一个充当其类型的接口.

单例的实现: 私有构造方法, 类中保留一个字段实例(static, final), 用public直接公开字段或者用一个public static的getInstance()方法返回该字段.

为了使单例实现序列化(Serializable), 仅仅在声明中加上implements Serializable是不够的, 为了维护并保证单例, 必须声明所有实例域都是transient的, 并提供一个readResolve()方法, 返回单例的实例.

否则每次反序列化一个实例时, 都会创建一个新的实例.

从Java 1.5起, 可以使用枚举来实现单例: 只需要编写一个包含单个元素的枚举类型.

这种方法无偿地提供了序列化机制, 绝对防止多次实例化.

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

只包含静态方法和静态域的类名声不太好, 因为有些人会滥用它们来编写过程化的程序. 尽管如此, 它们确实也有特有的用处, 比如:

java.lang.Math, java.util.Arrays把基本类型的值或数组类型上的相关方法组织起来; java.util.Collections把实现特定接口的对象上的静态方法组织起来; 还可以利用这种类把final类上的方法组织起来, 以取代扩展该类的做法.

这种工具类(utility class)不希望被实例化, 然而在缺少显式构造器的情况下, 系统会提供默认构造器, 可能会造成这些类被无意识地实例化.

通过做成抽象类来强制该类不可被实例化, 这是行不通的, 因为可能会造成"这个类是用来被继承的"的误解, 而继承它的子类又可以被实例化.

所以只要让这个类包含一个私有的构造器, 它就不能被实例化了. 进一步地, 可以在这个私有构造器中抛出异常.

这种做法还会导致这个类不能被子类化, 因为子类构造器必须显式或隐式地调用super构造器. 在这种情况下, 子类就没有可访问的超类构造器可调用了.

第5条 优先使用依赖注入而不是直接绑定资源

对于其行为由底层资源参数化的类(比如SpellChecker, 底层资源是dictionary), 静态辅助类和单例都是不合适的实现方式.

一个简单的模式是在创建新实例的时候, 通过构造函数传入资源.

依赖注入(dependency injection): 依赖(dictionary)在spell checker被创建的时候注入(injected).

依赖注入适用于: 构造函数, 静态工厂, builder模式.

优点: 灵活, 复用, 易于测试.

一个有用的变种: 将资源工厂传入构造函数.

依赖注入的framework: Dagger, Guice, Spring.

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

一般来说, 最好能重用对象而不是每次需要的时候创建一个相同功能的新对象.

如果对象是不可变的(immutable), 它就始终可以被重用.

比如应该用:

代码语言:txt
AI代码解释
复制
String s = "bikini";

而不是:

代码语言:txt
AI代码解释
复制
String s = new String("bikini"); // Don't do this

包含相同字符串的字面常量对象是会被重用的(同一个虚拟机).

对于同时提供了静态工厂方法和构造方法的不可变类, 通常可以使用静态工厂方法而不是构造器, 以避免创建不必要的对象.

比如Boolean.valueOf(). Boolean(String)在Java 9已经deprecated了.

string.matches()做字符串正则匹配检查: 重复使用会有性能问题, 因为每次都会创建一个Pattern对象. -> 改进: 在类初始化的时候创建一个static final的Pattern对象, 然后方法重复利用.

除了重用不可变对象以外, 也可以重用那些已知不会被修改的可变对象. 比如把一个方法中需要用到的不变的数据保存成常量对象(static final), 只在初始化的时候创建一次(static 块), 这样就不用每次调用方法都重复创建.

如果该方法永远不会调用, 那也不需要初始化相关的字段, 可以通过延迟初始化(lazily initializing)把这些对象的初始化放到方法第一次被调用的时候. (但是不建议这样做, 没有性能的显著提高, 并且会使方法看起来复杂.)

如果对象是immutable的, 那么重用的安全性是很明显的.

其他有些情形则并不总是这么明显了. (适配器(adapter)模式, Map的接口keySet()方法返回同样的Set实例).

Java 1.5中加入了自动装箱(autoboxing), 会创建对象. 所以程序中优先使用基本类型而不是装箱基本类型, 要当心无意识的自动装箱.

小对象的构造器只做很少量的显式工作, 创建和回收都是很廉价的, 所以通过创建附加的对象提升程序的清晰简洁性也是好事.

通过维护自己的对象池(object pool)来避免创建对象并不是一种好的做法(代码, 内存), 除非池中的对象是非常重量级的. 正确使用的典型: 数据库连接池.

第7条 消除过期的对象引用

一个内存泄露的例子: 一个用数组实现的Stack, 依靠size标记来管理栈的深度, 但是这样从栈中弹出来的过期对象并没有被释放.

称内存泄露为"无意识的对象保持(unintentional object retention)"更为恰当.

修复方法: 一旦对象引用已经过期, 只需清空这些引用即可.

清空对象引用应该是一种例外, 而不是一种规范行为.

消除过期引用最好的方法是让包含该引用的变量结束其生命周期. 如果你是在最紧凑的作用域范围内定义变量, 这种情形就会自然发生.

一般而言, 只要类是自己管理内存, 程序员就应该警惕内存泄露问题. 一旦元素被释放掉, 则该元素中包含的任何对象引用都应该被清空.

内存泄露的另一个常见来源是缓存. 这个问题有这几种可能的解决方案:

  • 1.缓存项的生命周期由该键的外部引用决定 -> WeakHashMap;
  • 2.缓存项的生命周期是否有意义并不是很容易确定 -> 随着时间的推移或者新增项的时候删除没用的项.

内存泄露的第三个常见来源是监听器和其他回调. 如果你实现了一个API, 客户端注册了回调却没有注销, 就会积聚对象.

API端可以只保存对象的弱引用来确保回调对象生命周期结束后会被垃圾回收.

第8条 避免使用终结方法和清理器

终结方法(finalizer)通常是不可预测的, 也是很危险的, 一般情况下是不必要的. 使用终结方法会导致行为不稳定, 降低性能, 以及可移植性问题.

Java 9废弃了finalizers, 取而代之的是清理器 -> cleaners. cleaners虽然没有finalizers那么危险, 但还是不可预测, 慢, 并且通常是不必要的.

不要把finalizer当成是C++中的析构器(destructors)的对应物.

在Java中, 当一个对象变得不可到达的时候, 垃圾回收器会回收与该对象相关联的存储空间.

C++的析构器也可以用来回收其他的非内存资源, 而在Java中, 一般用try-finallytry-with-resources块来完成类似的工作.

终结方法的缺点在于不能保证会被及时地执行. 从一个对象变得不可到达开始, 到它的终结方法被执行, 所花费的时间是任意长的. JVM会延迟执行终结方法.

及时地执行终结方法正是垃圾回收算法的一个主要功能. 这种算法在不同的JVM上不同.

Java语言规范不仅不保证终结方法会被及时地执行, 而且根本就不保证它们会被执行. 所以不应该依赖于终结方法来更新重要的持久状态.

不要被System.gc()System.runFinalization()这两个方法所迷惑, 它们确实增加了终结方法被执行的机会, 但是它们并不保证终结方法一定会被执行.

如果未捕获的异常在终结过程中被抛出来, 那么这种异常可以被忽略, 而且该对象的终结过程也会终止.

使用终结方法或清洁器有一个严重的性能损失.

终结方法还有一个严重的安全问题: 使类暴露给了finalizer attacks. -> 抵御: 非final的类提供一个空的finalize方法.

如果类的对象中封装的资源(例如文件或线程)确实需要终止, 应该怎么做才能不用编写终结方法呢?

只需提供一个显式的终止方法. 并要求该类的客户端在每个实例不再有用的时候调用这个方法.

实现AutoCloseable, 提供一个显式的终止方法close().

注意, 该实例必须记录下自己是否已经被终止了, 如果被终止之后再被调用, 要抛出异常.

例子: InputStream, OutputStreamjava.sql.Connection上的close()方法; java.util.Timercancel()方法.

Image.flush()会释放实例相关资源, 但该实例仍处于可用的状态, 如果有必要会重新分配资源.

显式的终止方法通常与try-with-resources块结合使用, 以确保及时终止.

终结方法的好处, 它有两种合法用途:

  • 当显式终止方法被忘记调用时, 终结方法可以充当安全网(safety net). 但是如果终结方法发现资源还未被终止, 应该记录日志警告, 这表示客户端代码中的bug.
  • 对象的本地对等体(native peer), 垃圾回收器不会知道它, 当它的Java对等体被回收的时候, 它不会被回收. 如果本地对等体拥有必须被及时终止的资源, 那么该类就应该有一个显式的终止方法, 如前面的close(); 如果本地对等体并不拥有关键资源, 终结方法是执行这项任务最合适的工具.

第9条 优先使用try-with-resources而不是try-finally

曾经, try-finally是确保资源被关闭的最好方式, 即便是有Exception或者return也不怕.

但是要关闭多个资源, 嵌套使用的时候看起来很丑.

并且如果try和finally块中都有异常抛出, 通常第二个会掩盖了第一个.

所有的这些问题都被Java 7新添加的try-with-resources语句解决了.

要使用的话, 资源类必须实现AutoCloseable接口.

当多个异常抛出的时候, 后续异常会被suppressed, 可以通过getSuppressed()方法获取(Java 7).

try-with-resources也可以加catch语句.

总之, 推荐使用try-with-resources -> 代码更短, 更简洁, close()被隐式调用, 异常信息更有意义.

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
《Effective Java》读书笔记(一)之创建和销毁对象
最近在研读《Effective Java》一书,读书不做点笔记,感觉很容易就忘掉,于是用本篇博客来记录阅读此书的笔记。 郑重声明: 由于是《Effective Java》一书的笔记,所以大部分内容基本来自此书,还有一小部分是自己的理解。
老马的编程之旅
2022/06/22
3750
笔记《Effective Java》01: 创建和销毁对象
《Effective Java》这本书可以说是程序员必读的书籍之一。这本书讲述了一些优雅的,高效的编程技巧。对一些方法或API的调用有独到的见解,还是值得一看的。刚好最近重拾这本书,看的是第三版,顺手整理了一下笔记,用于自己归纳总结使用。建议多读一下原文。今天整理第一章节:创建和销毁对象。
有一只柴犬
2025/01/24
900
笔记《Effective Java》01: 创建和销毁对象
效率编程 之「创建和销毁对象」
但是很遗憾,在标准的 JDK 中,并没有提供类似的静态工厂方法。不过,我们可以通过在项目中引入谷歌发布的Guava,使用类似上述的静态工厂方法。当然,静态工厂方法也不是尽善尽美的,也有其缺点:
CG国斌
2019/05/26
5400
Effective Java(一)
对于类而言,为了让客户端获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。还有一种方法,也应该在每个程序员的工具箱中占有一席之地。类可以提供一个公有的静态工厂方法( static factory method ),它只是一个返回类的实例的静态方法。下面是一个来自 Boolean(基本类型 boolean 装箱类)的简单示例。这个方法将 boolean 基本类型值转换成了 Boolean 对象引用:
Remember_Ray
2020/08/03
6790
《Effective Java》 第一讲:创建和销毁对象
1.实现方式:静态成员类(静态内部类)。 public static class Builder { ... }
微风-- 轻许--
2022/04/13
2930
Effective Java 第二版 学习笔记(一) 创建和销毁对象-静态工厂
对于类而言,为了让客户端获取它自身的一个实例,最常用的方法就是提供一个公有构造器,还有一种方式是类提供一个公有的静态工厂放。它只是一个返回类的实例的静态方法。
2019/01/07
5020
《Effective Java》—— 创建与销毁对象
本篇主要总结的是《Effecticve Java》中关于创建和销毁对象的内容。 比如: 何时以及如何创建对象 何时以及如何避免创建对象 如何确保及时销毁 如何管理对象销毁前的清理动作 考虑用静态工厂方法代替构造器 使用静态工厂的优势: 有名称 不必每次调用的时候都创建一个新的对象 返回原返回类型的任何子类型对象 在创建参数化类型实例时,代码更加简洁。 使用静态工厂的缺点: 类如果不包含公有的或者受保护的构造器,就不能被子类化 与其他的静态方法实际上没有任何区别 举个例子: public class A
用户1154259
2018/01/17
8220
Effective Java 第二版 学习笔记(3) 创建和销毁对象-私有构造器、避免创建不必要的对象
有时候,可能需要编写只包含静态方法和静态域的类。这样的工具类不希望被实例化,实例对它没有任何意义。然而,在缺少显式构造器的情况下,编译器会自动提供一个公有的、无参的缺省构造器。
2019/01/07
5320
《Effective Java 》系列一
编写实例受控类有几个原因。实例受控使得类可以确保他是一个Singleton或者是不可实例化的。他还使得不可变类可以确保不会存在两个相等的实例。
高广超
2018/12/12
8630
Effective.Java 读书笔记(1)静态工厂和构造方法
用户在获得类它本身的实例的时候,通常会想到的就是使用public的构造器,但是一个类可以提供一个public的工厂方法。 这种工厂方法简化了返回该类实例的静态方法
Mezereon
2018/09/13
3820
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
本文基于Effective Java中创建和销毁对象的章节汇总出8个相关的好习惯(文末附案例地址)
菜菜的后端私房菜
2024/07/24
1271
3分钟快速阅读-《Effective Java》(一)
简介 Effective Java这本书针对的是有经验的程序员,本书共计78个条目,目的是告诉大家如何进行更加有效的编程 1.考虑用静态工厂方法代替构造器 静态工厂方法的优点 1.1 静态工厂方法有对应方法名称,构造器则没有.所以使用静态工厂方法能够更加直接的表达想要实例化的对象 1.2 静态工厂方法不用每次都创建出一个新的对象 1.3 静态工厂方法可以返回父类对象或者接口对象 1.5 静态工厂方法可以使用泛型来作为返回的参数 静态工厂方法的缺点 1.1 如果不含有公有的或者受保护的构造
cwl_java
2019/10/26
4010
Effective Java tips
所有对 Elvis.getInstance 的调用都返回相同的对象引用,并且不会创建其他的 Elvis 实例(与前面提到的警告相同)
只喝牛奶的杀手
2023/04/07
3020
Effective Java tips
effective Java 创建和销毁对象篇
小伙伴们好呀,我是 小羊 ,今天来和大家分享下 《Effective Java》这本书的 第2章 —— 创建和销毁对象 。
Java4ye
2024/02/14
3100
effective Java 创建和销毁对象篇
Effective Java要点笔记
工作中如果构造函数有多个 且 特定 的话,我一般倾向写两三个函数签名不一样构造器。但是如果在构造参数很多且多变,要写一个内部构建器,用builder模式,而不是大量重叠构造器。
程序员小强
2020/04/14
4640
Effective Java要点笔记
Effective Java - 静态方法与构造器
传统来讲,为了使客户端能够获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。像下面这样
cxuan
2019/07/10
9100
为什么android API 中有很多对象的创建都是使用new关键字
首先,谢邀。 其次,是怎么找到我知乎账号的,我隐藏的这么深(脸红了) 最后,加入了自己的总结概括,让然也可以当成读书笔记来看。
小鄧子
2018/08/20
7600
Effective Java笔记(不含反序列化、并发、注解和枚举)
最近把Effective Java复习了一遍,其中有比较多的java最佳实践可以在平时编程中用到。反序列化、并发、注解和枚举这四章没看,并发这本书里讲的比较简单,推荐java并发编程实战这本书。注解和枚举与 Thinking in java中讲的差不多。反序列化用的不多就没看了,以后用到了再复习一下。放上一些书籍笔记的源码,肯定会有纰漏大家可以选择性看看Thinking in Java、算法导论、Effective Java笔记源码 1.创建和销毁对象 1.考虑用静态工厂方法代替构造器: 优势:
何时夕
2018/05/02
9750
Effective Java通俗理解(上)
  这篇博客是Java经典书籍《Effective Java(第二版)》的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约会持续1个月左右。 第1条:考虑用静态工厂方法代替构造器   通常情况下我们会利用类的构造器对其进行实例化,这似乎毫无疑问。但“静态工厂方法”也需要引起我们的高度注意。   什么是“静态工厂方法”?这不同于设计模式中的工厂方法,我们可以理解它为“在一个类中用一个静态方法来返回这个类的实例”,例如: public st
用户1148394
2018/01/12
1.5K0
Effective Java通俗理解(上)
Effective Java(第三版)-学习笔记
这本书的目的是帮助编写清晰正确,可用的,健壮性灵活性高和可维护的代码,而非针对高性能。主要从对象,类,接口,泛型,枚举,流,并发和序列化等方面介绍。
Monica2333
2020/06/22
1.2K0
推荐阅读
相关推荐
《Effective Java》读书笔记(一)之创建和销毁对象
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档