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>一书的 ...
随机推荐
- BZOJ3990 [SDOI2015]排序 【搜索】
题目 小A有一个1-2^N的排列A[1..2^N],他希望将A数组从小到大排序,小A可以执行的操作有N种,每种操作最多可以执行一次,对于所有的i(1<=i<=N),第i中操作为将序列从左到 ...
- java面试题之Thread类中的start()和run()方法有什么区别
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法, 区别: 当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动: start()方法才会启动新 ...
- maven项目打包 编码gbk的不可映射字符
中文系统默认gbk编码格式,你的代码是utf8格式的.所以报错 <build> <plugins> <plugin> <groupId>org.apac ...
- 开源 project
移动:http://www.csdn.net/article/2014-04-22/2819435-facebook-mobile-open-source-projects/1
- Codeforces663E. Binary Table
$n \leq 20,m \leq 100000$的01矩阵,可整行整列01翻转,问最少剩几个1. 一个暴力的做法是枚举$2^n$种行翻转然后$m$列扫一遍.但其实在行翻转情况确定的情况下我们只关心两 ...
- C语言的二次实验报告
题目一 题目二 题目三 题目四 题目五 部分源代码 11-1求矩阵的局部极大值 #include<stdio.h>int main(){ int m,n,i=0,j=0,count= ...
- Android Tools update proxy
Android Tools Android SDK在线更新镜像服务器 中国科学院开源协会镜像站地址: IPV4/IPV6: http://mirrors.opencas.cn 端口:80 IPV4/I ...
- 30深入理解C指针之---字符串和数组
一.字符串与数组 1.定义:使用字符数组表示字符串 2.特征: 1).可以直接使用字符串字面量初始化字符数组 2).声明后,赋值就只能使用字符串操作函数strcpy函数赋值 3).可以使用数组的一一赋 ...
- Perl语言入门--4--列表
1.列表也是数组的形式:(1,'a',2,3,4) 元素可以是任意类型,变量,表达式 2.空列表:() 单元素列表:(2) .与值2不同 qw(1 $a str) #qw是用空格作为分隔符,元素 ...
- Python Challenge 第六关
第六关只有一张图和一个 PayPal 的链接,右键源代码注释中写着 PayPal 是作者要赞助的,跟题目没关系,其他的提示只有注释中写的个 zip.试过下图片,改图片扩展名等等都失败了,最后乱试改了下 ...