Effctive_java_01 阅读笔记

简介: 使用静态工厂方法替代构造方法,静态工厂方法的优劣势

使用静态工厂方法替代构造方法

一、 类获取实例的方法

  1. 提供一个公共构造方法(传统)
  2. 提供一个公共静态工厂方法

二、 使用静态工厂方法的优劣

1 优点

1. 有自己的名字,构造方法没有;

  • 如果构造方法的参数本身并不描述被返回的对象,有名称的静态工厂更易于使用,易于阅读;
  • 一个类只能有一个给定签名的构造方法。如果需要多个同样签名的构造方法,只能通过修改参数类型的顺序,造成识别的困难,此时可以使用静态工厂方法。

2. 与构造方法不同,不需要每次调用都创建一个新的对象;

  • 表现: 允许不可变的类使用预先构建的实例或者在构造时缓存实例,提高对象的使用率,从而提高性能;
  • 静态工厂方法 重复调用返回相同对象的能力 可以使 类在任何时候存在的实例都可以严格控制。这样的类称为实例控制。
    实例是专门类的对象(实例即对象,实例是从类的角度考虑,对象是从程序的角度考虑)
  • 实例控制的作用:

    1. 允许一个类来保证它是一个单例项或不可实例化的;

      • 使一个类是单例有三种方法,静态工厂方法是其中一种,如下:

    1 公共属性方法

          public class Elvis{
             public static final Elvis INSTANCE = new Elvis();
             private Elvis(){...}
             public void leaveTheBuilding(){...}
          }
  • 私有构造方法只调用一次,初始化公共静态final Elvis.INSTANCE 属性。没有公共的或受保护的构造方法,保证了全局的唯一性;
  • 一旦Elvis被初始化,一个Elvis的实例就会存在,仅此一个。客户端做任何事情都无法改变。但是,如果特殊存在调用AccessibleObject.setAccessible()方法,
  • 以反射方式调用私有构造方法,就需要额外处理,在请求创建第二个实例时抛出异常。

    2 静态工厂方法

         public class Elvis{
            private static final Elvis INSTANCE = new Elvis();
            private Elvis(){...}
            public static Elvis getInstance(){return INSTANCE;}
            public void leaveTheBuilding(){...}
         }
  • 所有对Elvis.getInstance 的调用都返回相同的对象引用,并且不会创建其他的Elvis实例。

    3 以上两种方法都是基于保持构造方法私有和导出公共静态成员以提供对唯一实例的访问。

    -  第一种: 1 API明确表示该类是一个单例:公共静态属性是final的,故包含相同的对象引用;2 更简单。
    -  第二种: 1 灵活,无论该类是否为单例而不必更改其API;返回唯一的实例,但可修改。 
            2 如果程序需要,可以编写一个泛型单例工厂。
            3 方法引用可以用 supplier,例如 Elvis :: instance 等同于 Supplier<Evlist> 。除非符合这三个优点,否则选公共属性方法。
    
    
    4 值得注意的是,第三种实现方法是使用枚举类
      public enum Elvis{
         INSTANCE;
         public void leaveTheBuilding(){...}
      }
    -    使用枚举实现的好处是: 是实现单例的最佳方式。提供了免费的序列化机制,并提供了针对多个实例化的坚固保证,即使是在复杂的序列化或反射攻击的情况下。   
    -    局限性: 如果单例必须继承Enum以外的父类,就不能使用。    
    1. 使用私有构造方法实现类的非实例化

      • 一般工具类,以此实现;比如 Math 或 Arrays ;

        public class UtilityClass{
         private UtilityClass(){
            throw new AssertionError();
         }
        }
      • 构造方法是私有的,在类外不可访问。AssertionError异常不是严格要求的,但是它防止了客户端在类中意外地调用构造方法。保证类在任何情况下都不会被实例化。
      • 缺点是: 阻止了类的子类化。所有的构造方法都必须显式或隐式地调用父类构造方法,而子类则没有可访问的父类构造方法来调用。
      • 值得注意的是: 通过创建抽象类来实现类的非实例化是不行的。抽象类是可以被子类化,子类可以被实例化。而且,误导用户改类的设计是为了继承。

    2 允许一个不可变的值类保证不存在两个相同的实例

3、 与构造方法不同,可以返回其返回类型的任何子类型的对象

  1. 在选择返回对象的类时提供了很大的灵活性。
  • 应用: Api可以返回对象而不需要公开它的类。
  • 适用于: 基于接口的框架,接口为静态工厂方法提供自然返回类型。
  • 版本不同:

    1. 1.8 之前
      接口不能有静态方法。

    通过静态工厂方法在一个非实例类中实现,返回对象的类都是非公开的。使用这种静态工厂方法需要客户端通过接口而不是实现类来返回引用的对象。
    一般来说,也应该优先使用接口而不是类来引用对象,唯一真正需要应用对象的类的时候是使用构造函数创建它的时候。

    1. 1.8 及之后
      接口可以包含静态方法,很多公开的静态成员应该放在这个接口本身。但是大部分实现代码还是应该放在单独的私有类中,因为

    java8要求所有的静态成员都是公共的。java9 允许私有静态方法,但是静态字段和成员仍然需要公开。

