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

说明:

关于本博客使用的书籍,源代码Gitee仓库 和 其他的相关问题,请查看本专栏置顶文章:《Effective Java》第0条:写在前面,用一年时间来深度解读《Effective Java》这本书

正文:

如果提到如何创建一个类的对象,你的第一反应肯定是:new一个对象!也就是用构造器的方式。

但是在 Effective Java 这本书中,作者提出了另一个创建对象的思路,那就是静态工厂。

举个栗子:

第一种:正常情况:创建一个 “男人” 的对象

// Person类
public class Person {

    String sex;

    Person(String sex) {
        this.sex = sex;
    }
    
    Person() {
        this.sex = "男";
    }
}

// Main类
public class Main {
    public static void main(String[] args) {
        Person man = new Person();
        Person woman = new Person("女");
    }
}

第二种:使用静态工厂:创建一个 “男人” 的对象

// Person类
public class Person {

    private static Person MAN = new Person("男");
    private static Person WOMAN = new Person("女");
    
    String sex;

    private Person(String sex) {
        this.sex = sex;
    }

    public static Person getMan() {
        return MAN;
    }

    public static Person getWoman() {
        return WOMAN;
    }
}

// Main类
public class Main {
    public static void main(String[] args) {
        Person man = Person.getMan();
        Person woman = Person.getWoman();
    }
}

那么,使用静态工厂类的优缺点有什么呢?

优点1、原文P4:静态工厂方法与构造器不同的第一大优势在于,他们有名称。

从例子里也可以看到,如果用第一种方式创建的对象,我们都不能判断我们创建的是一个男人还是女人,或者通过参数的方式。但是第二种方式,我们通过方法名就可以判断是男人还是女人了。

优点2、原文P5:静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新的对象

类似于单例模式,如例子所示的第一种方式,每次创建对象使用new的方式,就会在堆内存中开辟一块空间用于存储对象,会导致在堆内存中创建多个一样的Person对象。

但是如果使用第二种静态工厂的方式,每次拿到的Person对象,其实都是一个,在内存中也只有这一个Person对象,这样就避免了占用过多的内存。Boolean.valueOf(boolean)就是使用了这种方式(如下),可以看出 TRUE 和 FALSE 都是事先定义好的对象,return 出去的对象也只有这两个其中的一个。

// jdk源码 Boolean 类
...
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
...
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

优点3、原文P5:静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象

我们把上面的例子里的Person类修改一下,改为以下:

// Person类
public abstract class Person {
    private static Person MAN = new Man();
    private static Person WOMAN = new Woman();

    String sex;

    private Person(String sex) {
        this.sex = sex;
    }

    public static Person getAnyone(double flag) {
        if (flag < 0.5) {
            return MAN;
        } else{
            return WOMAN;
        }
    }

    private static class Man extends Person {
        Man() {
            super("男");
        }
    }

    private static class Woman extends Person {
        Woman() {
            super("女");
        }
    }
}
// Main类
public class Main {
    public static void main(String[] args) {
        Person anyone = Person.getAnyone(0.8);
    }
}

该例子中,Man类 和 Woman类 都变成了Person 的子类,getAnyone方法可以返回任意的一个子类,但是Main方法中,没有做任何的修改,如果此时又有一个Badman类实现了Person,那么只需要在Person中加入一个静态成员变量和get方法即可。

书中所写到的,Collections类中,emptyList、singletonList,就是用了这种方式实现的。他们都实现了List接口,使用了静态方法返回不同的实现。

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

翻译一下就是,每次调用所传递的参数不同,静态工厂可以根据参数的不同返回不同的类,如上例中的 getAnyone 方法。如果有一天,我们把 0.8,对于用户来说根本没有感知。

书中写到的 EnumSet 类,是个抽象类,有构造器,但是构造器是给子类实现来用的,比如静态工厂方法 noneOf,就用了该方式

// jdk源码 EnumSet 类
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);
}

从源码可以看出,当 universe.length 是 64 的时候,返回的是 JumboEnumSet 子类。

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

字都认识,连接来就不知道啥意思了吧。

还是拿上个例子来说吧,getAnyone方法可以返回任意的一个子类,目前只有两个子类,分别是 男人类 和 女人类,假如未来又出来一个新的 外星人类,那么这个外星人类只需要实现Person类,就可以完美适配上面的代码。但现在 这个外星人类是不存在的。

书中的例子是JDBC的API,当开发人员编写JDBC的时候,可能某个数据库还不存在,假如现在我开发了一个“小猪数据库”,我只需要实现JDBC的API,就可以让Java连上我的数据库了

即:JDBC 中的 DriverManager.getConnection() 方法:

getConnection() 的返回类型是 Connection 接口(由 Java 标准库定义);

当 Java 团队编写 DriverManager 类时,Connection 的具体实现类(如 MySQLConnection、XiaoZhuConnection)根本不存在 —— 这些实现类是由数据库厂商(如 MySQL、小猪数据库)后来编写的;

但 DriverManager 依然可以通过静态工厂方法返回这些 “未来才会存在” 的实现类对象,只要它们实现了 Connection 接口。

源码(仅展示关键代码):

// 通过 info(即在application.yml中的配置项,来生成一个Connection的子类)
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    ......
    for(DriverInfo aDriver : registeredDrivers) {
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                // 从这里将配置文件中配置的信息,转换为 Connection,这样任意一个数据库都可以连接了
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }
    ......
}

现在优点都讲完了,接下来看看缺点

缺点1、原文P7:类如果不含公有的或者受保护的构造器,就不能被子类化,但是内部类除外。

像是上面的例子,实现Person的两个类都是内部类,所以Person的构造器可以是private的,如果其中有一个类不是内部类,那么Person的构造器就不能是Persion的,否则就不能被子类继承。

缺点2、原文P7:程序员很难发现静态工厂方法

因为我们已经习惯了去new一个对象,如果一个对象的构造器不是private的,那么我们习惯性的就去new了。

所以书中给的解决方案是:通过注释,写明这个是静态工厂;或者遵守标准的命名习惯(具体看书P7)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值