活动介绍

Java编程:对象创建与销毁之静态工厂方法

立即解锁
发布时间: 2025-08-18 00:25:47 阅读量: 1 订阅数: 4
### Java 编程:对象创建与销毁之静态工厂方法 在 Java 编程中,对象的创建和销毁是基础且关键的操作。通常,类会通过提供公共构造函数来让客户端获取其实例,但还有一种技术值得每个程序员掌握,那就是静态工厂方法。 #### 静态工厂方法简介 静态工厂方法是一种返回类实例的静态方法。例如,`Boolean` 类中的 `valueOf` 方法: ```java public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } ``` 需要注意的是,这里的静态工厂方法与设计模式中的工厂方法模式不同,它在设计模式中没有直接对应的概念。 #### 静态工厂方法的优点 1. **具有名称**:与构造函数不同,静态工厂方法有名称。如果构造函数的参数本身不能描述返回的对象,那么一个命名恰当的静态工厂方法会更易于使用,客户端代码也更易读。例如,`BigInteger(int, int, Random)` 构造函数返回一个可能为素数的 `BigInteger`,若将其表示为名为 `BigInteger.probablePrime` 的静态工厂方法会更好(该方法最终在 1.4 版本中添加)。 2. **可避免重复创建对象**:静态工厂方法不需要每次调用时都创建新对象。这使得不可变类可以使用预构造的实例,或在构造时缓存实例,从而避免创建不必要的重复对象,提高性能。例如,`Boolean.valueOf(boolean)` 方法就不会创建新对象,类似享元模式。同时,这也允许类严格控制实例的存在情况,保证类是单例或不可实例化的,还能确保不可变类中不存在两个相等的实例,客户端可以使用 `==` 运算符代替 `equals(Object)` 方法,提高性能。枚举类型就提供了这样的保证。 3. **可返回任意子类型对象**:静态工厂方法可以返回其返回类型的任意子类型对象,这在选择返回对象的类时提供了极大的灵活性。例如,Java 集合框架通过一个不可实例化的类 `java.util.Collections` 中的静态工厂方法导出了 32 种集合接口的便利实现,返回对象的类都是非公共的,这使得 API 更加紧凑,概念上也更简单。此外,`java.util.EnumSet` 类根据底层枚举类型的大小返回不同的实现,客户端无需关心具体的实现类。这种灵活性还构成了服务提供者框架的基础,如 Java 数据库连接 API(JDBC)。服务提供者框架包含服务接口、提供者注册 API 和服务访问 API 三个基本组件,可选的第四组件是服务提供者接口。以下是一个简单的服务提供者框架实现: ```java // Service provider framework sketch // Service interface public interface Service { ... // Service-specific methods go here } // Service provider interface public interface Provider { Service newService(); } // Noninstantiable class for service registration and access public class Services { private Services() { } // Prevents instantiation (Item 4) // Maps service names to services private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>(); public static final String DEFAULT_PROVIDER_NAME = "<def>"; // 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` 提供了如下静态工厂方法: ```java public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); } ``` 那么可以将原本冗长的声明: ```java Map<String, List<String>> m = new HashMap<String, List<String>>(); ``` 替换为简洁的声明: ```java Map<String, List<String>> m = HashMap.newInstance(); ``` 虽然截至 1.6 版本,Java 语言还不能对构造函数调用进行类型推断,但你可以在自己的工具类或参数化类中提供这样的静态工厂方法。 #### 静态工厂方法的缺点 1. **无法子类化**:只提供静态工厂方法的类如果没有公共或受保护的构造函数,就不能被子类化,公共静态工厂方法返回的非公共类也是如此。例如,集合框架中的便利实现类就无法被子类化,但这也可以促使程序员使用组合而不是继承。 2. **不易区分**:静态工厂方法与其他静态方法不易区分,在 API 文档中不像构造函数那样突出,因此可能难以确定如何实例化一个只提供静态工厂方法的类。不过,可以通过在类或接口注释中突出静态工厂方法,并遵循常见的命名约定来减少这个缺点。常见的静态工厂方法命名如下: | 名称 | 说明 | | ---- | ---- | | `valueOf` | 大致返回与参数具有相同值的实例,实际上是类型转换方法 | | `of` | `valueOf` 的简洁替代,在 `EnumSet` 中流行 | | `getInstance` | 返回由参数描述但不能说具有相同值的实例,单例情况下不接受参数并返回唯一实例 | | `newInstance` | 与 `getInstance` 类似,但保证返回的每个实例都与其他实例不同 | | `getType` | 与 `getInstance` 类似,但工厂方法在不同的类中,`Type` 表示工厂方法返回对象的类型 | | `newType` | 与 `newInstance` 类似,但工厂方法在不同的类中,`Type` 表示工厂方法返回对象的类型 | ### 总结 静态工厂方法和公共构造函数各有其用途,了解它们的相对优点很有必要。通常,静态工厂方法更可取,因此在提供公共构造函数之前,应先考虑静态工厂方法。 下面是静态工厂方法优点的流程图: ```mermaid graph LR A[静态工厂方法] --> B[具有名称] A --> C[可避免重复创建对象] A --> D[可返回任意子类型对象] A --> E[减少参数化类型实例创建的冗长性] B --> B1[更易使用和阅读代码] C --> C1[提高性能] C --> C2[控制实例存在情况] D --> D1[API 更紧凑] D --> D2[构成服务提供者框架基础] E --> E1[编译器可推断类型参数] ``` 通过以上内容,我们可以看到静态工厂方法在 Java 编程中具有重要的作用,合理使用可以提高代码的可读性、性能和可维护性。 ### 深入理解静态工厂方法在实际应用中的考量 #### 性能优化与实际案例 在前面提到静态工厂方法能够避免重复创建对象从而提高性能,下面通过一个具体案例来进一步说明。假设我们有一个需要频繁创建 `Date` 对象的场景,通常我们会使用构造函数来创建: ```java import java.util.Date; public class DateCreationExample { public static void main(String[] args) { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Date date = new Date(); } long endTime = System.currentTimeMillis(); System.out.println("使用构造函数创建 1000000 个 Date 对象耗时: " + (endTime - startTime) + " 毫秒"); } } ``` 现在我们使用静态工厂方法来实现类似的功能,假设我们有一个 `DateFactory` 类: ```java import java.util.Date; public class DateFactory { private static final Date CURRENT_DATE = new Date(); public static Date getCurrentDate() { return CURRENT_DATE; } } ``` 然后在测试代码中使用静态工厂方法: ```java public class DateFactoryExample { public static void main(String[] args) { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Date date = DateFactory.getCurrentDate(); } long endTime = System.currentTimeMillis(); System.out.println("使用静态工厂方法获取 1000000 次 Date 对象耗时: " + (endTime - startTime) + " 毫秒"); } } ``` 通过对比这两种方式的执行时间,我们可以明显看到静态工厂方法在避免重复创建对象时的性能优势。 #### 服务提供者框架的实际应用 服务提供者框架在实际开发中有广泛的应用,除了 JDBC 之外,我们可以再举一个简单的日志服务的例子。 首先定义服务接口: ```java // 日志服务接口 public interface LogService { void log(String message); } ``` 然后定义服务提供者接口: ```java // 日志服务提供者接口 public interface LogProvider { LogService newLogService(); } ``` 接着实现一个具体的日志服务和提供者: ```java // 控制台日志服务实现 public class ConsoleLogService implements LogService { @Override public void log(String message) { System.out.println("Console Log: " + message); } } // 控制台日志服务提供者 public class ConsoleLogProvider implements LogProvider { @Override public LogService newLogService() { return new ConsoleLogService(); } } ``` 最后实现服务注册和访问类: ```java import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; // 日志服务注册和访问类 public class LogServices { private LogServices() { } private static final ConcurrentMap<String, LogProvider> providers = new ConcurrentHashMap<>(); public static final String DEFAULT_PROVIDER_NAME = "<default>"; // 提供者注册 API public static void registerDefaultProvider(LogProvider p) { registerProvider(DEFAULT_PROVIDER_NAME, p); } public static void registerProvider(String name, LogProvider p) { providers.put(name, p); } // 服务访问 API public static LogService newInstance() { return newInstance(DEFAULT_PROVIDER_NAME); } public static LogService newInstance(String name) { LogProvider p = providers.get(name); if (p == null) { throw new IllegalArgumentException("No provider registered with name: " + name); } return p.newLogService(); } } ``` 在使用时,我们可以这样注册和获取日志服务: ```java public class LogServiceExample { public static void main(String[] args) { // 注册默认提供者 LogServices.registerDefaultProvider(new ConsoleLogProvider()); // 获取日志服务并使用 LogService logService = LogServices.newInstance(); logService.log("This is a test log message."); } } ``` 通过这个例子,我们可以看到服务提供者框架如何将服务的实现和使用解耦,提高了系统的可扩展性和可维护性。 #### 静态工厂方法的使用建议 在实际开发中,我们可以根据以下情况来决定是否使用静态工厂方法: 1. **需要多个构造函数但参数签名相似时**:当一个类需要多个构造函数,且这些构造函数的参数签名相似,容易引起混淆时,使用静态工厂方法并为其命名可以提高代码的可读性和可维护性。 2. **频繁创建相同对象时**:如果需要频繁创建相同的对象,使用静态工厂方法可以避免重复创建,提高性能。 3. **返回对象类型需要灵活变化时**:当需要根据不同的条件返回不同类型的对象时,静态工厂方法可以轻松实现这种灵活性。 4. **参数化类型实例创建时**:在创建参数化类型的实例时,使用静态工厂方法可以减少代码的冗长性。 #### 总结 静态工厂方法在 Java 编程中是一种非常有用的技术,它具有很多优点,如提高代码可读性、性能优化、提供灵活性等,但也存在一些缺点,如无法子类化和不易区分等。在实际开发中,我们应该根据具体的需求和场景来选择使用静态工厂方法还是构造函数,充分发挥它们各自的优势。 下面是静态工厂方法使用决策的流程图: ```mermaid graph TD A[是否需要多个构造函数且参数签名相似] -->|是| B[使用静态工厂方法] A -->|否| C[是否频繁创建相同对象] C -->|是| B C -->|否| D[是否需要灵活返回对象类型] D -->|是| B D -->|否| E[是否创建参数化类型实例] E -->|是| B E -->|否| F[考虑使用构造函数] ``` 通过合理使用静态工厂方法,我们可以编写出更加高效、可维护和灵活的 Java 代码。
corwn 最低0.47元/天 解锁专栏
赠100次下载
点击查看下一篇
profit 400次 会员资源下载次数
profit 300万+ 优质博客文章
profit 1000万+ 优质下载资源
profit 1000万+ 优质文库回答
复制全文

