Effective Java 第三版——62. 当有其余更合适的类型时就不用字符串

Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,因此JDK 最好下载 JDK 9以上的版本。java

Effective Java, Third Edition

62. 当有其余更合适的类型时就不用字符串

字符串被设计用来表示文本,它们在这方面作得很好。由于字符串是如此常见,而且受到开发语言的良好支持,因此很天然地倾向会将字符串用于其余目的,而不是它们设计的本来目的。这一条目讨论了一些不该该使用字符串作的事情。git

字符串是其余值类型的不良替代品。当一段数据从文件、网络或键盘输入进入程序时,它一般是字符串形式的。有一种天然的倾向是这样的,可是这种倾向只有在数据本质上是文本的状况下才合理。若是是数值类型,则应将其转换为适当的数值类型,如int、float或BigInteger。若是是“是”或“否”问题的答案,则应将其转换为适当的枚举类型或boolean值。更一般地说,若是有合适的值类型,不管是基本值仍是对象引用,都应该使用它;若是没有,你应该编写一个。虽然这条建议彷佛很明显,但常常被违反。程序员

字符串是枚举类型的不良替代品。 正如条目 34中所讨论的,枚举使得枚举类型常量比字符串好得多。github

字符串是聚合类型的不良替代品。 若是实体具备多个组件,则将其表示为单个字符串一般是个坏主意。 例如,这里是来自真实系统的一行代码——标识符名称已被更改:安全

// Inappropriate use of string as aggregate type
String compoundKey = className + "#" + i.next();

这种方法有许多缺点。 若是用于分隔属性的字符出如今某个属性中,结果可能会产生混乱。 要访问单个属性,必须解析字符串,这很慢,很乏味且容易出错。 不能提供equals,toString或compareTo方法,但必须接受String类提供的行为。 更好的方法是编写一个类来表示聚合,一般是私有静态成员类(条目 24)。网络

字符串是功能的不良替代品。 有时,字符串用于授予对某些功能的访问权限。 例如,考虑ThreadLocal的设计。 这样的工具提供了每一个线程都有本身值的变量。 从版本1.2开始,Javal类库就有了一个ThreadLocal工具,但在此以前,程序员必须本身动手来实现。 当多年前遇到设计这样一个工具的任务时,几我的独立地想出了相同的设计,其中客户提供的字符串键用于识别每一个线程局部变量:app

// Broken - inappropriate use of string as capability!
public class ThreadLocal {
    private ThreadLocal() { } // Noninstantiable

    // Sets the current thread's value for the named variable.
    public static void set(String key, Object value);

    // Returns the current thread's value for the named variable.
    public static Object get(String key);
}

这种方法的问题是,字符串键表示线程本地变量的共享全局命名空间。为了使这种方法有效,客户端提供的字符串键必须是唯一的;若是两个客户端各自决定为它们的线程本地变量使用相同的名称,它们无心中共享一个变量,这一般会致使两个客户端都失败。并且,安全性不好。恶意客户端能够故意使用与另外一个客户端相同的字符串密钥来非法访问另外一个客户机端数据。工具

能够经过用一个不可伪造的键(有时称为功能)替换字符串来修复这个API:线程

public class ThreadLocal {
    private ThreadLocal() { }    // Noninstantiable

    public static class Key {    // (Capability)
        Key() { }
    }

    // Generates a unique, unforgeable key
    public static Key getKey() {
        return new Key();
    }

    public static void set(Key key, Object value);

    public static Object get(Key key);
}

虽然这解决了基于字符串的API的这两个问题,可是能够作得更好。再也不真正须要静态方法。它们能够变成键上的实例方法,此时再也不是线程局部变量的键:而是线程局部变量。此时,顶层类再也不作任何事情,能够删除它,并将嵌套类重命名为ThreadLocal:设计

public final class ThreadLocal {
    public ThreadLocal();
    public void set(Object value);
    public Object get();
}

此API不是类型安全的,由于当从线程局部变量中检索它时,必须将值从Object转换为其实际类型。原始的基于字符串的API类型安全是不可能实现的,基于键的API类型安全也是很难实现的,但经过使ThreadLocal成为参数化类(第29项)来使这种API类型安全是一件简单的事情:

public final class ThreadLocal<T> {
    public ThreadLocal();
    public void set(T value);
    public T get();
}

粗略地说,这是java.lang.ThreadLocal提供的API。 除了解决基于字符串的API的问题以外,它还比任何基于键的API更快,更优雅。

总而言之,当存在或能够编写更好的数据类型时,避免将对象表示为字符串的天然倾向。 使用不当,字符串比其余类型更麻烦,更灵活更差,速度更慢,更容易出错。 字符串一般被滥用的类型包括基本类型,枚举类型和聚合类型。

相关文章
相关标签/搜索