二、创建和销毁对象

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

1.用静态工厂方法代替构造器

优势:
  • 它们有名称

  • 不必在每次调用它们的时候都创 建一个新对象。

  • 它们可以返回原返回类型的任何子类 型的对象。

  • 所返回的对象的类可以随着每次调用而发生变化,这取 决于静态工厂方法的参数值。

  • 方法返回的对象所属的类,在编写包含该静态工厂方 法的类时可以不存在。

缺点:
  • 类如果不含公有的或者受保护的构造器,就不能被子 类化。

  • 程序员很难发现它们

例:

下面是静态工厂方法的一些惯用名称。 这里只列出了其中的一小部分:

from一一类型转换方法,它只有单个参数,返回该类型的一个相对应的实例

Date d = Date.from(instance);public static Date from(Instant instant) {try {return new Date(instant.toEpochMilli());} catch (ArithmeticException ex) {throw new IllegalArgumentException(ex);}}

valueOf一一比 from 和 of 更烦琐的一种替代方法

BigInteger bigInteger = BigInteger.valueOf(Integer.MAX_VALUE);public static BigInteger valueOf(long val) {// If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constantif (val == 0)return ZERO;if (val > 0 && val <= MAX_CONSTANT)return posConst[(int) val];else if (val < 0 && val >= -MAX_CONSTANT)return negConst[(int) -val];return new BigInteger(val);}

等等。。。

2.遇到多个构造器参数时要考虑使用构建器

方法一:重叠构造器
  • 提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,最后包含所有。
  • 缺点:当有许多参数时,难编写

eg

public class NutritionFacts {private final int servingSize;//requiredprivate final int servings;   //requiredprivate final int calories;   //optionalprivate final int fat;        //optionalprivate final int sodium;    //optionalprivate final int carbohydrate;//optionalpublic 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;}
}NutritionFacts nutritionFacts = new NutritionFacts(240, 8, 100, 0, 35, 27);
方法二:javaBeans
  • 先调用一个无参构造器来创建对象,再调用setter方法设置每个必要的参数
  • 缺点:构造过程被分到几个调用中,在构造过程中,JavaBean可能处于不一致状态,JavaBean模式使得把类做成不可变的可能性不复存在。额外确保它的线程安全
方法三:建造者模式

例:

public class NutritionFacts {private final int servingSize;//requiredprivate final int servings;   //requiredprivate final int calories;   //optionalprivate final int fat;        //optionalprivate final int sodium;    //optionalprivate final int carbohydrate;//optionalpublic static class Builder{//required parametersprivate final int servingSize;private final int servings;//可选参数private int calories = 0;private int fat = 0;private int sodium = 0;private int carbohudrate = 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 sodium(int val){fat = val;return this;}public Builder carbohyrate(int val){fat = 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.carbohudrate;}
}NutritionFacts nutritionFacts = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohyrate(27).build();

Builder模式也适用于类层次结构

Pizza.Builder的类型是泛型,带有一个递归类型参数,它和抽象的self方法一样,允许在子类中适当地进行方法链接,不需要转换类型,这个针对java缺乏self类型的解决方案,被称作模拟的self类型。

public abstract class Pizza {public enum Topping{HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}final Set<Topping> toppings;abstract static class Builder<T extends Builder<T>>{EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);public T addTopping(Topping topping){toppings.add(Objects.requireNonNull(topping));return self();}abstract Pizza build();protected abstract T self();}Pizza(Builder<?> builder){toppings = builder.toppings.clone();}
}

两个具体的pizza类。

package leetcode.shuzu;import java.util.Objects;public class NyPizza extends Pizza {public enum Size{SMALL, MEDIUM, LARGE}private final Size size;public static class Builder extends Pizza.Builder<Builder>{private final Size size;public Builder(Size size){this.size = Objects.requireNonNull(size);}@Overridepublic NyPizza build(){return new NyPizza(this);}@Overrideprotected Builder self(){return this;}}private NyPizza(Builder builder){super(builder);size = builder.size;}
}public class Calzone extends Pizza {private final boolean sauceInside;public static class Builder extends Pizza.Builder<Builder>{private boolean sauceInside = false;public Builder sauceInside(){sauceInside  = true;return this;}@Overridepublic Calzone build(){return new Calzone(this);}@Overrideprotected Builder self(){return this;}}private Calzone(Builder builder){super(builder);sauceInside = builder.sauceInside;}
}
NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();

如果类的构造器或者静态工厂中具有多个参数,设计这种类时, Builder 模式就是一种不错的选择,

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

方法一:在第一种方法中,公有静态成员是个 final 域
public class Elvis{public static final Elvis INSTANCE = new Elvis();private Elvis{...}public void leaveTheBuilding(){...}
}

