《Effective Java》第1条:用静态工厂方法代替构造器

本文探讨了静态工厂方法在Java中的应用,强调了其优点如明确对象类型、减少重复对象、隐藏实现和灵活返回子类型等,同时也指出没有公开构造器导致的局限性和对程序员发现性的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于一个类而言,为了让客户端获取到它自身的一个实例,有两种方法:

  1. 提供一个公有的构造器
  2. 提供一个公有的静态工厂方法

静态工厂方法返回类的实例,以Boolean类为例:

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

 该方法接收boolean基本类型,返回Boolean对象引用

静态工厂与构造器相比的优势:

1. 静态工厂方法有名称,可能能够确切描述正被返回的对象

 例如BigInteger的probablePrime方法:

 Returns a positive BigInteger that is probably prime, with the specified bitLength. The probability that a BigInteger returned by this method is composite does not exceed 2^{-100}.

public static BigInteger probablePrime(int bitLength, Random rnd) {
        if (bitLength < 2)
            throw new ArithmeticException("bitLength < 2");

        return (bitLength < SMALL_PRIME_THRESHOLD ?
                smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
                largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
    }

返回一个具有指定位长度的正的BigInteger, 它可能是素数,为合数的可能性不超过2^{-100},"probablePrime"直接指明返回的实例的类型(大概率是素数)

2. 不必每次调用的时候都创建一个新对象,可以使用预先构建好的实例,或者复用构建好的实例,能够:①避免创建不必要的重复对象;②有助于类严格控制在某个时刻有哪些实例应该存在

以Runtime类为例,通过静态工厂方法实现为重复调用返回相同实例对象,保证唯一性 

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}

3. 可以返回原返回类型的任何子类型的对象, 能够:①不会使对象对应的子类型变成公有的,从而隐藏实现类;②不必知道实现类的存在,不需要多写一个外部类,减少API数量;③可以直接通过接口引用被返回的对象

 实现一个Book类,对于正在打折的书可以调用getSaledBook方法获取SaledBook实例,①能够实现SaledBook类的隐藏,②不需要知道SaledBook类的存在,通过Book即可调用

class Book {
    protected String name;
    protected int price;
    
    private Book() { }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }

    public static Book getBook() {
        return new Book();
    }
    
    public static Book getSaleBook() {
        return new SaleBook();
    }
    
    private static class SaleBook extends Book{
        public void setPrice(int price) {
            this.price = (int)(price*0.8);
        }
    }
}

自行调试: 

public class Test {
    public static void main(String[] args) {
        Book book = Book.getBook();
        book.setPrice(12);
        Book saleBook = Book.getSaleBook();
        saleBook.setPrice(12);
        System.out.println(book.getPrice());
        System.out.println(saleBook.getPrice());
    }
}

4. 所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值,客户端不知道也不需要知道它们从工厂方法中得到的对象的类

 沿用第3条的例子,丢弃getSaledBook方法,统一通过getBook方法获取Book对象,依照传入的参数决定返回的对象

class Book {
    protected String name;
    protected int price;

    private Book() { }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }

    public static Book getBook(String type) {
        switch (type) {
            case "Normal":
                return new Book();
            case "Sale":
                return new SaleBook();
            default:
                return null;
        }
    }

    private static class SaleBook extends Book{
        public void setPrice(int price) {
            this.price = (int)(price*0.8);
        }
    }
}

自行调试: 

public class Test {
    public static void main(String[] args) {
        Book book = Book.getBook("Normal");
        book.setPrice(12);
        Book saleBook = Book.getBook("Sale");
        saleBook.setPrice(12);
        System.out.println(book.getPrice());
        System.out.println(saleBook.getPrice());
    }
}

5. 方法返回对象所属的类,在编写包含该静态工厂方法的类时可以不存在 

沿用第4条的例子,如果我们需要再扩展一个Book的子类RiseBook表示价格上涨的书籍,只需要编写该子类然后在静态工厂方法中声明即可

class Book {
    protected String name;
    protected int price;

    private Book() { }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }

    public static Book getBook(String type) {
        switch (type) {
            case "Normal":
                return new Book();
            case "Sale":
                return new SaleBook();
            case "Rise":
                return new RiseBook();
            default:
                return null;
        }
    }

    private static class SaleBook extends Book{
        public void setPrice(int price) {
            this.price = (int)(price*0.8);
        }
    }

    private static class RiseBook extends Book{
        public void setPrice(int price) {
            this.price = (int)(price*1.2);
        }
    }
}

自行调试:

public class Test {
    public static void main(String[] args) {
        Book book = Book.getBook("Normal");
        book.setPrice(12);
        Book saleBook = Book.getBook("Sale");
        saleBook.setPrice(12);
        Book riseBook = Book.getBook("Rise");
        riseBook.setPrice(12);
        System.out.println(book.getPrice());
        System.out.println(saleBook.getPrice());
        System.out.println(riseBook.getPrice());
    }
}

 静态工厂方法的劣势:

1. 类如果没有public或者protected的构造器,就不能被继承

考虑到减少API、隐藏实现类的需求,上述例子中SaleBook和RiseBook均为私有类,因此它们无法被继承

2. 程序员很难发现它们

 静态工厂方法在文档中没有明确标识,同时需要建立标准的命名习惯,不再赘述

总而言之,静态工厂方法和公有构造器各有用处,但是静态工厂方法通常来说更加合适,第一反应不应该是考虑公有构造器。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值