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

这个静态工厂,与设计模式中的静态工厂不同,这里的静态工厂方法,替换为“静态方法”比较好理解,主要就是建议编写静态方法来创建对象。

使用静态方法的好处:

1、静态方法有名称,可以确切地描述功能,比如根据哪些参数,返回哪种类型;

2、不需要先创建对象实例,再调用方法来创建所需的对象;使用静态方法可以直接使用类名加静态方法名即可调用;

3、可以返回原类型的任何子类型对象;

4、可以通过传入不同的参数,获得不同的返回值;

5、方法返回的对象所属的类,在编写包含该静态方法的类时可以不存在,比如JDBC API;

6、使用构造方法创建对象时的开销,相对于使用静态方法来说,要大得多,比如Boolean.valueOf(Stirng)与new Boolean(String);

缺点和不足

1、静态方法中要想返回对象,那么该对象所属的类必须有可以访问的构造器,否则不能被实例化;

2、静态方法比较难被发现;

第2条 遇到多个构造器时考虑使用构建器

当类的构造器或者静态工厂中具有较多的参数时,或者参数可能后期会增加时,那么在设计类的时候,可以使用Builder模式。

  1. package cn.ganlixin.effective_java;
  2.  
  3. public class BuilderTest {
  4. public static void main(String[] args) {
  5. final Person person = new Person.Builder().id(1).name("ganlixin").build();
  6. }
  7. }
  8.  
  9. class Person {
  10. private int id;
  11. private String name;
  12.  
  13. private Person(Builder builder) {
  14. id = builder.id;
  15. name = builder.name;
  16. }
  17.  
  18. public static class Builder {
  19. // Builder一般来说包含有外层类相同的属性
  20. private int id;
  21. private String name;
  22.  
  23. public Builder id(int val) { this.id = val; return this; }
  24. public Builder name(String val) { this.name = val; return this; }
  25. public Person build() { return new Person(this); }
  26. }
  27. }

  

  上面的代码,使用Lombok的话,只需要使用一个@Builder注解即可:

  1. package cn.ganlixin.effective_java;
  2.  
  3. import lombok.Builder;
  4.  
  5. public class LombokBuilder {
  6.  
  7. public static void main(String[] args) {
  8. final Person person = new Person.PersonBuilder().id(1).name("ganlixin").build();
  9. }
  10. }
  11.  
  12. @Builder
  13. class Person {
  14. private int id;
  15. private String name;
  16. }

  

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

  单例模式:

  1、通过私有化构造器,可以防止客户端使用new关键字创建对象;

  2、提供静态方法获取单例对象;

  以上两点存在的问题,可以使用AccessibleObject.setAccessible()方法通过反射机制调用构造器,可以在构造其中判断是否已经创建实例,如果已创建,则抛出异常;

  3、单例对象的序列化问题;

  使用枚举类型创建单例属性,例子如下:

  1. public class SingletonEnum {
  2. public static void main(String[] args) {
  3. final OneDemo instance = OneDemo.INSTANCE;
  4. instance.test();
  5. }
  6. }
  7.  
  8. enum OneDemo {
  9. INSTANCE;
  10.  
  11. // 定义其他function
  12. public void test() {
  13. // .........
  14. }
  15. }

  

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

当我们创建工具类的时候,一般来说工具类不应该被实例化,虽然可以将工具类声明为抽象类来避免被实例化,但是这样并不好,因为抽象类的子类可以实例化!

解决方案:显示声明工具类的无参构造方法,在其中抛出异常即可。

  1. class MyUtil {
  2.  
  3. private MyUtil(){ throw new RuntimeException("工具类不应该被实例化"); }
  4.  
  5. // 声明工具类中的方法
  6. public static void myMethod(){}
  7. }

  

