本文主要是介绍带你快速看完9.8分神作《Effective Java》—— 方法篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
🍊 Java学习:Java从入门到精通总结
🍊 Spring系列推荐:Spring源码解析
📆 最近更新:2021年12月16日
🍊 个人简介:通信工程本硕💪、阿里新晋猿同学🌕。我的故事充满机遇、挑战与翻盘,欢迎关注作者来共饮一杯鸡汤
🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!
豆瓣评分9.8的图书《Effective Java》,是当今世界顶尖高手Josh Bloch的著作,在我之前的文章里我也提到过,编程就像练武,既需要外在的武功招式(编程语言、工具、中间件等等),也需要修炼心法(设计模式、源码等等)学霸、学神OR开挂。
我也始终有一个观点:看视频跟着敲代码永远只是入门,从书籍里学到了多少东西才决定了你的上限。
我个人在Java领域也已经学习了近5年,在修炼“内功”的方面也通过各种途径接触到了一些编程规约,例如阿里巴巴的泰山版规约,在此基础下读这本书的时候仍是让我受到了很大的冲激,学习到了很多约定背后的细节问题,还有一些让我欣赏此书的点是,书中对于编程规约的解释让我感到十分受用,并愿意将他们应用在我的工作中,也提醒了我要把阅读JDK源码的任务提上日程。
最后想分享一下我个人目前的看法,内功修炼不像学习一个新的工具那么简单,其主旨在于踏实,深入探索底层原理的过程很缓慢并且是艰辛的,但一旦开悟,修为一定会突破瓶颈,达到更高的境界,这远远不是我通过一两篇博客就能学到的东西。
接下来就针对此书列举一下我的收获与思考。
不过还是要吐槽一下的是翻译版属实让人一言难尽,有些地方会有误导的效果,你比如java语言里extends是继承的关键字,书本中全部翻译成了扩展 就完全不是原来的意思了。所以建议有问题的地方对照英文原版进行语义上的理解。
没有时间读原作的同学可以参考我这篇文章。
文章目录
- 49 检查参数的有效性
- 50 必要时进行保护性拷贝
- 51 谨慎设计方法
- 52 慎用重载
- 53 慎用可变参数
- 54 返回空的数组或集合,不要返回null
- 55 谨慎返回optional
- 56 为所有已公开的API 元素编写文档注释
49 检查参数的有效性
当编写方法或构造方法时,都应该考虑其参数应该有哪些限制。应该把这些限制写到文档里,并在方法体的开头显式检查。
大多数方法和构造方法对于传递给他们的参数有一些限制。例如,索引值必须是非负数,对象引用必须为非null。我们应该在文档里清楚地指明这些限制,并且在方法的最开始进行检查。
如果没有验证参数的有效性,可能会导致违背失败原子性:
- 该方法可能在处理过程中失败,该方法可能会出现费解的异常
- 该方法可以正常返回,会默默地计算出错误的结果
- 该方法可以正常返回,但是使得某个对象处于受损状态,在将来某个时间点会报错
对于public
和protected
方法,要用Java文档的@throws
注解来说明会抛出哪些异常,通常为:IllegalArgumentException
,IndexOutOfBoundsException
或 NullPointerException
,例如:
/*** Returns a BigInteger whose value is (this mod m). This method* differs from the remainder method in that it always returns a* non-negative BigInteger.** @param m the modulus, which must be positive* @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
}
在Java 7中添加的 Objects.requireNonNull
方法灵活方便,因此没有理由再手动执行null检查。该方法返回其输入的值,因此可以在使用值的同时执行null检查:
this.strategy = Objects.requireNonNull(strategy, "strategy");
对于不是public的方法,通常应该使用断言来检查参数:
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;... // Do the computation
}
不同于一般的有效性检查,如果它们没有起到作用,本质上也没有成本开销。
在某些场景下,有效性检查的成本很高,且在计算过程里也已经完成了有效性检查,例如对象列表排序的方法Collections.sort(List)
。
如果List里的对象不能互相比较,就会抛ClassCastException
异常,这正是sort方法该做的事情,所以提前检查列表中的元素是否可以互相比较并没有很大意义。
有些计算会隐式执行必要的有效性检查,如果检查失败则会抛异常,这个异常可能和文档里标明的不同,此时就应该使用异常转换将其转换成正确的异常。
50 必要时进行保护性拷贝
Java是一门安全的语言,它对于缓存区溢出、数组越界、非法指针以及其他内存损坏错误都自动免疫。
但仅管如此,我们也必须保护性地编写程序,因为代码随时可能会遭受攻击。
如果没有对象的帮助,另一个类是不可能修改对象的内部状态的,但对象可能会在无意的情况下提供这样的帮助。例如,下面的代码表示一个不可变的时间周期:
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;}... // Remainder omitted
}
上面代码虽然强制令period 实例的开始时间小于结束时间。然而,Date 类是可变的,很容易违反这个约束:
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!
从Java 8 开始,解决此问题的显而易⻅的方法是使用 Instant
(或LocalDateTime
或 ZonedDateTime
)代替Date
,因为他们是不可变的。但Date
在老代码里仍有使用的地方,为了保护 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(this.start + " after " + this.end);
}
有了新的构造方法后,前面的攻击将不会对Period 实例产生影响。注意:保护性拷⻉是在检查参数的有效性之前进行的,且有效性检查是在拷贝实例上进行的。
这样做可以避免从检查参数开始到拷贝参数之间的时间段内,其他的线程改变类的参数
也被称作 Time-Of-Check / Time-Of-Use 或 TOCTOU攻击
看了之前章节的同学可能有疑问了,这里为什么没用clone
方法来进行保护性拷贝?
答案是:Date
不是final的,所以clone
方法不能保证返回类确实是 java.util.Date
的对象,也可能返
这篇关于带你快速看完9.8分神作《Effective Java》—— 方法篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!