解读 Java 经典巨著《Effective Java》90条编程法则,第1条:用静态工厂方法代替构造器

《Effective Java》是 Java 开发领域的一本重要著作,对于 Java 开发人员来说尤为宝贵。作者 Joshua Bloch 在书中深入探讨了 Java 编程中的最佳实践,并提供了大量的示例和建议,旨在帮助开发人员写出更高效、可维护的代码。书中的内容包括了 Java 语言的核心特性和设计模式,涉及到的主题如创建和销毁对象、类和接口的设计、泛型、枚举、并发等。

如果你想要在 Java 开发中遇到难题或想要提高编程技巧,那么这本书绝对值得一读。书中每个条目都以简洁明了的方式解释了如何避免常见错误并提高代码质量。如果你是 Java 开发人员,阅读《Effective Java》将对你的职业发展大有裨益。

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

创建一个类的实例最传统的方式是让类提供一个公共构造器,并使用 new 关键字来创建对象。例如:

public final class Boolean {
    
    private final boolean value;
    
    // 公有构造器
    public Boolean(boolean value) {
        this.value = value;
    }
}

// 使用构造器创建实例
Boolean b = new Boolean(true);

如果不通过公有构造器,或者说除了公有构造器之外,类还可以通过向外提供一个公有的静态工厂方法(static factory method)用于替代公有构造器。

例如 Java 的 Boolean 类中提供了一个静态工厂方法 valueOf 用于创建一个 Boolean 对象:

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

Boolean b = Boolean.valueOf(true);

静态工厂方法的五大优势

第一大优势:静态工厂方法可以有描述性的名称,使得创建实例的意图更加明确

在 Java 中,一个类禁止有两个及以上相同签名的构造器(即构造器名、参数类型和顺序完全相同),然而我们可以通过重载的方式定义参数顺序不同的构造器来绕过这个限制,但这种做法容易让调用人员混淆,如果参数类型和顺序都非常接近,调用者可能会因为记不清楚顺序而误用构造器,增加出错的风险。同时,这种设计也使得代码的理解和维护变得更为复杂。

public class Person {
    private String name;
    private int age;

    // 构造器 1
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 构造器 2,参数顺序不同
    public Person(int age, String name) {
        this.name = name;
        this.age = age;
    }
}

静态工厂方法相比于构造器,静态工厂方法允许有描述性的名称,可以更明确地表明对象的创建方式,使得创建实例的意图更加明确。并且可以避免构造器参数的顺序问题。通过为不同的构造需求提供不同的静态方法,可以提高代码的可读性。例如:

public class User {
    private final String name;
    private final int age;
    private final String email;

    private User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // 静态工厂方法,创建一个仅包含姓名和年龄的 User 实例,邮箱默认为 null
    public static User createWithNameAndAge(String name, int age) {
        return new User(name, age, null);
    }

    // 静态工厂方法,创建一个仅包含姓名和邮箱的 User 实例,年龄默认为 0
    public static User createWithNameEmail(String name, String email) {
        return new User(name, 0, email);
    }
}

第二大优势:不必在每次调用它们的时候都创建一个新对象

在设计不可变类时,可以预先构建好实例,并将构建好的实例缓存起来,在需要时通过静态工厂方法重复利用这些实例,这样避免了每次请求时都创建新的对象,从而减少了内存使用和对象创建的开销。

例如 Boolean 类的静态工厂方法 valueOf(boolean b) 返回 Boolean.TRUEBoolean.FALSE 的实例,而不是每次都创建一个新的 Boolean 对象:

public final class Boolean {

    // 静态常量 TRUE,代表布尔值 true 的唯一实例
    public static final Boolean TRUE = new Boolean(true);

    // 静态常量 FALSE,代表布尔值 false 的唯一实例
    public static final Boolean FALSE = new Boolean(false);

    // 静态工厂方法,返回布尔值的唯一实例
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

}

第三大优势:可以返回原返回类型的任何子类型的对象

静态工厂方法提供了比构造器更大的灵活性,因为它能够返回接口类型的任意子类实例,而不仅仅是特定的类实例。这种灵活性特别适用于基于接口的框架(interface-based framework),允许 API 隐藏具体实现类,从而使 API 保持简洁。