第5条:优先考虑依赖注入来引用资源

  主要就是讲依赖注入。

  问题是这样的:有一个邮件白名单,对于接收到的邮件,需要先进行过滤一遍(匹配白名单),那么可能会写出下面的代码:

  1. // 邮件过滤器
  2. class MailChecker{
  3.  
  4. // 白名单列表,在代码中写死
  5. private List<String> whiteList = new ArrayList<String>(){{
  6. add("123"); add("456"); add("789");
  7. }};
  8.  
  9. // 进行检验操作
  10. public boolean isValid(String email) {
  11. return whiteList.contains(email);
  12. }
  13. }

  上面的代码其实并不好,原因:

  1、过滤名单写死了;

  2、只能进行邮件过滤;这一点可能会有疑惑,功能专一不好吗?这没问题,但是如果有一个“电话过滤器”是不是又要有一个PhoneChecker呢?

  推荐做法:在过滤器中不写死whiteList,白名单由外部传入,过滤器的功能就是进行过滤而已,改为如下:

  1. public class UseInject {
  2. public static void main(String[] args) {
  3. // 假设phoneList是外界传入的,或者是文件中读入的
  4. List<String> phoneList = null;
  5. Checker phoneChecker = new Checker(phoneList);
  6. phoneChecker.isValid("123");
  7. }
  8. }
  9.  
  10. class Checker{
  11. private List<String> whiteList;
  12.  
  13. // 接收外界传入的白名单(可以是电话、邮箱、地址....)
  14. public Checker(List<String> whiteList) {
  15. this.whiteList = whiteList;
  16. }
  17.  
  18. // 检验操作
  19. public boolean isValid(String val) {
  20. return whiteList.contains(val);
  21. }
  22. }

  如果是使用Spring这些框架,可以直接利用依赖注入,比如:

  1. public class UseInject {
  2.  
  3. @Value("${phoneList}")
  4. private static List<String> phoneList;
  5.  
  6. public static void main(String[] args) {
  7. Checker phoneChecker = new Checker(phoneList);
  8. phoneChecker.isValid("123");
  9. }
  10. }

  

第6条:避免创建不必要的对象

  1、虽然Java有自动装箱和自动装箱,但是请尽量使用基本数据类型;

  2、对于提供了静态工厂方法和构造器的类来说,应该优先使用静态方法而非构造器创建对象;

  3、有一些对象不应该被重复创建,比如正则表达式的Pattern实例;

  4、应该避免维护自己的对象池,数据库连接池除外,因为建立数据库连接的代价比较大,创建普通对象的代价则相对来说很小;

  5、不要太死板,有些时候,为了避免创建不必要的对象,会付出更多的代价;

第7条:消除过期的对象引用

  1、如果类是自己管理内存,那么就需要需要警惕内存泄漏问题,一旦元素被释放掉,则该元素包含的任何对象应用都应该被清空;

  2、缓存导致内存泄漏,可以使用WeakHashMap;

  3、如果提供服务给客户端调用,要警惕客户端的反复调用是否会反复产生对象,但是却没有清理垃圾;

第8条:避免使用终结方法和清除方法

  1、终结方法(finalizer)和清除方法(cleaner)不能保证会被及时的执行,从一个对象变为不可达开始,到执行这两个方法的事件是任意长的,所以,注重时间的任务不应该由finalizer和cleaner来完成;

  2、永远不应该依赖finalizer和cleaner方法来更新重要的持久状态,因为这两个方法可能没有机会被执行;

  3、System.gc和System.runFuinalization只能增加finalizer和cleaner的执行机会,注意,增加可能性,但不是保证,100%;

  4、finalizer中出现异常,终结过程会终止,可怕吧,想死还死不掉;

  5、降低程序性能、finalizer attack;

