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 代码。
0
0
复制全文
相关推荐










