前言

主要学习创建和销毁对象:

  • 1.何时以及如何创建对象
  • 2.何时以及如何避免创建对象
  • 3.如何确保它们能够适时地销毁
  • 4.如何管理对象销毁之前必须进行的清理动作

正文

一、用静态工厂方法代替构造器

获取类的实例的常用方法有:

  • 1.公有的构造器
  • 2.公有的静态工厂方法

下面通过Boolean类(基本类型boolean的包装类)的简单示例来学习:

//公有的构造器public Boolean(String s) {this(parseBoolean(s));}
//公有的静态工厂方法
public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);}

静态工厂方法相对于构造器的优势:

1.有名称

具有适当名称的静态工厂方法更易使用,产生的代码更易阅读。可用名称来突出它们之间的区别。

如构造器BigInteger(int,int,Random)返回的BigInteger可能为素数,若用名为BigInteger.probablePrime的静态工厂方法来表示,显得更加清楚。

2.不必每次调用它们的时候都创建一个新对象

不可变类可以使用预先构建好的实例,或者是将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。

例如之前的Boolean的静态工厂方法就说明了这项技术,这种方法类似于FlyWeight模式。如果程序经常请求创建相同的对象,并且创建对象的代价很高,这项技术极大地提升了性能。

//缓存起来的Boolean实例public static final Boolean TRUE = new Boolean(true);public static final Boolean FALSE = new Boolean(false);public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);}

静态工厂方法为重复的调用返回相同的对象。

3.可返回原返回类型的任何子类型的对象

4.创建参数化类型实例时,可使代码变得更加简洁

由于《Effective Java》这本书在编写的时候,JDK1.7还没有推出,因此在调用参数化类的构造器时,类型参数都必须要指明。而静态工厂方法能替我们实现类型推导的功能。

  //JDK7之前Map<String,List<String>> map = new HashMap<String,List<String>>();//JDK7Map<String,List<String>> m = new HashMap<>();//使用静态工厂方法public class MyHashMap extends HashMap {public static <K,V> HashMap<K,V> newInstance(){return new HashMap<K,V>();}//静态工厂方法实现类型推导 Map<String,List<String>> m = MyHashMap.newInstance();
}

静态工厂方法的缺点:

  • 1.类如果不含公有的或受保护的构造器,就不能被子类化。
  • 2.静态工厂方法和其他的静态方法实际上没有任何区别。
/**静态工厂方法的惯用名称 */
//valueOf:返回的实例与它的参数具有相同的值
//String.valueOf(int)方法public static String valueOf(int value) {return Integer.toString(value);}//of:valueOf的简洁版//EnumSet.of(E)方法public static <E extends Enum<E>> EnumSet<E> of(E e) {EnumSet<E> set = EnumSet.noneOf(e.getDeclaringClass());set.add(e);return set;}   //getInstance:返回的实例是通过方法的参数来描述的。//newInstance:返回的每个实例与其他的所有实例不同。//getType:Type表示工厂方法返回的对象类型//newType:与newInstance类似

二、多个构造器参数考虑用构建器

静态工厂和构造器的局限性:不能很好地扩展到大量的可选参数

对于有大量的参数的类的编写:

/***重叠构造器模式*提供一个只有必要参数的构造器,第二个有一个可选参数,第二个有两个,依此类推。*/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 NutritionFacts(int servingSize,int servings){this(servingSize,servings,0);}/*** 有一个可选元素的构造方法,以下依此类推*/public NutritionFacts(int servingSize,int servings,int calories){this(servingSize,servings,calories,0);}public NutritionFacts(int servingSize,int servings,int calories,int fat){this(servingSize,servings,calories,fat,0);}public NutritionFacts(int servingSize,int servings,int calories,int fat,int sodium){this(servingSize,servings,calories,fat,sodium,0);}public NutritionFacts(int servingSize,int servings,int calories,int fat,int sodium,int carbohydrate){this.servingSize = servingSize;this.servings = servings;this.calories = calories;this.fat = fat;this.sodium = sodium;this.carbohydrate = carbohydrate;}
}

此方法虽可行,但如果有许多参数时,客户端代码很难编写,并且难以阅读