相关推荐

李_涛

知名公司架构师
拥有多年在大型科技公司的工作经验,曾在多个大厂担任技术主管和架构师一职。擅长设计和开发高效稳定的后端系统,熟练掌握多种后端开发语言和框架,包括Java、Python、Spring、Django等。精通关系型数据库和NoSQL数据库的设计和优化,能够有效地处理海量数据和复杂查询。
最低0.47元/天 解锁专栏
赠100次下载
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看

最新推荐

编程中的数组应用与实践

### 编程中的数组应用与实践 在编程领域,数组是一种非常重要的数据结构,它可以帮助我们高效地存储和处理大量数据。本文将通过几个具体的示例,详细介绍数组在编程中的应用,包括图形绘制、随机数填充以及用户输入处理等方面。 #### 1. 绘制数组图形 首先,我们来创建一个程序,用于绘制存储在 `temperatures` 数组中的值的图形。具体操作步骤如下: 1. **创建新程序**:选择 `File > New` 开始一个新程序,并将其保存为 `GraphTemps`。 2. **定义数组和画布大小**:定义一个 `temperatures` 数组,并设置画布大小为 250 像素×250 像

ApacheThrift在脚本语言中的应用

### Apache Thrift在脚本语言中的应用 #### 1. Apache Thrift与PHP 在使用Apache Thrift和PHP时,首先要构建I/O栈。以下是构建I/O栈并调用服务的基本步骤: 1. 将传输缓冲区包装在二进制协议中,然后传递给服务客户端的构造函数。 2. 构建好I/O栈后,打开套接字连接,调用服务,最后关闭连接。 示例代码中的异常捕获块仅捕获Apache Thrift异常,并将其显示在Web服务器的错误日志中。 PHP错误通常在Web服务器的上下文中在服务器端表现出来。调试PHP程序的基本方法是检查Web服务器的错误日志。在Ubuntu 16.04系统中