第9条:try-with-resource优先于try-finally

  要使用try-with-resource,需要类实现AutoCloseable接口(大多数类都实现了)。

  下面以书中的例子举例:

  1. public static void notSuggest() throws IOException {
  2. BufferedReader bufferedReader = null;
  3. try {
  4. bufferedReader = new BufferedReader(new FileReader("demo.txt"));
  5. bufferedReader.readLine();
  6. } finally {
  7. bufferedReader.close();
  8. }
  9.  
  10. // 注意,上面的demo.txt不存在时,报异常FileNotFoundException,然后finally中的close也会报错,但是close的异常会覆盖readLine的异常
  11. // 所以调用notSuggest()方法,只会看到close的异常Exception in thread "main" java.lang.NullPointerException
  12. }

  改成下面这样就可以捕获所有异常了,但是看起来,代码好臃肿:

  1. public static void notSuggest2() {
  2. BufferedReader bufferedReader = null;
  3. try {
  4. bufferedReader = new BufferedReader(new FileReader("demo.txt"));
  5. bufferedReader.readLine();
  6. } catch (FileNotFoundException e) {
  7. e.printStackTrace();
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. } finally {
  11. try {
  12. bufferedReader.close();
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. // 虽然可以打印出所有的异常信息(FileNotFoundException和close的NullPointerException异常)
  18. // 按照常识,如果前面一步已经错了,就不应该再走下去,但是FileNotFoundException之后,还是会执行finally中的close,结果又出现异常
  19. }

  推荐使用try-with-resource

  1. public static void suggest() {
  2. try (BufferedReader bufferedReader = new BufferedReader(new FileReader("demo.txt"))) {
  3. bufferedReader.readLine();
  4. } catch (Exception e) {
  5. e.printStackTrace();
  6. }
  7. // 不需要catch每一个子异常,比如FileNotFoundException,直接写一个IOException或者Exception即可
  8. // 上面demo.txt不存在,所以会出现java.io.FileNotFoundException,但是没有报第二个异常哦
  9. }

  

 

《Effective Java》第1章 创建和销毁对象的更多相关文章

  1. effective java 第2章-创建和销毁对象 读书笔记

    背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...

  2. 【Effective Java】第二章-创建和销毁对象——1.考虑用静态工厂方法代替构造器

    静态工厂方法的优点: 可以赋予一个具有明确含义的名称 可以复用唯一实例,不必每次新建 可以返回原实例类型的子类对象 可以在返回泛型实例时更加简洁 缺点: 类如果不含有共有的或者受保护的构造器,就不能被 ...

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

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

  4. 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器

    类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...

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

    第一条 考虑用静态工厂方法代替构造器 什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化. 比如说 public class StaticFactory { private String na ...

  6. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  7. Effective Java 学习笔记之创建和销毁对象

    一.考虑用静态工厂方法代替构造器 1.此处的静态工厂方法是指返回指为类的对象的静态方法,而不是设计模式中的静态工厂方法. 2.静态工厂方法的优势有: a.使用不同的方法名称可显著地表明两个静态工厂方法 ...

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

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

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

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  10. 《Effective Java 2nd》第2章 创建和销毁对象

    目录 第1条:考虑使用静态工厂方法代替构造器 第2条:遇到多个构造器参数时考虑用构建器 第3条:用私有构造器或者枚举类型强化Singleton属性 第4条:通过私有构造器强化不可实例化的能力 第5条: ...

随机推荐

  1. Linux程序在Windows下编译运行_MinGW和Cygwin

    linux要在windows下编译运行,需要win下的gcc编译器,一般有两种:MinGW和Cygwin. 但某些函数在windows没有,即使使用两种工具也编译不过,需要查询windows函数并使用 ...

  2. java获取一个时间段内的时间天数

    package com.hzcominfo.hik.hikbigscreen.core; import java.text.SimpleDateFormat; import java.util.Arr ...

  3. Shell 日常 ip 端口可用性测试

    ip port 可用测试 telnet 测试某个ip 端口是否可用很方便,但是如果ip比较多,写脚本就不方便了因为是阻塞的 这里强烈推荐 nc nc -z -w 1 127.0.0.1 8990 这里 ...

  4. 项目Beta冲刺(团队)——05.26(4/7)

    项目Beta冲刺(团队)--05.26(4/7) 格式描述 课程名称:软件工程1916|W(福州大学) 作业要求:项目Beta冲刺(团队) 团队名称:为了交项目干杯 作业目标:记录Beta敏捷冲刺第4 ...

  5. 【大数据】Windows7、Hadoop2.7.6

    一.Java配置 1.完整路径不能有空格:C:\jdk1.8.0_101 2.配置环境变量:JAVA_HOME 二.Hadoop配置 1.完整路径不能有空格:F:\0002_BigData\Soft\ ...

  6. Go语言 - 结构体 | 方法

    自定义类型和类型别名 自定义类型 在Go语言中有一些基本的数据类型,如string.整型.浮点型.布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型. 自定义类型是定义了一个全新的类型 ...

  7. stm32中的型号对比——为什么很少用STM32F2,F3?

    源自网络 我觉得有三点: 1. F2属于加强版的F1,内核还是cortex M3,只是主频提高到了120MHz(F1是72MHz),但是这点提升没有实质性意义,性能比不上 2. F3是F4的削弱版,一 ...

  8. windows jenkins dotnet core 自动化构建webapi

    jenkins环境搭建好 注意一下几点: - 需要安装git - 需要安装dotnet core sdk环境 - 遇到这里报错,提示 Repository URL 错误的话,如果确实没有配置错误,重启 ...

  9. 洛谷 P1731 [NOI1999]生日蛋糕 题解

    每日一题 day53 打卡 Analysis 观察一个蛋糕的俯视图,上表面的面积其实就是最下面那一层的底面积,所以在第一次搜索的时候加入这个底面积,之后就只用考虑侧面积就好啦. 就是每次枚举r和h,如 ...

  10. Linux软件安装——软件包分类、RPM包管理

    1.软件包分类: (1)源码包: 优点:开源,即用户可以看到源代码,用户可以修改源代码:可以自由选择所需的功能:软件是编译安装,效率更高. 缺点:需要手动安装,安装慢. (2)二进制包(RPM包.系统 ...