/***JavaBeans模式*无参构造方法创建对象,使用setter方法设置必选或可选参数。*/public class NutritionFactsWithJavaBeans {/*** 必选元素*/private int servingSize;private int servings;/*** 可选元素*/private int calories;private int fat;private int sodium;private int carbohydrate;/*** 无参构造方法*/public NutritionFactsWithJavaBeans(){}/*** Setter方法*/public void setServingSize(int servingSize) {this.servingSize = servingSize;}public void setServings(int servings) {this.servings = servings;}public void setCalories(int calories) {this.calories = calories;}public void setFat(int fat) {this.fat = fat;}public void setSodium(int sodium) {this.sodium = sodium;}public void setCarbohydrate(int carbohydrate) {this.carbohydrate = carbohydrate;}public static void main(String[] args) {NutritionFactsWithJavaBeans cocaCola = new NutritionFactsWithJavaBeans();cocaCola.setServingSize(240);cocaCola.setServings(8);cocaCola.setCalories(100);cocaCola.setSodium(35);cocaCola.setCarbohydrate(27);}
}

JavaBeans模式创建实例容易,代码易读。但有很严重的缺点

  • 1.构造过程中可能出现不一致的状态,调试困难。
  • 2.因为有set方法,使得在JavaBeans模式中,不能将类变为不可变的,需要额外的努力来确保它的线程安全。

重叠构造器的安全性+JavaBeans模式的可读性====>Builder模式

易写易读,模拟了具名的可选参数。build方法可检验约束条件。

public class NutritionFactsWithBuilder {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 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 NutritionFactsWithBuilder build(){return new NutritionFactsWithBuilder(this);}}private NutritionFactsWithBuilder(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) {NutritionFactsWithBuilder nutritionFactsWithBuilder= new NutritionFactsWithBuilder.Builder(20,30).calories(3).build();}}

用私有构造器或枚举类型强化Singleton属性

Singleton表示仅仅被实例化一次的类。

实现Singleton的方法:

  • 1.public static final域实现
public class SingletonClazz{//public属性将本类唯一实例暴露出去public static final SingletonClazz INSTANCE = new SingletonClazz();//构造方法私有 保证全局唯一性private SingletonClazz(){}public void test(){System.out.println("test method");}
}

缺点:利用反射机制可调用到私有构造方法

 try {Constructor<SingletonClazz> constructor = SingletonClazz.class.getDeclaredConstructor();constructor.setAccessible(true);SingletonClazz clazz =  constructor.newInstance();clazz.test();} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {e.printStackTrace();}

如何防止调用私有构造方法,修改构造方法,在创建第二个实例的时候抛出异常即可。

 private SingletonClazz(){if(INSTANCE!=null)throw new RuntimeException("cannot create more than one instance of SingletonClazz");}
  • 2.静态工厂方法实现
public class SingletonClazz {private static final SingletonClazz INSTANCE = new SingletonClazz();private SingletonClazz(){}public static SingletonClazz getInstance(){return INSTANCE;}public void test(){System.out.println("test method");}
}

这个方法相比于之前的方法的优势是,灵活性,不需要更改API的前提下,可以改变该类是否应该为Singleton。

使Singleton类变成为可序列化的:

  • 实现Serializable接口
  • 需要声明所有实例为瞬时的(transient)
  • 提供一个readResolve()方法。

3.编写一个包含单个元素的枚举类型来实现

public enum SingletonClazz {INSTANCE;public void test(){System.out.println("test method");}
}

优点:简洁,并提供了序列化机制,而且能保证不会被多次实例化(即使是面对复杂的序列化或是发射攻击的时候)----->实现Singleton的最佳方法

通过私有构造器强化不可实例化的能力

在缺少显式构造器的情况下,编译器会自动提供一个公有的,无参的缺省构造器

public class DefaultConstructor {//没有显示构造器public static void main(String[] args) {//使用编译器提供的公有的,无参的缺省构造器DefaultConstructor defaultConstructor = new DefaultConstructor();}
}

通过将类做成抽象类来实现不可实例化的目的是不可取的。继承抽象类,子类也可以被实例化。同时也会误导用户,以为是为了继承而设计的。

public abstract class AbstractClazz {//通过抽象类来实现不可实例化是不可取的public static void main(String[] args) {//仍旧可以通过继承抽象类,来达到实例化子类的目的SubClazz subClazz = new SubClazz();}
}class SubClazz extends AbstractClazz{}

正确做法是:将构造器显式地声明为私有的。

