编程语言
首页 > 编程语言> > EffectiveJava——第二章 创建和销毁对象

EffectiveJava——第二章 创建和销毁对象

作者:互联网

前言

本篇笔记是《Effective Java》一书的笔记。

创建和销毁对象

用静态工厂代替构造器

如果你在设计一个类,你不知道该给你的类的使用者使用构造器还是静态工厂方法,大部分时候,静态工厂方法要比构造器更正确。

静态工厂方法能够带来很多好处。

静态工厂有名称

Java中的构造器被强制设计成与类同名,这可以理解,因为构造方法代表你正在创建一个类的实例,所以理应使用new 类名()的方式,这本没什么问题。但是你一定遇到过这样的问题,就是你正在设计某个用户类:

class User{
    private int id;
    private int age;
    private String name;
}

你的业务逻辑决定你有时需要使用age,name创建用户对象,有时使用id,name创建用户对象,这时构造器就无法满足需求了。

public User(int id, String name){/* do something... */}
// 这个构造器和上面的有一样的方法签名,也就是参数类型,所以无法定义
public User(int age, String name){/* do something... */}

这时,一般我们会提供一个User(int id,int age,String name)的构造器,但是这样你就必须忍受每次使用age,name时必须也填入一个用于占位的id参数,对于id,name也是。一些人还会巧妙地进行如下设计:

public User(int id, String name){/* do something... */}
// 调换该构造器的参数顺序
public User(String name, int age){/* do something... */}

这样确实可以通过编译,而且也能解决每次传入一个多余参数的问题,但随之而来的是不清晰的定义,哪个构造器代表什么?我调用了这个构造器后会得到一个什么样的对象?如果你有很多需要这样处理的参数,指定会乱套。如果你是一个有洁癖的编码人员(实际上99%的编码人员对于代码都有洁癖),那你绝对忍受不了这样的API。

取而代之,使用静态工厂方法,你可以为它指定特别的名字:

public static User userWithIdAndName(int id,String name){/* do something... */}
public static User userWithAgeAndName(int age,String name){/* do something... */}

享元设计

这是在java.lang.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);
}
public static Boolean valueOf(String s) {
    return parseBoolean(s) ? TRUE : FALSE;
}

使用静态工厂方法,你可以只返回你创建好的对象,而不需要每次重新创建对象,这种设计模式叫享元模式,有点像单例模式。使用这种模式,你还可以让本类的实例受控在一定范围内,比如数据库连接池,只有200个连接对象,不会多也不会少。

这样做可以减少无用对象的创建,并且你可以设计这些对象是不可变的(Immutable)、单例的(Singleton)等,总的来说,你对它们的控制更强了。

可以返回原返回类型的任何子类型

使用Collections中的一个静态方法来说明:

// Collections.emptySortedSet 
public static <E> SortedSet<E> emptySortedSet() {
    return (SortedSet<E>) UnmodifiableNavigableSet.EMPTY_NAVIGABLE_SET;
}

// UnmodifiableNavigableSet.EMPTY_NAVIGABLE_SET
private static final NavigableSet<?> EMPTY_NAVIGABLE_SET =
        new EmptyNavigableSet<>();

如你所见,Collections中的emptySortedSet方法的API返回一个SortedSet,这是一个接口,我们从库的使用者的角度来说,我们不知道实际返回的是什么类型,但只知道库肯定会返回一个实现类给我们,直接用就好了。实际上,它返回了UnmodifiableNavigableSet.EMPTY_NAVIGABLE_SET

当然,这是一个老式的规约,因为在1.8以下的Java版本中,不支持在接口中创建静态方法,所以当时的约定是对于一系列Type类型的数据,使用一个不可实例化的Types类来提供一些静态工厂方法创建那些数据的对象,就像Collections一样。当然,现在我们没必要这么写了,因为接口中已经能创建静态方法了,但这种设计仍然有用武之地。

返回的对象可以随着每次调用发生变化

我举个例子,我们知到在排序数据量不大时,使用插入排序会比快速排序快,这是因为我们在分析算法复杂度时经常忽略掉常数系数。我们完全可以使用一个静态工厂方法来动态选择这两种排序。

public static <T> Sorter<T> getSorter(List<T> list){
    return list.size() < 200 
        ? new InsertSorter<T>(list) : new QuickSorter<T>(list);
}

同样,对于使用者来说也不知道这些细节,它们只知道返回了一个Sorter,可以调用Sorter有的方法。

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

用JDBC来举例吧,JDBC只提供了一套接口,定义了一套使用Java访问数据库的标准,对应的实现由数据库厂商来自行根据这套标准设计。

如果你足够细心,应该发现了,DriverConnectionStatementResultSet这四个我们最常用的东西,它们都是接口,当我们使用它们的时候,你就会发现,我们使用的其实是厂商的实现类:

public static void main(String[] args) throws Exception {
    Class.forName("com.mysql.jdbc.Driver");

    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/mysql","root","root");
    System.out.println(conn);
    conn.close();
}

这个实现类在JDBC标准设计之初并未发布,但在这里我们却能通过注册厂商驱动,然后使用一个静态工厂方法来获得它,这给我们的程序带来了很多灵活性,让我们能随意替换一些实现。

下面是getConnection方法的部分代码,选择性观看

 private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    // ...
    // 这里遍历所有已注册的驱动程序
    for(DriverInfo aDriver : registeredDrivers) {
        // 如果驱动程序能够处理这次连接
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                // 连接
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    // 返回connection
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }

        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }
    // ... 
}

更多:桥接模式、ServiceLoader、服务提供者框架

懒得写了
...未完...

标签:...,销毁,name,int,EffectiveJava,static,第二章,public,String
来源: https://www.cnblogs.com/lilpig/p/14838810.html