《Effective Java》读书笔记 —— 方法

本文大多数内容适用于构造器,也适用于普通方法,焦点集中在可用性、健壮性和灵活性上。

1.检查参数的有效性

一个原则:应该在发生错误之后尽快检测出错误

不检查参数有效性的后果:

  • 方法在处理的过程中失败,并且产生令人费解的异常
  • 方法正常返回,但计算出错误的结果
  • 方法正常返回,但却使得某个对象处于被破坏的状态,将来在某个不确定的时候引发错误

常见方法参数的一些限制:

  • 索引值必须是非负数
  • 集合类的索引不能大于集合长度-1
  • 对象引用不能为 null

public 方法参数有效性检查

步骤:

  1. 用 Javadoc 的@throw标签在文档中说明违法参数值限制时抛出的异常,异常通常为 IllegalArgumentException、IndexOutOfBoundsException、NullPointerException等
  2. 公有方法内部进行参数有效性检查,并抛出相应异常
    /**
     * Returns a BigInteger whose value is.
     * @param m
     * @return this mod m
     * @throws ArithmeticException if m is less than or equal to 0
     */
    public BigInteger mod(BigInteger m) {
        if (m.signum() <= 0) {
            throw new ArithmeticException("Modulus <= 0:" + m);
        }
        // Do the computation
    }

private/package-private 方法参数有效性检查

非公有方法通常应该使用断言来检查它们的参数有效性

断言与普通有效性检查的区别:

  • 断言如果失败,抛出 AssertionError
  • 如果它们没起到作用,本质上也不会有成本开销
    private static void sort(long a[], int offset, int length) {
        assert a != null;
        assert offset >= 0 && offset <= a.length;
        assert length >= 0 && length <= a.length - offset;
        // next
    }

构造方法参数有效性检查

对于有些参数,方法本身没有用到,但被保存起来供以后使用。比如静态工厂方法、构造方法。检查参数有效性非常重要,避免构造出来的对象违反了这个类的约束条件

不需要检查参数有效性的情况

  • 有效性检查很昂贵,或者不切实际,比如 sort 方法,检查集合中每一个对象是否可以比较
  • 计算会隐式执行必要的有效性检查,比如 sort 方法,如果对象不能比较,就会抛出 ClassCastException

总结

  • 编写方法前,考虑好它的参数有哪些限制
  • 把限制写到方法开头的文档中
  • 通过显式的检查来实施限制

2.必要时进行保护性拷贝

先介绍一个概念,不可变性,之前介绍过,要尽可能创建不可变的类,因为它有很多优点,其中创建不可变类有几条规则:
- 如果类具有指向可变对象的域,必须确保该类的客户端无法获得执行这些对象的引用
- 在构造器中,永远不要用客户端提供的对象引用来初始化这样的域
- 在访问方法中,也不要返回该对象引用
- 在构造器、访问方法和 readObject 方法中请使用保护性拷贝技术

创建 Period 类,由于Date类是可变的,所以外部可能拿到内部的 start 和 end 信息,进而会修改这个信息

    public final class Period {
        private final Date start;
        private final Date end;

        public Period(Date start, Date end) {
            this.start = start;
            this.end = end;
        }
        
        public Date start() {
            return start;
        }
        
        public Date end() {
            return end;
        }
    }

对于构造器的每个可变参数进行保护性拷贝

为了避免内部信息被攻击,对于构造器的每个可变参数进行保护性拷贝,创建新的对象,而不是使用客户端传入的对象

    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
    }

注意:

  • 保护性拷贝是在检查参数有效性之前进行
  • 保护性拷贝,没有使用 clone 方法,因为 Date 是非 final 的,clone 方法不能保证一定会返回 Date 对象,可能返回出于恶意目的而设计的不可信子类的实例

使访问方法返回可变内部域进行保护性拷贝

    public Date start() {
        return new Date(start.getTime());
    }

    public Date end() {
        return new Date(end.getTime());
    }

