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>一书的 ...
随机推荐
- 学习struts2及MVC
1.MVC模式基础 1.1.MVC模式简介 MVC是一种架构型模式,它本身并不引入新的功能,只是用来指导我们改善应用程序的架构,使得应用的模型和视图相分离,从而达到更好的开发和维护效率.在MVC模式中 ...
- Linux rpm 命令参数使用
RPM是RedHat Package Manager(RedHat软件包管理工具)类似Windows里面的“添加/删除程序” rpm 执行安装包二进制包(Binary)以及源代码包(Source)两种 ...
- spring中MessageSource的配置使用方法1
Spring定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类.首先来了解一下该接口的几个重要方法: String getMessage(String code, Ob ...
- 【HDOJ6217】BBP Formula(公式)
题意:给定一个无穷项的分式,它的和等于π,问π的十六进制表示的小数点后第n位是多少 1 ≤ n ≤ 100000 思路:From https://blog.csdn.net/meopass/artic ...
- 【POJ3498】March of the Penguins(最大流,裂点)
题意:在靠近南极的某处,一些企鹅站在许多漂浮的冰块上.由于企鹅是群居动物,所以它们想要聚集到一起,在同一个冰块上.企鹅们不想把自己的身体弄湿,所以它们在冰块之间跳跃,但是它们的跳跃距离,有一个上限. ...
- 古代猪文 BZOJ 1951
古代猪文 [问题描述] “在那山的那边海的那边有一群小肥猪.他们活泼又聪明,他们调皮又灵敏.他们自由自在生活在那绿色的大草坪,他们善良勇敢相互都关心……” ——选自猪王国民歌 很久很久以前,在山的那边 ...
- cpu 性能
我们平时使用的CPU利用率方法是极具误导性的,并且一年更甚一年.那么什么是CPU利用率?是你的CPU到底有多忙,是像“% CPU”这样到处在用的指标所显示的那样吗? 在top命令里,你看到90%的CP ...
- Tomcat是怎么工作的(1) -- 开篇
这是一个系列文章的第一篇. 标题还是费了点脑子才确定的,起什么名字比较好呢.Tomcat工作原理?深入浅出Tomcat运行机制?从零开始研究Tomcat?Tomcat是怎么运行起来的?Tomcat是如 ...
- codeforces gym 100825 D Rings
这题果然就是个暴力题.... 看每个T的四个方向,有'.',或者在边界上就填1 不然就填四个方向上最小的那个数再加1 然而写wa了几发,有点蠢... #include <bits/stdc++. ...
- 推荐一款基于 AI 开发的 IDE 插件,帮助提升编码效率
最近在浏览技术社区的时候,发现了一款神奇 IDE 插件,官网称可以利用 AI 帮助程序员写代码,一下子吸引了我的好奇心.赶紧下载下来使用一番,感觉确实蛮神奇,可以火速提升编程效率. 这款插件叫做 ai ...