AWSLambda冷启动问题全解析

### AWS Lambda 冷启动问题全解析 #### 1. 冷启动概述 在 AWS Lambda 中,冷启动是指函数实例首次创建时所经历的一系列初始化步骤。一旦函数实例创建完成,在其生命周期内不会再次经历冷启动。如果在代码中添加构造函数或静态初始化器,它们仅会在函数冷启动时被调用。可以在处理程序类的构造函数中添加显式日志,以便在函数日志中查看冷启动的发生情况。此外,还可以使用 X-Ray 和一些第三方 Lambda 监控工具来识别冷启动。 #### 2. 冷启动的影响 冷启动通常会导致事件处理出现延迟峰值,这也是人们关注冷启动的主要原因。一般情况下,小型 Lambda 函数的端到端延迟

Clojure多方法:定义、应用与使用场景

### Clojure 多方法:定义、应用与使用场景 #### 1. 定义多方法 在 Clojure 中,定义多方法可以使用 `defmulti` 函数,其基本语法如下: ```clojure (defmulti name dispatch-fn) ``` 其中,`name` 是新多方法的名称,Clojure 会将 `dispatch-fn` 应用于方法参数,以选择多方法的特定实现。 以 `my-print` 为例,它接受一个参数,即要打印的内容,我们希望根据该参数的类型选择特定的实现。因此,`dispatch-fn` 需要是一个接受一个参数并返回该参数类型的函数。Clojure 内置的