私有构造器仅被调用一次,用来实例化公有的静态 fina l 域 Elvis . INSTANCE 保证了 Elvis 的全局唯一性 , 一旦 Elvis 类被实例化,将只会存在一个 Elvis 实例 ,不多也不少 。

享有特权的客户端可以借助 Access 工bleObject.setAccessible 方法,通过反射机制调用私有构造器 。 如果需要抵御这种攻击,可以修改构造器,
让它在被要求创建第二个实例的时候抛出异常 。

方法二:公有的成员是个静态工厂方法
public class Elvis{private static final Elvis INSTANCE = new Elvis();private Elvis(){...}public static Elvis getInstance(){return INSTANCE;}public void leaveTheBuilding(){...}
}

会导致假冒的Elvis

将上述方法实现的singleton类变成是可序列化的,仅仅在声明中加上implements serializable是不够的,为了维护并保证singleton必须声明所有实例都是瞬时的(transient),并提供一个readResolve方法。

private Object readResolve(){return INSTANCE;
}
方法三:声明一个包含单个元素的枚举类型
public enum Elvis{INSTANCE;public void leaveTheBuilding(){...}
}

单元素的枚举类型经常成为实现 Singleton 的最佳方法 。

注意,如果 Singleton必须扩展一个超类,而不是扩展 Enum 的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口) 。

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

5.优先考虑依赖注人来引用资源

静态工具类和 Singleton 类不适合于需要引用底层资源的类 。

静态工具类和 singleton 实现

public class SpellChecker {//singlton
//    private final Lexicon dictionary = ...;
//    private SpellChecker() {//    }
//    public static INSTANCE = new SpellChecker();//静态工具类
//    private static final Lexicon dictionary = ...;
//
//    private SpellChecker() {//    }public static boolean isValid(String word){...};public static List<String> suggestions(String type){...}
}

这里需要支持类的多个实例,每个实例使用客户端指定的资源(这里词典),当创建一个新的实例时 , 就将该资源传到构造器中 。 这是依赖注入( dependency injection )的一种形式:词典( dictionary )是拼写检查器的一个依赖( dependency ),在创建拼写检查器时就将词典注入( injected )其中 。

public class SpellChecker {private final Lexicon dictionary;public SpellChecker(Lexicon dictionary) {this.dictionary = Objects.requireNonNull(dictionary);}public static boolean isValid(String word){...};public static List<String> suggestions(String type){...}
}

6.避免创建不必要的对象

eg:

static boolean isRomanNumeral(String s){return s.matches("^(?=.)M*()C[MD]|D?C{0,3})"+ "(X[CL] | L?X{0,3})(I[XV]|V?I{0,3})$");
}

不适合在注重性能的情形中重复使用,修改为

public class RomanNumerals{private static final Pattern ROMAN = Pattern.compile("^(?=.)M*()C[MD]|D?C{0,3})"+ "(X[CL] | L?X{0,3})(I[XV]|V?I{0,3})$");static boolean isRomanNumeral(String s){return ROMAN.matcher(s).matches();}
}

情形二:自动装箱使得基本类型和装箱基本类型之间的差别变得模糊起来, 但是并没有完全消除 。

private static long sum(){Long sum = 0L;for(long i = 0; i <= Integer.MAX_VALUE; i++){sum += i;}return sum;
}

分析:答案正确,但比实际情况慢,sum被声明成了Long, 程序需要构造大约2的31次方个多余的Long实例, 每次往Long sum中增加long时构造一个实例。

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

7.消除过期的对象引用

过期引用 :永远也不会在被解除的引用

eg:栈先增长,再收缩,从栈中弹出来的对象不会被当作垃圾回收。因为栈内部维护着对这些对象的过期引用。

解决:一旦对象引用已经过期,只需清空这些引用即可 。 对于上述例子中的 Stack 类而言,只要一个单元被弹出拢,指向它的引用就过期了

public Object pop(){if(size == 0) throw new EmptyStackException();Object result = elements[--size];elements[size] = null;return result;
}
  • 只要类是自己管理内存,程序员就应该警惕内存泄漏问题

  • 内存泄漏的另一个常见来源是缓存。 只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用 WeakHashMap 代表缓存;当缓存中的项过期之后,它们就会自动被删除 。 记住只有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时, WeakHashMap 才有用处 .

  • 内存泄漏的第三个常见来源是监昕器和其他回调

8.避免使用终结方法和清除方法

终结方法和清除方法的缺点在于不能保证会被及时执行

注重时间的任务不应该由终结方法或者清除方法来完成 。

永远不应该依赖终结方法或者清除方法来更新重要的持久状态。

9.try-with-resources 优先于 try-finally