public class UtilityClazz {//防止类被实例化private UtilityClazz(){throw new AssertionError();}}

避免创建不必要的对象

最好能重用对象,而不是在每次需要的时候创建一个相同功能的新对象。

重用不可变对象。

public class NoNeedObject {public static void main(String[] args) {String s1 = new String("12345");//错误做法String s2 = "12345";//正确做法String s3 = "12345";String s4 = new String("12345");check(s1,s2);//不同check(s2,s3);//相同 重用了对象check(s1,s4);//不同}public static void check(String one,String two){//==比较的是两个引用是否指向同一个对象if(one == two)System.out.println("same address");elseSystem.out.println("different address");}
}

重用那些已知不会被修改的可变对象。

class Person{private final Date birthDate;public Person(Date birthDate){this.birthDate = birthDate;}public boolean isBabyBoomer(){//没有必要的对象创建//每次判断都会生成Calendar,Date,TimeZone实例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;}
}

用一个静态的初始化器来避免上面那种效率低下的情况。

class Person{private final Date birthDate;public Person(Date birthDate){this.birthDate = birthDate;}private static final Date BOOM_START;private static final Date BOOM_END;static {System.out.println("创建Calendar等对象");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 boolean isBabyBoomer(){return birthDate.compareTo(BOOM_START)>=0&&birthDate.compareTo(BOOM_END)<=0;}}

自动装箱(JDK5引入):Java编译器能在基本类型和包装类之间自动转换。如intInteger,doubleDouble等等。

相关学习链接:
1.https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
2.https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html

 public static void main(String[] args) {//使用包装类Longlong beforetime = System.currentTimeMillis();Long sum = 0L;for(long i = 0;i<Integer.MAX_VALUE;i++){sum+=i;}long aftertime = System.currentTimeMillis();System.out.println("Long--->sum="+sum);System.out.println("time="+(aftertime-beforetime));//使用基本类型longbeforetime = System.currentTimeMillis();long sum2 = 0L;for(long i = 0;i<Integer.MAX_VALUE;i++){sum2+=i;}aftertime = System.currentTimeMillis();System.out.println("long--->sum="+sum2);System.out.println("time="+(aftertime-beforetime));}

运行结果:

Long--->sum=2305843005992468481
time=9642
long--->sum=2305843005992468481
time=777

结论:优先使用基本类型而不是装箱基本类型,当心无意识的自动装箱。

注意: 很多规定只是建议,不要矫枉过正,犯了教条主义的错误,一定要与实际的开发情况结合,实事求是。

不要错误地认为“应该尽可能地避免创建对象”,应该是“避免创建不必要的对象”,注意是不必要的!

通过维护自己的对象池(object pool)来避免创建对象并不是一种好的做法,除非池中的对象非常重要。

消除过期的对象引用

手工管理内存的语言:C或C++

具有垃圾回收功能的语言:Java,简化程序员的工作

public class Stack {private Object[] elements;private int size = 0;private static final int DEFAULT_INITIAL_SIZE = 16;public Stack(){elements = new Object[DEFAULT_INITIAL_SIZE];}public void push(Object e){ensureCapacity();elements[size++] = e;}/*** stack: push(1) push(2) push(3) push(4) push(5) pop() pop()*      5*      4*      3  ---->栈顶*      2*      1  *      *      栈内部维护着过期的引用,也就是永远不会再被解除的引用,如4和5*    fixed:*        elements[size]=null;*        */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);}}
}

上面这段代码存在内存泄露的问题,当栈先是增长,然后弹出,从栈中弹出的对象都不会被当做垃圾回收。

栈内部维护着这些对象的过期引用,也就是永远不会被解除的引用。栈数组中下标大于或等于size的元素的引用都是过期的引用。

在支持垃圾回收的语言中, 内存泄露(也就是无意识的对象保持)很隐蔽。

修复办法:一旦对象引用过期,就清空这些引用。

注意:清空对象引用应该是一种例外,而不是一种规范行为。

容易导致内存泄露的几种情形:

  • 类自己管理内存
  • 缓存
  • 监听器和其他回调

避免使用终结方法

终结方法(finalizer):不可预测,危险,一般情况下是不必要的。

终结方法的缺点:

  • 不能保证会被及时地执行,而且根本不会保证它们会被执行。(所以不应该依赖终结方法来更新重要的持久状态
  • 严重的性能损失

终止类的对象中封装的资源(文件或线程),只需提供一个显式的终止方法。不需要编写终结方法。

例子:

  • InputStream,OutputStream,java.sql.Connection的close方法
  • java.util.Timer的cancel方法

一般与try-catch结合起来使用,以确保及时终止

和我一起学Effective Java之创建和销毁对象相关推荐

  1. Effective Java:创建和销毁对象

    前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.本博客是针对<Effective Java>这本书第2章所写的一篇读书笔记 ...

  2. 【读薄Effective Java】创建和销毁对象

    1. 考虑用静态工厂方法代替构造器 1.1 静态工厂的优点 静态工厂就是通过静态方法来代替构造器.相比构造函数,它有几个优势. 构造器没有名称.而静态工厂能指定名称,当一个类有多组构造函数的时候,可以 ...

  3. Java进阶 创建和销毁对象

    最近准备写点Javase的东西,希望可以帮助大家写出更好的代码. 1.给不可实例化的类提供私有构造器 比如:每个项目中都有很多工具类,提供了很多static类型的方法供大家使用,谁也不希望看到下面的代 ...

  4. Effective java 总结1 - 创建和销毁对象

    Effective java 总结 - 创建和销毁对象 第1条 用静态工厂方法代替构造器 优势 静态工厂方法有名称 不必每次调用的时候创建一个新的对象 可以返回原返回类型的任何子类型对象 返回对象的类 ...

  5. Effective Java(1)-创建和销毁对象

    Effective Java(1)-创建和销毁对象 转载于:https://www.cnblogs.com/Johar/p/10556218.html

  6. 《Effective Java》学习笔记 第二章 创建和销毁对象

    第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作. 1 考虑用静态工厂方法代替构造器 一般在某处获取一 ...

  7. [Effective Java]第二章 创建和销毁对象

    第一章      前言 略... 第二章      创建和销毁对象 1.            考虑用静态工厂方法代替构造器 创建对象方法:一是最常用的公有构造器,二是静态工厂方法.下面是一个Bool ...

  8. Effective Java (3rd Editin) 读书笔记:1 创建和销毁对象

    1 创建和销毁对象 Item 1:考虑用静态工厂方法取代构造器 public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE); ...

  9. Effective Java读书笔记---二、创建和销毁对象

    二.创建和销毁对象 何时以及如何创建对象, 何时以及如何避免创建对象, 如何确保它们能够适时地销毁, 如何管理对象销毁之前必须进行的各种清理动作 1.用静态工厂方法代替构造器 优势: 它们有名称 不必 ...

  10. 《Effect Java》学习笔记1———创建和销毁对象

    第二章 创建和销毁对象 1.考虑用静态工厂方法代替构造器 四大优势: i. 有名称 ii. 不必在每次调用它们的时候都创建一个新的对象:   iii. 可以返回原返回类型的任何子类型的对象: JDBC ...

最新文章

  1. java urlconn 下载慢_使用HttpURLConnection下载文件时出现 java.io.FileNotFoundException彻底解决办法...
  2. HDU 1431 素数回文
  3. 数据结构与算法之美-目录
  4. vue 3.0和2.0区别_一文看懂 Vue.js 3.0 的优化
  5. linux二重进程,二叉树递归实现与二重指针
  6. 事物传递机制、应用、加载时机
  7. android学习笔记五。2、其他组件
  8. LintCode 207. 区间求和 II(线段树)
  9. 如何通过 Shell 监控异常等待事件和活跃会话
  10. JavaWeb中的Servlet原理是什么?(存库,建议收藏)
  11. keil 5(C51)下载安装
  12. Zookeeper如何保证数据一致性
  13. EGo1下板_简单秒表
  14. RFID亮灯电子标签在仓储管理中的应用
  15. MFC真的过时了吗?C++是否真的适合做GUI界面?
  16. 中科院计算机网络信息中心是一种怎样的存在?
  17. 爬取QQ音乐中一首歌的相关信息及评论(破解反爬虫、多协程队列爬虫)
  18. 【CPU GPU TPU】机器学习扫盲篇
  19. Linux-DNS服务器搭建
  20. Puzzle(自认为是模拟)

热门文章

  1. 【Github使用感触之一】使多文件多版本变得简单
  2. 从零基础入门Tensorflow2.0 ----三、7.自定义损失函数
  3. 【GIS导论】实验七 地图设计与输出
  4. 《深度学习Python实践》第14章——自动流程
  5. python的标准库——turtle
  6. 多点最小二乘法平面方程拟合计算与代码实现
  7. 【ArcGIS|空间分析|网络分析】8 查找能够为需求点对提供服务的最佳路径
  8. kubernetes集群搭建(二进制方式)
  9. linux系统man命令空白键,man查看帮助命令
  10. android控制软键盘显示与隐藏