资讯专栏INFORMATION COLUMN

《Effective Java》,关于方法

worldligang / 2046人阅读

摘要:检查参数的有效性每当编写方法或者构造器时,应该考虑它的参数有哪些限制。为了保护实例的内部信息避免受到这种攻击,对于构造器的每个可变参数进行保护性拷贝是必要的,并且使用备份对象作为实例的组件,而不是使用原始的对象。

检查参数的有效性

每当编写方法或者构造器时,应该考虑它的参数有哪些限制。应该把这些限制写到文档中,并且在这个方法体开头处,通过显示的检查来实施这些限制。养成这样的习惯非常重要。

必要时进行保护性拷贝
public final class Period {
    private final Date start;
    private final Date end;
    
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0) {
            throw new IllegalArgumentException(start + " after " + end);
        }
        this.start = start;
        this.end = end;
    }
    
    public Date start() {
        return start;
    }
    
    public Date end() {
        return end;
    }

}

因为Date类本身时可变的,所以,

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // 这个操作把实例的内部信息修改了。

为了保护Period实例的内部信息避免受到这种攻击,对于构造器的每个可变参数进行保护性拷贝是必要的,并且使用备份对象作为Period实例的组件,而不是使用原始的对象。

public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    
    if (this.start.compareTo(this.end) > 0) {
        throw new IllegalArgumentException(start + " after " + end);
    }
}

注意,保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是原始的对象。

但是改变Period实例仍然是有可能的,因为它的访问方法提供了对其内部成员的访问能力。

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // 这个操作把实例的内部信息修改了。

为了防御这第二种攻击,只需改这两个访问方法,使它返回可变内部域的保护性拷贝即可。

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

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

在内部组件被返回给客户端之前,对它们进行保护性拷贝也是同样的道理。

只要有可能,都应该使用不可变的对象作为对象内部的组件,这样就不必再为保护性拷贝操心。

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

谨慎设计方法签名 谨慎地选择方法的名称

首要目标是选择易于理解的。

第二目标是选择与大众认可的名称相一致的名称。

不要过于追求提供便利的方法

每个方法都应该尽其所能。方法太多会使类难以学习、使用、文档化、测试和维护。

对于类和接口所支持的每个动作,都提供一个功能齐全的方法。

避免过长的参数列表

目标是四个参数,或者更少。

有三种方法可以缩短过长的参数列表。
第一种是把方法分解成多个方法,每个方法只需要这些参数的一个子集。
第二种是创建辅助类,用来保存参数的分组。
第三种是从对象构建到方法调用都采用Builder模式。

对于参数类型,要优先使用接口而不是类

如果使用的是类而不是接口,则限制了客户端只能传入特定的实现,如果碰巧输入的数据是以其他的形式存在,就会导致不必要的、可能非常昂贵的拷贝操作。

对于boolean参数,要优先使用两个元素的枚举类型

它是代码更易于阅读和编写,也使以后更易于添加更多的选项。

慎用重载

下面的程序试图根据一个集合是Set、List,还是其他的集合类型来分类。

public class CollectionClassifier {
    public static String classify(Set set) {
        return "Set";
    }
    
    public static String classify(List list) {
        return "List";
    }
    
    public static String classify(Collection collection) {
        return "Unknow Collection";
    }
    
    public static void main(String[] args) {
        Collection[] collections = {
            new HashSet(),
            new ArrayList(),
            new HashMap().values()
        };
        
        for(Collection collection : collections) {
            System.out.println(classify(collection));
        }
    }
}

可能期望会打印“Set”,接着是“List”,以及“Unknow Collection”,实际上不是这样。而是“Unknow Collection”打印三次。

因为classify方法被重载(overload)了,而要调用哪个重载方法是在编译时做出决定的。对于for循环中的三次迭代,参数的编译时类型都是相同的,Collection。每次迭代的运行时类型都是不同的,但这并不影响对重载方法的选择。因为该参数的编译时类型为Collection,所以,唯一合适的重载方法是第三个,classify(Collection),在循环的每次迭代中,都会调用这个重载方法。

对于重载方法(overload method)的选择是静态的,而对于被覆盖的方法(overriden method)的选择则是动态的。

选择被覆盖的方法的正确版本是在运行时的,选择的依据时被调用方法所在对象的运行时类型。

再看看下面这个程序。

class Wine {
    String name() {
        return "wine";
    }
}

class SparklingWine extends Wine {
    @Override
    String name() {
        return "sparkling wine";
    }
}