在处理必须关闭的资源时,始终要优先考虑用 try-with-resources ,而不是用 try-finally 。 这样得到的代码将更加简洁、清晰,产生的异常也更有价值。 有了 trywith -resources 语句,在使用必须关闭的资源时,就能更轻松地正确编写代码了 .

static void copy(String src, String dst) throws IOException{try(InputStream in = new FileInputStream(src);OutputStream out = new FileOutputStream(dst)){byte[] buf = new byte[BUFFER_SIZE];int n;while ((n = in.read(buf)) >= 0){out.write(buf, 0, n);}}}带catchstatic String firstLineOfFile(String path, String defaultVal){try(BufferedReader br = new BufferedReader(new FileReader(path))){return br.readLine();}catch (IOException e){return defaultVal;}}

Effective Java读书笔记---二、创建和销毁对象相关推荐

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

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

  2. Effective Java读书笔记二:枚举和注解

    第30条:用enum代替int常量 当需要一组固定常量的时候,应该使用enum代替int常量,除了对于手机登资源有限的设备应该酌情考虑enum的性能弱势之外. 第31条:用实例域代替序数 枚举的ord ...

  3. Effective Java(一)—— 创建和销毁对象

    在客户端(调用端)获取自身实例的方法: 公有的构造器: 类的静态工厂方法: 1. 使用静态工厂方法代替构造器 Boolean 是对基本类型 boolean 的包装类: public final cla ...

  4. Effective Java读书笔记(二)

    Effective Java 读书笔记 (二) 创建和销毁对象 遇到多个构造器参数时要考虑使用构建器 创建和销毁对象 何时以及如何创建对象? 何时以及如何避免创建对象? 如何确保它们能够适时地销毁? ...

  5. Effective Java 读书笔记(七):通用程序设计

    Effective Java 读书笔记七通用程序设计 将局部变量的作用域最小化 for-each 循环优于传统的 for 循环 了解和使用类库 如果需要精确的答案请避免使用 float 和 doubl ...

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

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

  7. Effective Java读书笔记

    序列化 谨慎的实现Serializable接口 实现Serializable最大的代价,一旦这个类被发布就大大降低了改变这个类实现的灵活性,这个类中所有私有实例域都将变成导出API的一部分,不符合最低 ...

  8. Effective Java读书笔记三:创建和销毁对象

    第1条:考虑用静态工厂方法代替构造器 对于类而言,为了让客服端获得它的一个实例最常用的的一个方法就是提供一个公有的构造器.还有一种方法,类可以提供一个公有的静态工厂方法(static factory ...

  9. Effective Java 读书笔记(一)

    前言: 开个新的坑位,<effective java>的读书笔记,之后有时间会陆陆续续的更新,读这本书真的感触满多,item01和item02就已经在公司的项目代码中看到过了.今天这篇主要 ...

最新文章

  1. 移动机器人平台的坐标系---map,odom,base_link
  2. 数据结构实验之二叉树五:层序遍历(STL和模拟队列两种方法)
  3. SAP 物料订单创建、下达、报工、收货与投料(ABAP代码)
  4. PyQt4编程之简短地做出多个选择框
  5. FPGrowth算法总结复习
  6. Windows8/Silverlight/WPF/WP7/HTML5周学习导读(1月1日-1月6日)
  7. Gitlab7.0通知邮箱的配置(smtp)
  8. 关于导航按钮在其他尺寸下显示问题
  9. 金明的预算方案(洛谷-P1064)
  10. pytorch中的Sequential使用方法
  11. selenium定位不到元素的原因。
  12. ntsd.exe 附使用教程
  13. 计算机基础5y浏览器,5y浏览器
  14. 计算机上分辨率怎么设置在哪里设置方法,windows7如何设置分辨率_windows7设置分辨率的方法...
  15. docker迁移禅道
  16. 亲属卡额度是什么意思_2019信用卡权益总结之十二:附属卡
  17. codeforces 696C C. PLEASE(概率+快速幂)
  18. java倒计时器_java – 使用计时器倒计时
  19. 旋转矩阵变换左右手系
  20. 【编译原理】:如何将控制语句翻译成四元式

热门文章

  1. gprs实用技巧:gprs模块tcp连接稳定性测试
  2. ie visio 打开_Visio viewer 不能从IE打开vsd文件(转) | 学步园
  3. 图像工程的读书笔记 图像成像过程
  4. 三菱mr系列服务器故障代码a6,三菱MR-JE-40A操作手册MR-JE-40A故障排除篇手册 - 广州凌控...
  5. 大数加法(ascll转换),利息计算(数组,sizeof与循环运用)
  6. 基于ssm的社团报名管理系统开题答辩问题与答案
  7. [机缘参悟-37]:人感官系统的结构决定了人类是以自我为中心
  8. CodeForces - 3A Shortest path of the king【水题】
  9. VASP学习1入门知识
  10. spring-integration初探