用户调用静态工厂方法时只需要关注接口,而不用了解或依赖具体的实现,减少了对实现细节的依赖。这种设计也便于在未来对实现细节进行修改或扩展,而不会影响到依赖这个API的调用方的代码。

// 工厂类,用于创建 Shape 对象
public class ShapeFactory {

    // 静态工厂方法,根据输入的类型返回相应的 Shape 实例
    public static Shape createShape(String type) {
        if ("circle".equalsIgnoreCase(type)) {
            return new Circle();
        } 
        else if ("rectangle".equalsIgnoreCase(type)) {
            return new Rectangle();
        }
        throw new IllegalArgumentException("Unknown shape type");
    }
}

// Shape 接口,定义所有形状必须实现的公共方法
public interface Shape {
    void draw();
}

// Circle 类,实现 Shape 接口,表示一个圆形
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

// Rectangle 类,实现 Shape 接口,表示一个矩形
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }
}

// 测试 ShapeFactory 功能
public class Main {
    public static void main(String[] args) {
        // 使用工厂方法创建一个圆形对象
        Shape shape = ShapeFactory.createShape("circle");
        shape.draw();
    }
}

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

静态工厂方法能够根据每次调用时传入的不同参数返回不同的具体实现。例如,通过传递不同的参数,静态工厂方法可以返回不同的子类实例:

public class NumberFactory {
    public static Number getNumber(int type) {
        switch (type) {
            case 1:
                return new Integer(1);
            case 2:
                return new Double(2.0);
            default:
                return null;
        }
    }
}

注意:第三大优势关注的是静态工厂方法返回的对象类型可以是返回类型的任何子类型,而不是固定类型。第四大优势则关注静态工厂方法根据不同的参数值返回不同的具体实现,即对象的类可以根据参数值发生变化。

第五大优势:方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

静态工厂方法的主要优势在于它允许我们在编写包含静态工厂方法的类时,不需要关心或直接知道具体的实现类,具体实现类可以在运行时动态决定。这使得系统能在运行时灵活地加载和使用不同的实现类,而不是在编译时就锁定特定的实现。

这个优势特别适用于需要支持多个实现且实现类可能动态变化的系统,尤其是服务提供者框架(Service Provider Framework),在这个框架中,系统在运行时决定和加载实际的实现类,而不需要在编译时确定。JDBC 的 DriverManager 就是服务提供者框架的一个经典示例。

在 JDBC 中,驱动程序是通过 java.sql.Driver 接口实现的,不同的数据库供应商提供不同的实现。而 DriverManager 类负责管理数据库驱动的加载和连接。在使用时通过 DriverManager 的静态工厂方法 DriverManager.getConnection 根据传入的 URL、用户名和密码,选择合适的驱动程序来建立连接,驱动的具体实现类会在运行时动态加载的。

public class JdbcExample {
    public static void main(String[] args) {
        try {
            // 动态加载 MySQL 驱动程序
            Class.forName("com.mysql.cj.jdbc.Driver");
            
            // 使用 DriverManager 获取数据库连接
            String url = "jdbc:mysql://localhost:3306/mydatabase";
            String username = "root";
            String password = "password";
            Connection connection = DriverManager.getConnection(url, username, password);
            
            System.out.println("Connection established successfully!");
            
            // 关闭连接
            connection.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

注意:静态工厂方法的第五个优势,涉及到设计和开发规范,旨在提供一个统一的 API 设计。

两个缺点

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

如果一个类只通过静态工厂方法提供实例,那么通常该类的构造器会被设置为私有的,因此这也将使得这个类无法被子类化。

image-20240910223945256

缺点二:程序员很难发现它们

由于静态工厂方法是通过类名直接调用的静态方法,而不是通过创建类的新实例,因此它们可能在文档和 API 浏览工具中不如构造器那样明显。

如果静态工厂方法没有在类的文档中充分说明,或者缺乏清晰的示例和说明,程序员可能很难找到这些方法并理解它们的用途,特别是当这些静态工厂方法具有类似的名称或参数签名时,代码的阅读和维护可能变得更加复杂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@赵士杰

如果对你有用,可以进行打赏,感

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值