class Champagne extends SparklingWine {
    @Override
    String name() {
        return "champagne";
    }
}

public class Overriding {
    public static void main(String[] args) {
        Wine[] wines = {
            new Wine(),
            new SparklingWine(),
            new Champagne()
        };
        
        for(Wine wine : wines) {
            System.out.println(wine.name());
        }
    }
}

这个程序打印“wine”、“sparkling wine”和“champagne”。

可以对第一个程序修改一下,用单个方法替换三个重载的classify方法,如下。

public static String classify(Collection collection) {
    return collection instanceof Set ? "Set" :
           collection instanceof List ? "List" : "Unknow Collection";
}

因为覆盖机制时规范,而重载机制是例外。

到底怎样才算是胡乱使用重载机制呢?这个问题仍有争议。安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。

慎用可变参数

Java 1.5发行版本中增加了可变参数方法,一般称作variable arity method(可匹配不同长度的变量方法)[JLS,8.4.1]。

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

static int sum(int... args) {
    int sum = 0;
    for (int arg : args) {
        sum += arg;
    }
    return sum;
}

当真正需要让一个方法带有不定数量的参数时,可变参数就非常有效。可变参数是为printf而设计的。printf和反射机制都从可变参数中极大地受益。

在重视性能地情况下,需要小心。可变参数方法的每次调用都会导致进行一次数组分配和初始化。

需要可变参数的灵活性,但又无法承受性能成本,可以这么做。

public void foo() {}
public void foo(int a1) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int a3) {}
public void foo(int a1, int a2, int a3, int... rest) {}

在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是不应该被过度滥用。如果使用不当,会产生混乱的结果。

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

对于一个返回null而不是零长度数组或者集合的方法,几乎每次调用该方法都需要曲折的处理方式。这样很容易出错,因为编写客户端程序的程序员可能会忘记写专门的代码来处理null返回值。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70055.html

相关文章

  • Effective Java》,关于异常

    摘要:只针对异常的情况才使用异常下面展示两种遍历数组元素的实现方式。第一种方式在访问数组边界之外的第一个数组元素时,用抛出捕获忽略异常的手段来达到终止无限循环的目的。这种做法被称为异常转译。 只针对异常的情况才使用异常 下面展示两种遍历数组元素的实现方式。 try { int i = 0; while(true) range[i++].climb(); } c...

    CoorChice 评论0 收藏0
  • 学习Java必读的10本书籍

    摘要:学习编程的本最佳书籍这些书涵盖了各个领域,包括核心基础知识,集合框架,多线程和并发,内部和性能调优,设计模式等。擅长解释错误及错误的原因以及如何解决简而言之,这是学习中并发和多线程的最佳书籍之一。 showImg(https://segmentfault.com/img/remote/1460000018913016); 来源 | 愿码(ChainDesk.CN)内容编辑 愿码Slo...

    masturbator 评论0 收藏0
  • Java 高效编程(Effective Java)中文第三版

    摘要:来源前条来源一书英文版已经出版,这本书的第二版想必很多人都读过,号称四大名著之一,不过第二版年出版,到现在已经将近年的时间,但随着,,,甚至的发布,语言发生了深刻的变化。译者在这里第一时间翻译成中文版。供大家学习分享之用。 来源:sjsdfg/effective-java-3rd-chinese前 51 条来源:Effective Java, Third Edition 《Effec...

    ysl_unh 评论0 收藏0
  • effective java 观后感

    摘要:这本书是我第一次买的,从买来至今整本书还没有看完,只看了一半,原因是个人比较懒,而且玩的心比较大,经过这么多年的沉淀,终于可以偷点时间写下对于这本书的观后感了整本书给我的感觉不像是一个技术书,更多的是讲解一些实用技巧,而对于我这个职场菜鸟来 effective Java 这本书是我第一次买的, 从买来至今整本书还没有看完, 只看了一半, 原因是个人比较懒,而且玩的心比较大,经过这么多年...

    calx 评论0 收藏0
  • Java 学习资料总结

    摘要:三进阶阶段这个阶段主要是靠我们自己学习总结,可以通过前辈们的博客或者自己研究源码,这些非常有利于我们快速的成长。让自己保持永远学习的精神。五零基础学习资料最后给大家准备了一份不错的学习资源,里面有很多学习视频和资料,后台回复资源,即可获取。 showImg(https://segmentfault.com/img/bVbauV8?w=1212&h=816); 前两次给大家分享了关于 j...

    FleyX 评论0 收藏0

发表评论

0条评论

worldligang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<