Effective Java - [2. 创建与销毁对象]
让对象的创建与销毁在掌控中。
Item 1: 使用静态工厂方法而非使用构造函数
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
优势:
1. 方法名+参数名,相较于构造函数,能更好的描述返回对象;
BigInteger(int, int, Random)
BigInteger.probablePrime(int, int, Random)
2. 不会像构造函数那样,每次调用不一定必须返回新对象;
利用静态工厂方法可以得到类的单例对象,也可以辅助得到无法直接使用构造函数实例化的类的实例。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){
//在类外部无法用私有构造函数实例化
}
public static Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
//在类内部可以通过私有构造函数实例化
Singleton instance = new Singleton();
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
//比较实例地址
System.out.println(instance1 == instance2);
}
}
3. 可以返回子类对象
相比较于构造函数,静态工厂方法可以返回子类对象。这样的话,就可以将各个子类的具体实现隐藏起来,再通过静态工厂方法将功能暴露出来,使得方法易用而且也不用关心具体实现。理解的有些抽象,可以拿java.util.Collections举例。Collections无法实例化且方法全部为静态式,包含大量内部类且Default访问权。


隐藏子类的具体实现,并通过接口和静态工厂方法暴露功能,这样设计的一大好处就是将API集中起来易于使用和维护。
4. 同一方法调用可以返回不同子类的对象
乍看起来和第3条类似,但第3条指静态方法返回某个子类的对象,第4条指静态方法返回不同子类的对象。比如EnumSet类,有子类RegularEnumSet和JumboEnumSet。根据内部枚举类型,可以返回RegularEnumSet或者JumboEnumSet实例(也是面向接口编程的优势)。
5. 静态工厂方法所在类同静态工厂方法返回对象的类之间可以相互独立
感觉啰嗦,实际就是静态工厂方法返回对象的类可以后续再实现。这也是面向接口编程带来的好处 - 服务提供框架,具体例子可参考《谈谈服务提供框架》
缺点:
1. 一般只有静态方法的类,其构造函数会声明为private。这样该类就无法被继承。但可以通过组合模式解决。
2. 在目前的API文档中,没有对静态工厂方法做特殊处理,导致在查阅方法时很容易忽略(个人认为这个是小问题,时常想着先考虑静态工厂方法即可)
Item 2: 当构造参数过多时,应当考虑构建者模式
当构造参数过多时,如果使用构造函数创建对象,需要写很多而且代码大量重复,不易使用;如果采用JavaBean的方式,则会把创建对象的过程分拆到一个个的set方法中,存在线程安全问题(对JavaBean的一点改进)。这个时候要考虑到构建者(Builder)模式。同时,也可以通过Builder模式创建出不可变对象。
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
static class Builder {
//Required parameters
private final int servingSize;
private final int servings;
//Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
protected Builder(int servingSize, int servings){
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int calories){
this.calories = calories;
return this;
}
public Builder fat(int fat){
this.fat = fat;
return this;
}
public Builder sodium(int sodium){
this.sodium = sodium;
return this;
}
public Builder carbohydrate(int carbohydrate){
this.carbohydrate = carbohydrate;
return this;
}
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder){
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
this.fat = builder.fat;
this.sodium = builder.sodium;
this.carbohydrate = builder.carbohydrate;
}
public int getServingSize() {
return servingSize;
}
public int getServings() {
return servings;
}
public int getCalories() {
return calories;
}
public int getFat() {
return fat;
}
public int getSodium() {
return sodium;
}
public int getCarbohydrate() {
return carbohydrate;
}
}
创建对象代码示例:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build()
可以看到,想比于构造函数或者JavaBean的方式,Builder模式利用静态内部Builder类 + 链式调用创建实例。
当构造函数参数 >=4 个时,优先考虑Builder模式创建对象。
Item 3: 通过私有化构造函数或者枚举类来创建单例
创建单例的方式常用的有如下两种(这里不考虑double-lock checking避免多线程的问题),都需要将构造函数私有化。
1. 静态成员变量 + 私有构造函数
public class Elvis1 {
public static final Elvis1 Elvis1 = new Elvis1();
private Elvis1(){
System.out.println("private constructor to make instance singleton");
}
public void check(){
}
}
2. 静态工厂方法 + 私有构造方法
public class Elvis2 {
private static final Elvis2 ELVIS_2 = new Elvis2();
private Elvis2(){
System.out.println("private constructor to make instance singleton");
}
public void check(){
}
public static Elvis2 getInstance(){
return new Elvis2();
}
}
相比之下,静态工厂方法更加灵活些,可以控制方法返回的对象。
需要注意的一点,如果一个单例类需要序列化,那只implements Serializable是不够的,还需要以下2点:
(1) 字段声明 transient
(2) 提供readResolve()方法,否则在反序列化后会创建新的实例,而不是单例了。
public class Elvis implements Serializable{
private static final Elvis ELVIS = new Elvis();
private Elvis(){
System.out.println("private constructor to make instance singleton");
}
public static Elvis getInstance(){
return ELVIS;
}
private Object readResolve(){
// Return the one true Elvis and let the garbage collector take care of the Elvis impersonator.
return ELVIS;
}
}
测试代码如下:
public static void main(String[] args) {
try {
//serializable and deserializable
Elvis elvis = Elvis.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("t.tmp"));
oos.writeObject(elvis);
oos.close();
System.out.println("object serialized");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("t.tmp"));
Object deElvis = ois.readObject();
ois.close();
System.out.println("object de-serialized");
System.out.println(elvis);
System.out.println(deElvis);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
如果没有实现readResolve(),输出如下。对象地址不同,也就是反序列化创建了新对象。实现readResolve()方法保证序列化和反序列化得到对象唯一。
private constructor to make instance singleton
object serialized
object de-serialized
com.chris.item3.code.Elvis@6d6f6e28
com.chris.item3.code.Elvis@135fbaa4
3. 单元素枚举
这种方式保证实例唯一,反序列化后仍然唯一,而且最简洁。
public enum Elvis {
INSTANCE;
public void sayHello(){
System.out.println("hello, a single-element enum!");
}
}
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.sayHello();
}
Item 4: 构造函数私有化,使得类无法实例化
当类不需要有实例时(比如工具类),最好将构造函数私有化。
public class Utility {
private Utility() {
throw new AssertionError("tool class, can't be instanced");
}
public static String toUpperCase(String s){
return s.toUpperCase();
}
}
Item 5: 依赖注入而不是将依赖写死在类中
Item 9: 用 try-with-resource代替try-finally
类实现AutoCloseable后,为保证最终调用close()以释放资源,会使用try-finally的方式。
public static String firstLineOfFile2(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
但这样存在2个问题,
1. 当方法中涉及到多个资源对象时,需要finally中分别调用close()
2. 当try块和finally块中同时抛出异常时,try块中的异常将被掩盖,往往try块中的异常是我们想要的。
使用try-with-resource方式可以避免以上问题,
public static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
资源对象的close()方法将隐式进行,并且当try块中抛出异常时会抑制(suppress) close()方法抛出的异常,这样我们就得到了感兴趣的异常。
在实际业务场景中,更多的是try-catch-finally方式,即捕获感兴趣的异常并处理。
public static void copy2(String src, String des){
//try-catch-finally
InputStream in = null;
OutputStream os = null;
try {
in = new FileInputStream(src);
os = new FileOutputStream(des);
byte[] buffer = new byte[BUFFER_SIZE];
int num = 0;
while ((num = in.read(buffer)) >= 0){
os.write(buffer, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null){
in.close();
}
if (os != null){
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
用try-with-resource方式可以简化以上代码(省去finally块),更加简洁。
public static void copy(String src, String des){
//try-with-resource
try (InputStream in = new FileInputStream(src); OutputStream os = new FileOutputStream(des)) {
byte[] buffer = new byte[BUFFER_SIZE];
int num = 0;
while ((num = in.read(buffer)) >= 0){
os.write(buffer, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
}
}
总之,当类实现AutoCloseable后,为保证close()的调用,使用try-with-resource方式。
Effective Java - [2. 创建与销毁对象]的更多相关文章
- 和我一起学Effective Java之创建和销毁对象
前言 主要学习创建和销毁对象: 1.何时以及如何创建对象 2.何时以及如何避免创建对象 3.如何确保它们能够适时地销毁 4.如何管理对象销毁之前必须进行的清理动作 正文 一.用静态工厂方法代替构造器 ...
- 《Effective Java》—— 创建与销毁对象
本篇主要总结的是<Effecticve Java>中关于创建和销毁对象的内容. 比如: 何时以及如何创建对象 何时以及如何避免创建对象 如何确保及时销毁 如何管理对象销毁前的清理动作 考虑 ...
- 【读书笔记】《Effective Java》——创建和销毁对象
Item 1. 考虑用静态工厂方法替代构造器 获得一个类的实例时我们都会采取一个共有的构造器.Foo x = new Foo(): 同时我们应该掌握另一种方法就是静态工厂方法(static facto ...
- Effective java笔记2--创建于销毁对象
一.创建对象的两种方式 1.提供公有的构造器. 2.提供一个返回类实例的静态方法. 二.使用静态方法创建对象 优势: 1.静态工厂方法的一个好处是,与构造函数不同,静态工厂方法具有名字.产生的客户端代 ...
- Java进阶 创建和销毁对象
最近准备写点Javase的东西,希望可以帮助大家写出更好的代码. 1.给不可实例化的类提供私有构造器 比如:每个项目中都有很多工具类,提供了很多static类型的方法供大家使用,谁也不希望看到下面的代 ...
- Effective Java笔记一 创建和销毁对象
Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...
- effective java读书小记(一)创建和销毁对象
序言 <effective java>可谓是java学习者心中的一本绝对不能不拜读的好书,她对于目标读者(有一点编程基础和开发经验)的人来说,由浅入深,言简意赅.每一章节都分为若干的条目, ...
- Effective Java(1)-创建和销毁对象
Effective Java(1)-创建和销毁对象
- 《Effective Java》读书笔记(一)之创建和销毁对象
最近在研读<Effective Java>一书,读书不做点笔记,感觉很容易就忘掉,于是用本篇博客来记录阅读此书的笔记. 郑重声明: 由于是<Effective Java>一书的 ...
随机推荐
- [LOJ#526]「LibreOJ β Round #4」子集
[LOJ#526]「LibreOJ β Round #4」子集 试题描述 qmqmqm有一个长为 n 的数列 a1,a2,……,an,你需要选择集合{1,2,……,n}的一个子集,使得这个子集中任意两 ...
- JS判断SharePoint页面编辑状态
这篇博客主要讲使用不同的客户端方式来判断页面的编辑模式. 1.当页面处于发布状态时,可以使用下面两种方式:if(g_disableCheckoutInEditMode == true) { ale ...
- 应用defineProperty简单实现vue的双向数据绑定
双向数据绑定简易版本如何应用defineProperty的getter setter 方法 有这样HTML片段 <input type="text" id="dem ...
- class文件检查器
Class文件检查器保证装载的class文件内容有正确的内部结构,并且这些class文件互相间协调一致.Class文件检查器实现的安全目标之一就是程序的健壮性.如果某个有漏洞的编译器,或某个聪明的黑客 ...
- 使用 IntelliJ IDEA 开发 Android 应用程序时配置 Allatori 进行代码混淆
IntelliJ IDEA 提供了非常强大的 Android 开发支持,就连 Google 官方推荐的 Android Studio 其实也是 IntelliJ IDEA 的一个 Android 开发 ...
- OceanBase数据库实践入门——手动搭建OceanBase集群
前言 目前有关OceanBase功能.案例.故事的文章已经很多,对OceanBase感兴趣的朋友都想安装一个数据库试试.本文就是分享初学者如何手动搭建一个OceanBase集群.这也是学习理解Ocea ...
- 济南day4
啥也不会,做了不对,对了没分.T1 50 + 30 + 0想了想,有思路,写了,半个小时写完,算错复杂度,复杂度最差(n*m),想成了(n+m)被卡没了50分,gg.....T2自己写了个单向并查集, ...
- bzoj 1453: [Wc]Dface双面棋盘
1453: [Wc]Dface双面棋盘 Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 617 Solved: 317[Submit][Status][ ...
- [HEOI2015]定价
题目描述 在市场上有很多商品的定价类似于 999 元.4999 元.8999 元这样.它们和 1000 元.5000 元和 9000 元并没有什么本质区别,但是在心理学上会让人感觉便宜很多,因此也是商 ...
- JavaSwing仿QQ登录界面,注释完善,适合新手学习
使用说明: 这是一个java做的仿制QQ登录界面,界面仅使用一个类, JDK版本为jdk-11 素材包的名字为:素材(下载)请在项目中新建一个名字为“素材”的文件夹. 素材: https://pan. ...