1.创建和销毁对象_EJ
在这里记录《Effective Java》学习笔记。该书介绍了java编程中70多种极具实用价值的经验规则,揭示了该做什么,不该做什么才能产生清晰、健壮和高效的代码。
第1条: 考虑用静态工厂方法代替构造器
对于类而言,为了让客户端获得它自身的实例,最常用的方法就是提供一个公有的构造器。还有一种方法就是提供一个静态工厂方法。下面是Boolean的一个简单示例。
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
提供静态工厂方法而不是公有构造器有几大有点:
1.静态工厂方法有名称。
2.不必在每次调用它们的时候都创建一个新的对象。
3.可以返回原返回类型的任何子类型的对象。一个服务提供者框架的例子:
public interface Provider {
Service newService();
}
public interface Service {
//Service-specific methods go here
}
public class Services {
private Services(){
//prevents instantiation
}
private static final String DEFAULT_PROVIDER_NAME = "<def>";
private static final Map<String, Provider> providers = new ConcurrentHashMap<>();
//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();
}
}
4.在创建参数化类型实例的时候,它们使代码变得更加简洁。一个hashMap的例子:
Map<String, List<String>> m = new HashMap<String, List<String>>(); //这会显得很繁琐
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
Map<String, List<String>> m = HashMap.newInstance();
但是实际上从JDK7(包括JDK7)之后的集合类可以用以下简洁的代码代替:
Map<String, List<String>> m = new HashMap<>();
静态工厂方法的缺点:
1.类如果不含有公有的或受保护的构造器,就不能被子类化。
2.它们与其它静态方法没有实质区别,如果要想查明如何实例化一个类,这是非常困难的。
第2条: 遇到多个构造器参数时要用构建器
静态工厂方法和构造器有个共同的缺点,不能很好地扩展到大量可选参数。考虑用一个类表示食品包装外的成分标签,这些标签的有些域是必需的,比如每份含量,有些是可选的,比如脂肪含量、胆固醇,钠等。采用重叠构造器模式可行,但是当有许多参数的时候,客户端代码就会很难写。采用JavaBean模式,然后用setter方法设置值也可行,但在构造过程中可能使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; public static class Builder{
private final int servingSize;
private final int servings;
private int calories=0;
private int fat=0;
private int sodium=0;
private int carbohydrate=0;
public Builder(int servingSize, int servings){
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val){
this.calories = val;
return this;
}
public Builder fat(int val){
this.fat = val;
return this;
}
public Builder sodium(int val){
this.sodium = val;
return this;
}
public Builder carbohydrate(int val){
this.carbohydrate = 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 String toString(){
return "NutritionFacts create success";
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 3).
calories(100).fat(33).sodium(99).carbohydrate(88).build();
System.out.println(cocaCola.toString());
} }
可见builder方式非常灵活,简而言之,如果静态工厂方法和构造器中有多个参数,builder模式就是一个不错的选择。
第3条: 用私有构造器或枚举类型强化singleton属性
实现singleton时,可以把构造器设为私有,并导出公有的静态成员。
public class Instance {
public static final Instance instance = new Instance();
private Instance(){}
}
也可以用静态工厂类,
//singleton with private final field
public class Instance2 implements Serializable{
private static final Instance2 instance = new Instance2();
private Instance2(){}
public static Instance2 getInstance(){
return instance;
}
}
对于上面的两种方法,都会只返回同一个对象引用。公有域方法的好处是能清楚地知道这是一个Singleton类。工厂方法的优势之一在于它提供了灵活性,我们可以改变该类是否是Singleton的想法,第二个优势与泛型有关(见泛型模块,第27条)。
为了使Singleton类变成可序列化的,仅仅在申明中加上implements Serializable是不够的。为了维护并保证Singleton,必须提供一个readResolve方法,否则每次反序列化一个序列化实例时,都会创建一个新的实例。
//singleton with private final field
public class Instance2 implements Serializable{
private static final Instance2 instance = new Instance2();
private Instance2(){}
public static Instance2 getInstance(){
return instance;
}
private Object readResolve(){
return instance;
}
}
public class InstanceSerializable { public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Instance2 instance = Instance2.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\objFile.obj"));
out.writeObject(instance);
out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\objFile.obj"));
Instance2 instance11 = (Instance2) in.readObject();
in = new ObjectInputStream(new FileInputStream("D:\\objFile.obj"));
Instance2 instance22 = (Instance2) in.readObject();
in.close();
System.out.println(instance11 == instance22); //true
} }
实现Singleton还有第三种方法-枚举类型。
public enum EnumObj {
INSTANCE;
}
这种方法与公有域方法类似,但它更加简洁,无偿地提供了序列化机制,绝对防止多次序列化。
第4条:通过私有构造器强化不可实例化的能力
有些工具类如Math类,Arrays类,Collections类不希望被实例化,实例化对他们没有意义。而通过做成抽象类来强制该类不可以被实例化也是行不通的,因为该类可以被子类化,子类可以被实例化。因此建议用private修饰构造器。
第5条: 避免创建不必要的对象
一般来说,最好能重用对象,而不是每次需要时就创建一个相同功能的对象。一个极端的反面例子:
String s = new String("str"); //Don't do this
如果这种用法在循环中,或是在一个频繁调用的方法中,则会创建成千上万个不必要的String实例。
String s = "str";
改进后的版本只会有一个实例,对于所有在同一虚拟机运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用。
除了重用不可变对象外,也可以重用已知不会被修改的可变对象。下面例子检验一个人是否是“高峰期出生的小孩”。
public class Person {
private final Date birthDate;
public Person(Date birthDate){
this.birthDate = birthDate;
}
//Don't do this
public boolean isBabyBoomer(){
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0;
}
}
每次调用isBabyBoomer方法时,都会创建一个Calendar,一个TimeZone和两个Date对象,这是没必要的。
public class Person2 {
private final Date birthDate;
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public Person2(Date birthDate){
this.birthDate = birthDate;
}
public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
}
}
改造后,如果isBabyBoomer方法频繁调用,会显著提高性能,代码的含义也更加清晰了。
自动装箱(autoboxing)功能是一种创建多于对象的方法,它允许我们将基本类型和装箱基本类型混用,按需要自动装箱和拆箱。
public class LongTest {
//hideously slow program! can you spot the object creation?
public static void main(String[] args) {
long start = Calendar.getInstance().getTimeInMillis();
long sum = 0L;
//Long sum = 0L; //不要使用Long对象,这样意味着程序会多构造2^31个Long实例
for(long i=0; i < Integer.MAX_VALUE; i++){
sum += i;
}
long end = Calendar.getInstance().getTimeInMillis();
System.out.println(end - start);
}
protected void defaultTest(){
System.out.println("包内访问权限");
}
}
通过例子我们可以看出,要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
这个条目并不是暗示“创建对象代价非常昂贵,我们应该尽可能地避免创建对象”。相反,有时通过创建附加对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。比如第39条有关“保护性拷贝”的内容。而维护自己的对象池来避免创建对象也不见得是种好方法,除非池中的对象是非常重量级的,维护自己的对象池必定会把代码弄的很乱,同时增加内存占用,还会损害性能。
第6条: 消除过期的对象引用
Java具有垃圾回收功能,当用完了对象之后,它们会被自动回收,但这并不代表不需要考虑内存回收的事了。一个栈内存泄漏的例子:
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){
elements[size++] = e;
}
public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
return result;
}
private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
这程序隐藏着个问题,程序中发生了内存泄漏,该类的pop()方法有问题。从栈中弹出来的对象不会被垃圾回收,这是因为栈内部维护着对这些对象的过期引用(就是指永远不会被解除的引用),在这里指大于size的那些元素。修复很简单:
public Object pop(){
if(size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null; //清空过期引用,不然会导致内存泄漏
return result;
}
一般而言,只要是类自己管理内存,我们就应该警惕内存泄漏问题。内存泄漏的另一个常见来源是缓存,一旦把对象放到缓存中之后,就很容易被遗忘,从而在它不再使用之后很久一段时间内仍留在内存内。对于这种问题,我们可以用WeakHashMap代表缓存。内存泄漏的第三个常见来源是监听器和其他回调。对于这种情况最佳方法是只保存它们的弱引用,例如只将它们保存成WeakHashMap中的键。
第7条: 避免使用终结方法
终结方法指的就是finalize()方法,java语言规范不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。所以不应该依赖终结方法更新重要的持久状态,例如依赖终结方法来释放共享资源(比如数据库)上的永久锁,很容易让整个分布式系统垮掉。
终结方法既然存在那它就并不是毫无用处,第一种用途就是充当一个“安全网”,终结方法“本该”是在GC前做一些清理动作,但GC的时间未知,也就是终结方法执行时间未知,对于FileInputStream类我们都知道应该在try-finally中对其调用close方法,但也许我们会忘记编写此方法,在FileInputStream源代码中就实现了终结方法目的就在于如果忘记了close方法,至少还有终结方法,虽然可能不能得到及时执行,但晚执行总比不执行好吧。第二个用途可能使用的场景就可能比较少,JVM只回收普通对象,对于本地对象(也就是不是Java实现的对象),JVM并不会对它进行回收,此时我们就可以在终结方法中对本地对象进行一些清理操作,但一定记住一定要是“不拥有关键资源的前提”,且在子类中重写了终结方法一定要现实调用super.finalize(),否则父类的终结方法不会被调用。
综上,对于终结方法,一般代码中并不会使用,如果要使用一定要考虑上面两种用途是否值得去做,万万不应该依赖终结方法来更新重要的持久状态。
1.创建和销毁对象_EJ的更多相关文章
- Effective Java笔记一 创建和销毁对象
Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...
- 《Effective Java》—— 创建与销毁对象
本篇主要总结的是<Effecticve Java>中关于创建和销毁对象的内容. 比如: 何时以及如何创建对象 何时以及如何避免创建对象 如何确保及时销毁 如何管理对象销毁前的清理动作 考虑 ...
- ch2 创建和销毁对象
ch2 创建和销毁对象
- [Effective Java]第二章 创建和销毁对象
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- 《Effect Java》学习笔记1———创建和销毁对象
第二章 创建和销毁对象 1.考虑用静态工厂方法代替构造器 四大优势: i. 有名称 ii. 不必在每次调用它们的时候都创建一个新的对象: iii. 可以返回原返回类型的任何子类型的对象: JDBC ...
- effective java读书小记(一)创建和销毁对象
序言 <effective java>可谓是java学习者心中的一本绝对不能不拜读的好书,她对于目标读者(有一点编程基础和开发经验)的人来说,由浅入深,言简意赅.每一章节都分为若干的条目, ...
- 和我一起学Effective Java之创建和销毁对象
前言 主要学习创建和销毁对象: 1.何时以及如何创建对象 2.何时以及如何避免创建对象 3.如何确保它们能够适时地销毁 4.如何管理对象销毁之前必须进行的清理动作 正文 一.用静态工厂方法代替构造器 ...
- Effective Java(1)-创建和销毁对象
Effective Java(1)-创建和销毁对象
- 《Effective Java 2nd》第2章 创建和销毁对象
目录 第1条:考虑使用静态工厂方法代替构造器 第2条:遇到多个构造器参数时考虑用构建器 第3条:用私有构造器或者枚举类型强化Singleton属性 第4条:通过私有构造器强化不可实例化的能力 第5条: ...
随机推荐
- SpringMVC 使用 MultipartFile 实现文件上传
该代码实现了文件上传和文本字段同时传递到后台进行处理的功能. 直接贴代码,中间涉及到的实体类就不贴了,和功能没啥关系的. Controller /** * 添加活动 * * @param req * ...
- JMagic 操作 ImageMagick 处理图片
项目描述 imagemagick是功能强大的图片处理库,以稳定及高效率著称,众多语言对该库进行封装处理.比如php.java.由于我们是java项目,直接使用java通过JNI技术调用ImageMag ...
- 第74节:Java中的Cookie和Session
第74节:第74节:Java中的Cookie和Session ServletContext: 什么是ServletContext,有什么用哦,怎么用呢? 启动服务器后,会给每个应用程序创建一个Serv ...
- RISC-V平台的汇编指令解析
csrr a0, 0xF14 //把0xF14的值读入到a0中 andi a1, a0, 0x1f //把a0 和0x1F按位与运算后存储到a1中 srli a0, a0, 5 ...
- Xamarin.Android 使用 SimpleAdapter 打造 ListView 万能适配器
第一步:创建 layout1.axml 来展示列表详细内容 <?xml version="1.0" encoding="utf-8"?> <L ...
- 推荐一篇关于java集合的博文,写的很nice
这也是我自己在网上看到的一篇博文,作者的博文都很棒,以后还会持续为大家推荐好的博文,只要大家不骂我只会转别人的博文,自己不会写,其实这些都是基础,前辈们已经在实践中总结的很细很全了,所以也没必要去总结 ...
- vue 关于vue.set的学习笔记
vue新手小白,在看vue文档的时候 发现vue关于 数组,对象值改变的与 ng有那么点不同. 官方表示 由于 JavaScript 的限制,Vue 不能检测以下变动的数组: 当你利用索引直接设置一个 ...
- 全网最全最详细的Windows下安装Anaconda2 / Anaconda3(图文详解)
不多说,直接上干货! 说明: Anaconda2-5.0.0-Windows-x86_64.exe安装下来,默认的Python2.7 Anaconda3-4.2.0-Windows-x86_64.ex ...
- AspNetPager分页控件的使用方法
1. 首先将AspNetPager.dll复制于应用程序下的bin目录,打开解决方案,引入dll文件 (通过NuGet获取) 2. 在工具栏中添加控件,这样可以支持拖拽使用 3.页面拖入分页控件,设置 ...
- kubelet集群网络配置flannel(覆盖网络)
kubernetes本身并不会对跨主机容器的网络进行设置,这需要额外的工具来实现.一些常用的开源工具主要包括flanne.OpenvSwitch.Weave.Calico等,这里面最常用的是flann ...