Hibernate:从基础使用到社区贡献的全面指南

# Hibernate:从基础使用到社区贡献的全面指南 ## 1. Hibernate拦截器基础 ### 1.1 拦截器代码示例 在Hibernate中,拦截器可以对对象的加载、保存等操作进行拦截和处理。以下是一个简单的拦截器代码示例: ```java Type[] types) { if ( entity instanceof Inquire) { obj.flushDirty(); return true; } return false; } public boolean onLoad(Object obj, Serial

JavaEE7中的MVC模式及其他重要模式解析

### Java EE 7中的MVC模式及其他重要模式解析 #### 1. MVC模式在Java EE中的实现 MVC(Model-View-Controller)模式是一种广泛应用于Web应用程序的设计模式,它将视图逻辑与业务逻辑分离,带来了灵活、可适应的Web应用,并且允许应用的不同部分几乎独立开发。 在Java EE中实现MVC模式,传统方式需要编写控制器逻辑、将URL映射到控制器类,还需编写大量的基础代码。但在Java EE的最新版本中,许多基础代码已被封装好,开发者只需专注于视图和模型,FacesServlet会处理控制器的实现。 ##### 1.1 FacesServlet的

设计与实现RESTfulAPI全解析

### 设计与实现 RESTful API 全解析 #### 1. RESTful API 设计基础 ##### 1.1 资源名称使用复数 资源名称应使用复数形式,因为它们代表数据集合。例如,“users” 代表用户集合,“posts” 代表帖子集合。通常情况下,复数名词表示服务中的一个集合,而 ID 则指向该集合中的一个实例。只有在整个应用程序中该数据类型只有一个实例时,使用单数名词才是合理的,但这种情况非常少见。 ##### 1.2 HTTP 方法 在超文本传输协议 1.1 中定义了八种 HTTP 方法,但在设计 RESTful API 时,通常只使用四种:GET、POST、PUT 和

响应式Spring开发:从错误处理到路由配置

### 响应式Spring开发:从错误处理到路由配置 #### 1. Reactor错误处理方法 在响应式编程中,错误处理是至关重要的。Project Reactor为其响应式类型(Mono<T> 和 Flux<T>)提供了六种错误处理方法,下面为你详细介绍: | 方法 | 描述 | 版本 | | --- | --- | --- | | onErrorReturn(..) | 声明一个默认值,当处理器中抛出异常时发出该值,不影响数据流,异常元素用默认值代替,后续元素正常处理。 | 1. 接收要返回的值作为参数<br>2. 接收要返回的值和应返回默认值的异常类型作为参数<br>3. 接收要返回

在线票务系统解析:功能、流程与架构

### 在线票务系统解析:功能、流程与架构 在当今数字化时代,在线票务系统为观众提供了便捷的购票途径。本文将详细解析一个在线票务系统的各项特性,包括系统假设、范围限制、交付计划、用户界面等方面的内容。 #### 系统假设与范围限制 - **系统假设** - **Cookie 接受情况**:互联网用户不强制接受 Cookie,但预计大多数用户会接受。 - **座位类型与价格**:每场演出的座位分为一种或多种类型,如高级预留座。座位类型划分与演出相关,而非个别场次。同一演出同一类型的座位价格相同,但不同场次的价格结构可能不同,例如日场可能比晚场便宜以吸引家庭观众。 -

并发编程:多语言实践与策略选择

### 并发编程:多语言实践与策略选择 #### 1. 文件大小计算的并发实现 在并发计算文件大小的场景中,我们可以采用数据流式方法。具体操作如下: - 创建两个 `DataFlowQueue` 实例,一个用于记录活跃的文件访问,另一个用于接收文件和子目录的大小。 - 创建一个 `DefaultPGroup` 来在线程池中运行任务。 ```plaintext graph LR A[创建 DataFlowQueue 实例] --> B[创建 DefaultPGroup] B --> C[执行 findSize 方法] C --> D[执行 findTotalFileS