Effective Java(第三版) 学习笔记 - 第八章 方法 Rule49~Rule56

目录

Rule49 检查参数的有效性

Rule50 必要时进行保护性拷贝

Rule51 谨慎设计方法签名

Rule52 慎用重载

 Rule53 慎用可变参数

Rule54 返回零长度的数组或集合、而不是null

Rule55 谨慎返回Optional

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


Rule49 检查参数的有效性

没什么好说的,做过项目的都知道,对前端或对外提供的接口优先检查参数。而且一般尝尝搭配注解来实现检查以及返回msg,@NotNull、@NotEmpty、@Size、@Max、@Min、@Pattern等javax.validation.constraints包下。接口入参加上@Validated注解进行校验、@BindingResult获得验证结果。

不太推荐书中的抛IllegalArgumentException、NullPointerException、IndexOutOfBoundsException等异常方式处理。一般都需要让调用方精准的知道哪些入参除了什么问题,只是简单的抛异常不太合适。

Rule50 必要时进行保护性拷贝

什么是保护性拷贝,简单来说就是防止方法的可变入参,在被调用方法之外发生的改变,进而引起了被调用方产生了异常或者破坏了接受入参时的有效性检查。所以我们在方法的一开始,用拷贝来保护我们的入参不发生改变,然后再利用有效性检查,检查拷贝之后的对象、而不是原始对象。

※值得注意的是,如果参数类型是可以被不信任方子类化的参数,请不要使用clone方法进行保护性拷贝。

// Broken "immutable" time period class (Pages 231-3)
public final class Period {
    private final Date start;
    private final Date end;

// 非保护性拷贝
    /**
     * @param  start the beginning of the period
     * @param  end the end of the period; must not precede start
     * @throws IllegalArgumentException if start is after end
     * @throws NullPointerException if start or end is null
     */
    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;
    }

    public String toString() {
        return start + " - " + end;
    }

// 保护性拷贝
//    // Repaired constructor - makes defensive copies of parameters (Page 232)
//    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(
//                    this.start + " after " + this.end);
//    }
//
//    // Repaired accessors - make defensive copies of internal fields (Page 233)
//    public Date start() {
//        return new Date(start.getTime());
//    }
//
//    public Date end() {
//        return new Date(end.getTime());
//    }

    // Remainder omitted
}
// Two attacks on the internals of an "immutable" period (232-3)
public class Attacks {
    public static void main(String[] args) {
        // Attack the internals of a Period instance  (Page 232)
        Date start = new Date();
        Date end = new Date();
        Period p = new Period(start, end);
        end.setYear(78);  // Modifies internals of p!
        System.out.println(p);

        // Second attack on the internals of a Period instance  (Page 233)
        start = new Date();
        end = new Date();
        p = new Period(start, end);
        p.end().setYear(78);  // Modifies internals of p!
        System.out.println(p);
    }
}

-----打印结果、绕过了开始时间要小于结束时间限制
Mon Jul 19 15:40:37 CST 2021 - Wed Jul 19 15:40:37 CST 1978
Mon Jul 19 15:40:37 CST 2021 - Wed Jul 19 15:40:37 CST 1978

相比这一点,书中针对这条提到的另外一条信息更需要我们注意:

Java8及之后的版本,更加建议使用java.time.Instant、java.time.LocalDateTime、java.time.ZonedDateTime去处理日期类型,因为它们内部定义的时间存储全部是final的,都实现了Java8新增的TemporalAccessor时间存储器。

Rule51 谨慎设计方法签名

列举一下书中提到的几点:

  • 谨慎地选择方法的名称。
  • 不要过于追求提供便利的方法。
  • 避免过长的参数列表。
  • 对于参数类型,优先使用接口而不是类。
  • 对于boolean参数,要优先使用两个参数的枚举。

※最后一条不是完全赞同

Rule52 慎用重载

这条并不是指完全不让用重载,而是不要胡乱的使用重载。问题是,目前并没有一个明确的说明,什么是胡乱的重载。保守一点的策略是:永远不要导出两个具有相同参数数目的重载方法。避免方法也很简单:起不同的方法名称。例如:ObjectOutputStream

 Rule53 慎用可变参数

可变参数(int... args),支持零个或多个入参。可变参数会先创建一个数组,数组大小为调用方的参数数量,然后将参数放入数组,最后数组传递给方法。

// Sample uses of varargs (Pages 245-6)
public class Varargs {
    // Simple use of varargs (Page 245)
    static int sum(int... args) {
        int sum = 0;
        for (int arg : args)
            sum += arg;
        return sum;
    }

//    // The WRONG way to use varargs to pass one or more arguments! (Page 245)
//    static int min(int... args) {
//        if (args.length == 0)
//            throw new IllegalArgumentException("Too few arguments");
//        int min = args[0];
//        for (int i = 1; i < args.length; i++)
//            if (args[i] < min)
//                min = args[i];
//        return min;
//    }

    // The right way to use varargs to pass one or more arguments (Page 246)
    static int min(int firstArg, int... remainingArgs) {
        int min = firstArg;
        for (int arg : remainingArgs)
            if (arg < min)
                min = arg;
        return min;
    }

    public static void main(String[] args) {
        System.out.println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        System.out.println(min(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }
}

如书中这个用例,获取最小值时,首先得保证起码有一个。如果直接用可变参数接受入参,无法避免可变参数0个的场景,建议考虑优先用一个固定参数接受一个值,然后再用可变参数接受剩余的内容。

至于书中提到的性能上的影响,一般项目中、有比这个更加容易引起性能的地方,这样的写法需要考虑性能的地方优先级较低。除非是基础底层组件,需要特别在意性能带来的影响,如果是这样也可以考虑使用EnumSet替代位域来减少开销。

※位域请看 Rule36 用EnumSet代替位域

Rule54 返回零长度的数组或集合、而不是null

这条感觉也没啥好说的,一般项目中都有这条的规约。包括从数据库中查询列表没有时,默认给个空列表。目的是为了防止使用端出现空指针异常,但是这并不是说,我们在使用查询结果时,就可以避免空判断了。

Rule55 谨慎返回Optional

关于Optional介绍,可以先看这篇《Java8工具类Optional》。

在Java8之前,关于无法返回值,要么返回null、要么抛出异常(集合还是需要参照上一条返回一个空的集合而不是null)。Java8之后,除了那两种情况,新增了一种选项就是Optional。它通过对对象进行一次包装,然后提供了例如isPresent()、ifPresent()、filter()、map()等方法帮助我们可以进行链式编程。

但是Optinal不是万能的,在使用上也需要一定的注意(最好先看看源码实现)。而且坦白的讲,目前在实际项目中,还没看见有人返回Optional的写法,一方面是某些场合的确不适合,但估计更多的是还未普及形成日常开发习惯吧。

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

虽然目前各种开发工具都可以自定义注释模板,但是里面核心的内容都是需要人工手写的。遗憾的是、往往项目中都不会太过于关注这一点。

  • 文档注释在源码或生成的文档中都应该是易于阅读的。
  • 针对重载的方法,需要有侧重的描述区别,而不是完全相同的描述。
  • 泛型和方法需要在注释中申明,参数的类型。
  • 枚举需要在文档中说明常量的含义。
  • 注解需要在文档中,需要说明所有成员。
  • 类或者静态方法是否线程安全,需要在文档中标明线程安全级别。

但是起码先从写注释做起,再谈写的如何吧。

本文技术菜鸟个人学习使用,如有不正欢迎指出修正。xuweijsnj

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值