4、 返回对象的类可以根据输入参数的不同而不同

  1. 声明的返回类型的任何子类都是允许的,返回对象的类也是可以变化。

    • 根据传入参数的不同,返回不同的值。比如:

      public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
          Enum<?>[] universe = getUniverse(elementType);
          if (universe == null)
         throw new ClassCastException(elementType + " not an enum");
      
          if (universe.length <= 64)
         return new RegularEnumSet<>(elementType, universe);
          else
         return new JumboEnumSet<>(elementType, universe);
      }
    • 这个方法的调用,根据枚举类型的元素数量,返回不同的实例。
    • 实现类RegularEnumSet和JumboEnumSet 的存在对于客户端来说是不可见的,或者说,客户端也是不关心的。只需要知道是EnumSet的子类即可。

5、 在编写包含该方法的类时,返回的对象的类不需要存在

  1. 静态工厂方法构成了服务提供者框架的基础,比如Java数据库连接Api(JDBC)。服务提供者框架是提供者实现服务的系统,并且系统使得实现对
    客户端可用,从而将客户端从实现中分离出来。

    • 服务提供者框架有三个基本组:

      1. 服务接口,表示实现;
      2. 提供者注册Api,提供者用来实现注册;
      3. 服务访问Api,客户端使用该Api获取服务的实例。
      • 值得注意的是 服务访问 API 允许客户指定选择实现的标准。在缺少这样的标准的情况下,API 返回一个默认实现的实例,或者允许客户通过所有可用的实现进行遍历。服务访问 API 是灵活的静态工厂,
        它构成了服务提供者框架的基础。
    • 服务提供者框架可以提供一个可选的组件做为第四个组件是:服务提供者接口;它描述了一个生成服务接口实例的工厂对象。在没有服务提供者接口的情况下,
      必须对实现进行反射实例化。例子:

      • 在jdbc的情况下,Connection 扮演服务接口的一部分,DriverManager.registerDriver 提供程序注册Api、DriverManager.getConnection 是服务访问Api,
        Driver是服务提供者接口。
      • 在Java6开始,平台提供一个通过的服务提供者框架 ServiceLoader,jdbc不用,因为jdbc出来的更早。
      • 依赖注入框架是很强大的服务提供者。
      • 服务访问 API 可以向客户端返回比提供者提供的更丰富的服务接口。称为桥接模式。

2 缺点

1、没有公共或受保护的构造方法的类不能被子类化

  • 比如,在Collentions框架中,不能将实现类子类化。

1、程序员很难找到,不突出

  • 解决,可以通过使用通用的命名减少这个问题。

三、总结

  • 静态工厂方法和公共构造方法都有它们的用途,并且了解它们的相对优点是值得的。通常,静态工厂更可
    取,因此避免在没有考虑静态工厂的情况下提供公共构造方法

内容整理在公众号中,比较方便查看,

XG54_9_WXMH_5X_HB_H_7V

博客都整理在github上,欢迎访问,还有一些常用的工具和学习源码

image
image
github

如有不对,多多指教

相关文章
|
5天前
|
运维 Java
Java版HIS系统 云HIS系统 云HIS源码 结构简洁、代码规范易阅读
云HIS系统分为两个大的系统,一个是基层卫生健康云综合管理系统,另一个是基层卫生健康云业务系统。基层卫生健康云综合管理系统由运营商、开发商和监管机构使用,用来进行运营管理、运维管理和综合监管。基层卫生健康云业务系统由基层医院使用,用来支撑医院各类业务运转。
27 5
|
15天前
|
Java
Java基础—笔记—static篇
`static`关键字用于声明静态变量和方法,在类加载时初始化,只有一份共享内存。静态变量可通过类名或对象访问,但推荐使用类名。静态方法无`this`,不能访问实例成员,常用于工具类。静态代码块在类加载时执行一次,用于初始化静态成员。
10 0
|
15天前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
15 0
|
1月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--希尔排序
数据结构与算法(Java篇)笔记--希尔排序
|
2月前
|
监控 负载均衡 Dubbo
|
2月前
|
Java
电子书阅读分享《Java工程师成神之路》
电子书阅读分享《Java工程师成神之路》
50 1
|
3月前
|
Java
电子书阅读分享《Java开发手册(嵩山版)》
电子书阅读分享《Java开发手册(嵩山版)》
622 0
|
15天前
|
Java API
Java基础—笔记—内部类、枚举、泛型篇
本文介绍了Java编程中的内部类、枚举和泛型概念。匿名内部类用于简化类的创建,常作为方法参数,其原理是生成一个隐含的子类。枚举用于表示有限的固定数量的值,常用于系统配置或switch语句中。泛型则用来在编译时增强类型安全性,接收特定数据类型,包括泛型类、泛型接口和泛型方法。
9 0
|
1月前
|
Java 测试技术 API
《手把手教你》系列技巧篇(六)-java+ selenium自动化测试-阅读selenium源码(详细教程)
【2月更文挑战第15天】《手把手教你》系列技巧篇(六)-java+ selenium自动化测试-阅读selenium源码(详细教程) 前面几篇基础系列文章,足够你迈进了Selenium门槛,再不济你也至少知道如何写你第一个基于Java的Selenium自动化测试脚本。接下来宏哥介绍Selenium技巧篇,主要是介绍一些常用的Selenium方法或者接口(API),通过这些接口(API)或者方法的具体操作,达到能够熟练使用Selenium编写Java的自动化测试脚本,从而为后续的Java+Selenium自动化测试框架设计打基础。
30 0
|
1月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--快速排序
数据结构与算法(Java篇)笔记--快速排序