effective java 第2章-创建和销毁对象 读书笔记
背景
去年就把这本javaer必读书——effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新。
1、考虑用静态工厂方法替代构造器
优点
- 静态工厂方法与构造器不同的第一大优势在于,他们有名称,比多个通过不同参数的构造器更具有辨识度。
- 静态工厂方法与构造器不同的第二大优势在于,不必在每次调用他们的时候都创建一个新对象。
- 静态工厂方法与构造器不同的第三大优势在于,他可以返回原返回类型的任何子类型的对象
- 服务提供者框架。
public interface Provider {
Service newService();
} public interface Service {
// Service-specific methods go here
} public class Services {
private Services() {
} // Prevents instantiation (Item 4) // Maps service names to services
private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>"; // Provider registration API
public static void registerDefaultProvider(Provider p) {
registerProvider(DEFAULT_PROVIDER_NAME, p);
} public static void registerProvider(String name, Provider p) {
providers.put(name, p);
} // Service access API
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
} public static Service newInstance(String name) {
Provider p = providers.get(name);
if (p == null)
throw new IllegalArgumentException(
"No provider registered with name: " + name);
return p.newService();
}
} public class Test {
public static void main(String[] args) {
// Providers would execute these lines
Services.registerDefaultProvider(DEFAULT_PROVIDER);
Services.registerProvider("comp", COMP_PROVIDER);
Services.registerProvider("armed", ARMED_PROVIDER); // Clients would execute these lines
Service s1 = Services.newInstance();
Service s2 = Services.newInstance("comp");
Service s3 = Services.newInstance("armed");
System.out.printf("%s, %s, %s%n", s1, s2, s3);
} private static Provider DEFAULT_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
@Override
public String toString() {
return "Default service";
}
};
}
}; private static Provider COMP_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
@Override
public String toString() {
return "Complementary service";
}
};
}
}; private static Provider ARMED_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
@Override
public String toString() {
return "Armed service";
}
};
}
};
}
- 静态工厂方法的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。
- 类型推倒
Map<String, List<String>> m = new HashMap<String, List<String>>(); public static<K,V> HashMap<K,V> newInstance(){
return new Hash<K,V>();
}
Map<String, List<String>> m = HashMap.newInstance();
缺点
- 静态工厂方法的主要缺点在于, 类如果不含公有的或者受保护的构造器,就不能被子类化。
- 第二个缺点 它们与其他的静态方法实际上没有任何区别。
- valueOf
- of
- getInstance
- newInstance
- getType
- newType
2、遇到多个构造器参数时要考虑用构建器
静态工厂和构造器共同的局限,他们都不能很好的扩展到大量的可选参数
可选参数扩展方法
- 重叠构造器-大量的构造器,客户端编写困难,难以阅读
- javaBean set-构造过程分配到多个调用中,可能造成不一致状态,很难保证线程安全
- 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;
public 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 carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}
3、用私有构造器或者枚举类型强化Singleton属性
构造器私有 public static final Elvis INSTANCE = new Elvis(); 可以通过反射调用私有构造器,为了抵御这种攻击,第二次创建该实例的时候抛出异常
构造器私有 private static final Elvis INSTANCE = new Elvis(); public static Elvis getInstance() {return INSTANCE;}
枚举单例
```java
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
4、通过私有构造器强化不可实例化的能力
- 同时也造成了不能子类化。(因为所有子类构造器都必须显式和隐式的调用了超类的构造器)
5、避免创建不必要的对象
- String s = new String("stringette"); 反例 -》String s = "stringette";
- 当心无意识的自动装箱拆箱
public class Sum {
// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
Long sum = 0L;
//long to Long is not a good idea.
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
}
6、消除过期的对象引用
- 栈内过期引用
- 缓存
- 监听器和回调函数
import java.util.Arrays;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
6.1原因分析
上述程序并没有明显的错误,但是这段程序有一个内存泄漏,随着GC活动的增加,或者内存占用的不断增加,程序性能的降低就会表现出来,严重时可导致内存泄漏,但是这种失败情况相对较少。
代码的主要问题在pop函数,下面通过这张图示展现
假设这个栈一直增长,增长后如下图所示
当进行大量的pop操作时,由于引用未进行置空,gc是不会释放的,如下图所示
从上图中看以看出,如果栈先增长,在收缩,那么从栈中弹出的对象将不会被当作垃圾回收,即使程序不再使用栈中的这些队象,他们也不会回收,因为栈中仍然保存这对象的引用,俗称过期引用,这个内存泄露很隐蔽。
1.2解决方法
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
一旦引用过期,清空这些引用,将引用置空。
6.2 缓存泄漏
内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘,对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值
6.2.1代码示例
/**
* Created by liuroy on 2017/2/25.
*/
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
public class Test {
static Map wMap = new WeakHashMap();
static Map map = new HashMap();
public static void init(){
String ref1= new String("obejct1");
String ref2 = new String("obejct2");
String ref3 = new String ("obejct3");
String ref4 = new String ("obejct4");
wMap.put(ref1, "chaheObject1");
wMap.put(ref2, "chaheObject2");
map.put(ref3, "chaheObject3");
map.put(ref4, "chaheObject4");
System.out.println("String引用ref1,ref2,ref3,ref4 消失");
}
public static void testWeakHashMap(){
System.out.println("WeakHashMap GC之前");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("WeakHashMap GC之后");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
}
public static void testHashMap(){
System.out.println("HashMap GC之前");
for (Object o : map.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("HashMap GC之后");
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
public static void main(String[] args) {
init();
testWeakHashMap();
testHashMap();
}
}
/** 结果
String引用ref1,ref2,ref3,ref4 消失
WeakHashMap GC之前
obejct2=chaheObject2
obejct1=chaheObject1
WeakHashMap GC之后
HashMap GC之前
obejct4=chaheObject4
obejct3=chaheObject3
Disconnected from the target VM, address: '127.0.0.1:51628', transport: 'socket'
HashMap GC之后
obejct4=chaheObject4
obejct3=chaheObject3
**/
上面代码和图示主演演示WeakHashMap如何自动释放缓存对象,当init函数执行完成后,局部变量字符串引用weakd1,weakd2,d1,d2都会消失,此时只有静态map中保存中对字符串对象的引用,可以看到,调用gc之后,hashmap的没有被回收,而WeakHashmap里面的缓存被回收了。
6.3监听器和回调
内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。需要确保回调立即被当作垃圾回收的最佳方法是只保存他的若引用,例如将他们保存成为WeakHashMap中的键。
7、避免使用终结方法(finalizer)
- 终结方法不能保证被及时的执行或者根本不能被执行
- 终结方法带来严重的性能损失
- 尽量提供显示的终止方法,例如try{} finally{(connect.close();)}
- 除非是作为安全为,或者是为了终止非关键的本地资源,否则不要使用终结方法
effective java 第2章-创建和销毁对象 读书笔记的更多相关文章
- 【Effective Java】第二章-创建和销毁对象——1.考虑用静态工厂方法代替构造器
静态工厂方法的优点: 可以赋予一个具有明确含义的名称 可以复用唯一实例,不必每次新建 可以返回原实例类型的子类对象 可以在返回泛型实例时更加简洁 缺点: 类如果不含有共有的或者受保护的构造器,就不能被 ...
- Effective Java(1)-创建和销毁对象
Effective Java(1)-创建和销毁对象
- Effective Java——(一)创建和销毁对象
第一条 考虑用静态工厂方法代替构造器 什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化. 比如说 public class StaticFactory { private String na ...
- Effective Java(一)—— 创建和销毁对象
在客户端(调用端)获取自身实例的方法: 公有的构造器: 类的静态工厂方法: 1. 使用静态工厂方法代替构造器 Boolean 是对基本类型 boolean 的包装类: public final cla ...
- [Effective Java]第二章 创建和销毁对象
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- 《Effective Java 2nd》第2章 创建和销毁对象
目录 第1条:考虑使用静态工厂方法代替构造器 第2条:遇到多个构造器参数时考虑用构建器 第3条:用私有构造器或者枚举类型强化Singleton属性 第4条:通过私有构造器强化不可实例化的能力 第5条: ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第一条
第二章 创建和销毁对象 第一条 使用静态工厂方法替代构造器,原因: 静态工厂方法可以有不同的名字,也就是说,构造器只能通过参数的不同来区分不同的目的,静态工厂在名字上就能表达不同的目的 静态工厂方法 ...
- [Java读书笔记] Effective Java(Third Edition) 第2章 创建和销毁对象
第 1 条:用静态工厂方法代替构造器 对于类而言,获取一个实例的方法,传统是提供一个共有的构造器. 类可以提供一个公有静态工厂方法(static factory method), 它只是一个返回类 ...
- [Effective Java读书笔记] 第二章 创建和销毁对象(1~7)
我的技术博客经常被流氓网站恶意爬取转载.请移步原文:http://www.cnblogs.com/hamhog/p/3537576.html,享受整齐的排版.有效的链接.正确的代码缩进.更好的阅读体验 ...
随机推荐
- android打电话
一.安卓中,TelephonyManager是电话管理器,它管理着电话的所有服务. 1.如图,为一个简单的打电话应用,他是通过调用系统API实现的,必须添加权限 2.先看清单文件,此处添加权限 < ...
- Canvas学习系列二:Canvas的坐标系统
上一章内容中我们对canvas元素有了一个初步的认识,在接下来的章节中我们会慢慢学习canvas中图形的绘制:但是在绘制之前我们先来看看canvas中的坐标系统,因为这样我们才能知道绘制的图形放在什么 ...
- 最近一个刚刚毕业的朋友说,他面试时候,遇到最频繁的css问题就是垂直居中,这里给出几种垂直居中方式!
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- CSS动画属性性能详细介绍
CSS动画属性会触发整个页面的重排relayout.重绘repaint.重组recomposite Paint通常是其中最花费性能的,尽可能避免使用触发paint的CSS动画属性,这也是为什么我们推荐 ...
- c/c++面试准备笔记1
在c++程序中调用被C编译器编译后的函数,为什么要加extern "C"? C++语言支持函数重载,C语言不支持函数重载.函数被C++编译后在库中的名字与C语言的不同.C++提供 ...
- 关于MATLAB处理大数据坐标文件2017529
今天我们离成功又近了一步,因为又失败了两次 第一次使用了所有特征,理由:前天的特征使用的是取单个特征测试超过85分的特征,结果出现过拟合现象. 本次使用所有特征是为了和昨天的结果作比较. 结果稍好:比 ...
- markdown基础
介绍: markdown是一种可以使用普通文本编译器编写的标记语言,通过简单的标记语法,它可以使普通文本具有一定的格式.说的简单一点,markdown其实就是一种简单的文本,与普通的文本文件(txt文 ...
- 如何编写一个带命令行参数的Python文件
看到别人执行一个带命令行参数的python文件,瞬间觉得高大上起来.牛逼起来,那么如何编写一个带命令行参数的python脚本呢?不用紧张,下面将简单易懂地让你学会如何让自己的python脚本,支持带命 ...
- El表达式取map值
map el表达取值 ${initMap['kehuList']}
- JavaScript中的排序
<script> //1. 冒泡排序 function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len ...