effective Java

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

2017-04-11  本文已影响61人  666真666

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

1.检查参数的有效性

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

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

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

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 方法参数有效性检查

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

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

    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
    }

构造方法参数有效性检查

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

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

总结

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());
    }

注意:

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

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

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

总结

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

3.谨慎设计方法签名

API 设计技巧总结:

缩短长参数列表的方式:

类参数的使用技巧:

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元素编写文档注释

上一篇下一篇

猜你喜欢

热点阅读