总结

  • 参数(和返回值)的保护性拷贝不仅仅针对不可变类,每当允许客户提供的对象进入内部数据结构中,则有必要考虑一下,客户提供的对象是否有可能是变化的
  • 长度非零的数组总是可变的,在把内部数组返回给客户端时,总要进行保护性拷贝
  • 真正的启示:尽可能使用不可变对象,不必再担心保护性拷贝
  • 对于Date,通常不要直接使用Date的引用,而是使用Date.getTime()返回的long基本类型作为时间的表示,防止Date对象的可变性导致的问题

最后,如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝这些组件,如果拷贝的成本受到限制,并且类信任它的客户端不会不恰当地修改组件,就可以在文档中指明客户端的职责是不得修改受到影响的组件,以此来代替保护性拷贝。

3.谨慎设计方法签名

API 设计技巧总结:

  • 谨慎地选择方法的名称
    • 易于理解
    • 与同一个包中的其他名称风格一致的名称
    • 选择与大众认可的名称
  • 不要过于追求提供便利的方法
  • 避免过长的参数列表
    • 四个参数或更少

缩短长参数列表的方式:

  • 把方法分解成多个方法,每个方法只需要这些参数的一个子集
  • 创建辅助类,用来保存参数的分组,一般是静态成员类,如果一个频繁出现的参数序列可以被看作代表了某个独特实体,则建议使用这种方式
  • 使用Builder模式,参数很多,且有些是可选的。

类参数的使用技巧:

  • 对于参数类型,要优先使用接口而不是类
    • 比如,没有理由使用 HashMap 作为参数,应当使用 Map 接口作为参数
  • 对于 boolean 参数,要优先使用两个元素的枚举类型
    • 代码更易于阅读和编写

4.慎用重载

重载方法的选择是静态的,是在编译时做出决定的,只能调用与此明确对应的重载方法,而不是其父类或者子类。与此不同的是覆盖方法,覆盖方法是动态的,是在运行时决定要调用子类还是父类的方法。

看一个例子。

private class CollectionClassifier {
    
    public static String classify(Set<?> s) {
        return "Set";
    }

    public static String classify(List<?> s) {
        return "List";
    }

    public static String classify(Collection<?> s) {
        return "Unknown Collection";
    }
    
    public static void main(String[] args) {
        Collection<?>[] collections = {
                new HashSet<String>(),
                new ArrayList<BigInteger>(),
                new HashMap<String, String>().values()
        };
        
        for (Collection<?> c : collections) {
            System.out.println(classify(c));  
        }
    }
}

结果,打印了三次 “Unknown Collection”,对于for循环的三次迭代,只能调用在编译时确定的参数为 Collection<?> 的方法。

解决方案:用单个方法替换三个重载方法

    public static String classify(Collection<?> c) {
        return c instanceof Set ? "Set" :
                c instanceof List ? "List" : "Unknown Collection";
    }

普通方法避免重载

具体,对于write方法,如果就有变形,不应该使用重载,而是增加诸如writeBoolean、writeInte这样的签名方法。

构造器方法避免重载

不能使用不同名称的构造器,但可以选择导出静态工厂,而不是重载构造器

总结

“能够重载方法”并不意味着“应该重载方法”。一般情况下,对于多个具有相同参数数目的方法来说,应该尽量避免重载方法。

5.慎用可变参数

可变参数:可变参数方法接口0个或者多个指定类型的参数。可变参数机制通过先创建一个数组,数组的大小为调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法。

可变参数性能问题:可变参数方法的每次调用功能会导致进行一次数组分配和初始化。

只有对于参数数目不确定的情况,才会使用可变参数。

6.返回零长度的数组或者集合,而不是 null

对于一个返回null而不是零长度的数组或者集合的方法,编写客户端的程序员很可能会忘记这种专门的代码来处理null返回值。

返回零长度数组不会增加开销,零长度数组是不可变的,是自由共享的。

7.为所有导出的API元素编写文档注释

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容