Effective.Java第45-55条(规范相关)
45. 明智谨慎地使用Stream
46. 优先考虑流中无副作用的函数
47. 优先使用Collection而不是Stream作为方法的返回类型
48. 谨慎使用流并行
49. 检查参数有效性
大多数方法和构造方法对可以将哪些值传到其对应参数有一些限制。例如:索引必须是非负数、对象引用必须是非null。你应该清楚地在文档中记载所有这些限制,并在方法主体的开头用检查来强制执行。
每次编写方法或构造方法时,都应该考虑对其参数存在哪些限制。应该记住这些限制,并在方法体的开头使用显示检查来强制执行这些限制。养成这样的习惯很重要。在第一次有效性检查失败时,它所需要的工作量将会得到对应的回报。
阿里规约有两条:
【参考】 下列情形,需要进行参数校验:
1) 调用频次低的方法。
2) 执行时间开销很大的方法。 此情形中, 参数校验时间几乎可以忽略不计,但如果因为参
数错误导致中间执行回退,或者错误,那得不偿失。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
5) 敏感权限入口。
【参考】 下列情形, 不需要进行参数校验:
1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底
层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所
以 DAO 的参数校验,可以省略。
3) 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参
数已经做过检查或者肯定不会有问题,此时可以不校验参数。
比如JDK7带的Objects的验证null的方法,一般用此方法显示检查是否为空指针
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
比如DateTimeFormatterBuilder类的如下方法:
public DateTimeFormatterBuilder appendPattern(String pattern) {
Objects.requireNonNull(pattern, "pattern");
parsePattern(pattern);
return this;
}
例子如下:
public static void main(String[] args) {
String string = null;
Objects.requireNonNull(string, "string is null");
}
结果:
Exception in thread "main" java.lang.NullPointerException: string is null
at java.util.Objects.requireNonNull(Objects.java:228)
at Client.main(Client.java:9)
补充:有时候我们修改元素的时候通过get方法传入ID进行修改,为了防止传入不存在的ID,我们可以在Action验证查到的bean
public String update() {
employeecharge = employeeChargeService.findById(id); // 检查是否存在
Objects.requireNonNull(employeecharge, "尝试访问不存在的数据" + id); return "update";
}
50. 必要时进行防御性拷贝
必须防御性地编写程序,假定类的客户端尽力摧毁类的不变量。
比如我们编写表示一个不可变的时间期间类,如下:
import java.util.Date; public class Period { private final Date start;
private final Date end; public Period(Date start, Date end) {
// >0表示在其后面
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + " after " + end);
} this.start = start;
this.end = end;
} public Date getStart() {
return start;
} public Date getEnd() {
return end;
} }
咋一看没问题,date设置为final,但是只是引用不可变,由于Date是可变类,所以可以利用这一特性进行改变,如下:
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
start.setDate(start.getDate() + 1);
从Java8开始,解决这种问题可以使用Instant(或LocalDateTime或ZonedDateTime)代替Date,因为这些类是不可变类。Date已经过时,在新代码中不应再使用。
// 类似于构造者模式
DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder().appendPattern("yyyy/MM=dd HH:mm:ss")
.toFormatter();
LocalDateTime localDate = LocalDateTime.parse("2019/06=24 20:01:01", dateTimeFormatter);
System.out.println(localDate); LocalDate now = LocalDate.now();
System.out.println(now); LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
JDK7中解决方法:
import java.util.Date; public class Period { private final Date start;
private final Date end; public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime()); // >0表示在其后面
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + " after " + end);
}
} public Date getStart() {
return new Date(start.getTime());
} public Date getEnd() {
return new Date(end.getTime());
} }
新的构造方法以及get方法将不会改变其值。注意:防御性拷贝是在参数有效性之前进行的,有效性检查是在拷贝上而不是在原始实例上进行的。
在可能的情况下,应该使用不可变对象作为对象的组件,这样就不必担心防御性拷贝。
51. 仔细设计方法签名
仔细设计方法名名称。主要目标是选择与同一包名中的其他名称一致且易于理解的名称,其次是选择与广泛的共识一致的名称。避免使用较长的方法名。
不要过分地提供方便的方法。
避免过长的参数列表。目标是四个或者更少。相同类型参数的长序列尤其有害,程序员记不住参数的意义且编译期间如果顺序错误也能正常编译。三种方法可以缩短过长的参数列表:将方法分解为多个方法;创建辅助类(静态内部类)来保存数组;从对象构造方法调用采用Build构造模式。
对于参数类型,优先选择接口而不是具体的类。如果有合适的接口来定义一个参数用接口定义。
与布尔型参数相比,优先选择两个元素枚举类型。
52. 明智审慎地使用重载
重载方法之间的选择是静态的,而重写方法的选择是动态的。
如下重载代码:
package zd.dms.test; /**
* 静态分配例子
*
* @author Administrator
*
*/
public class StaticDispatch {
static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public static void sayHello(Human human) {
System.out.println("human");
} public static void sayHello(Man man) {
System.out.println("man");
} public static void sayHello(Woman woman) {
System.out.println("woman");
} public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
sayHello(man);
sayHello(woman);
}
}
结果:
human
human
解释:从结果看出执行的参数类型是Human,为什么会选择Human的重载?
Human man = new Man();
我们把上面代码的Human称为变量的静态类型(Static Type),或者叫做外观类型(Apparent Type),后面的Man类称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
解释了这两个概念,main()方法的两次sayHello()方法调用,使用哪个重载版本完全取决于传入参数的数量和数据类型。代码中定义了 两个静态类型相同但实际类型不同的变量,但虚拟机(准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型来作为判定依据的。并且静态类型是编译器可知的,因此,在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了sayHello(Human)作为调用目标。 通过这个例子,我们明白了永远不要重写一个方法,参数个数完全相同,类型不同但是类型具有父子关系。
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。另外,编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是唯一的,往往只能确定一个更加合适的版本。
因为重写是规范,而重载是例外,应该避免混淆使用重载。一个最好的办法是永远不要写具有两个相同参数数量的重载。
53. 明智审慎地使用可变参数
可变参数方法正式名称为可变的参数数量方法,接受零个或多个指定类型的参数。可变参数机制首先创建一个数组,其大小是在调用位置传递的参数数量,然后将参数放入数组中,最后将数组传递给方法。
当需要使用可变数量的参数定义方法时,可变参数非常有用。在使用可变参数前加上必需参数,并注意可变参数带来的性能后果。
54. 返回空的数组或集合,不要返回null
在几乎使用null代替空数组或集合时,有可能造成一定的影响,因为调用方法的客户端可能不知道来处理返回null的情况。此外,用null代替空容器会使得返回容器的方法的实现变得复杂。
有时可能会觉得返回空集合或者数组会有分配空间的开销。这里有两个误区,首先,除非测量结果表明所讨论的分配是性能问题的真正原因,否则不宜担心此级别的性能。第二,可以在不分配空集合和数组的情况下返回它们。如果有数据证明分配集合会损害性能,可以通过重复返回相同的不可变空集合来避免分配,因为不可变对象可以自由共享。Collections.emptyList(),Collections.emptySet(),Collections.emptyMap()。
List<Object> emptyList = Collections.emptyList();
源码如下:
@SuppressWarnings("rawtypes")
public static final List EMPTY_LIST = new EmptyList<>(); @SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
数组的情况与集合的情况相同,永远不要返回null,而是返回长度为零的数组。如果认为零长度数组会损害性能,则可以重复返回相同的零长度数组,如下:
private static final Object[] OBJECTS = new Object[0];
总之,永远不要返回null来代替空数组或集合。它使你的API更难以使用,更容易出错,并且没有性能优势。
55. 明智审慎地返回Optional
Effective.Java第45-55条(规范相关)的更多相关文章
- <<Effective Java>> 第四十三条
<<Effective Java>> 第四十三条:返回零长度的数组或者集合,而不是null 如果一个方法的返回值类型是集合或者数组 ,如果在方法内部需要返回的集合或者数组是零长 ...
- Effective.Java第56-66条(规范相关)
56. 为所有已公开的API元素编写文档注释 要正确地记录API,必须在每个导出的类.接口.构造方法.方法和属性声明之前加上文档注释.如果一个类是可序列化的,还需要记录它的序列化形式. 文档注释在源 ...
- [Effective JavaScript 笔记]第55条:接收关键字参数的选项对象
53节建议保持参数顺序的一致约定对于帮助程序员记住每个参数在函数调用中的意义很重要.参数较少这个主意不错,但如果参数过多后,就出现麻烦了,记忆和理解起来都不太容易. 参数蔓延 如下面这些代码: var ...
- Effective java读书札记第一条之 考虑用静态工厂方法取代构造器
对于类而言,为了让client获取它自身的一个实例,最经常使用的方法就是提供一个共同拥有的构造器. 另一种放你发,也应该子每一个程序猿的工具箱中占有一席之地.类能够提供一个共同拥有的静态 工厂方法.它 ...
- <<Effective Java>>之善用组合而不是继承
使用JAVA这门OO语言,第一要义就是,如果类不是专门设计来用于被继承的就尽量不要使用继承而应该使用组合 从上图2看,我们的类B复写了类A的add喝addALL方法,目的是每次调用的时候,我们就能统计 ...
- Java异常(二) 《Effective Java》中关于异常处理的几条建议
概要 本章是从<Effective Java>摘录整理出来的关于异常处理的几条建议.内容包括:第1条: 只针对不正常的情况才使用异常第2条: 对于可恢复的条件使用被检查的异常,对于程序错误 ...
- 阅读《Effective Java》每条tips的理解和总结(1)
<Effective Java>这本书的结构是90来条tips,有长有短,每条tip都值的学习.这里根据对书中每条tip的理解做简短的总结,方便日后回顾.持续更新~ 1. 考虑用静态方法代 ...
- Effective Java 学习笔记之第七条——避免使用终结(finalizer)方法
避免使用终结方法(finalizer) 终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的. 不要把finalizer当成C++中析构函数的对应物.java中,当对象不 ...
- Effective Java 第三版——45. 明智审慎地使用Stream
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- SpringMVC拦截器执行流程
1:MyInterceptor1.MyInterceptor2这2个拦截器都放行 MyInterceptor1......preHandleMyInterceptor2......preHandle ...
- android studio学习---标签页分离,满足查同一个文件的不同部分
分离一个标签窗口:右键标签页,打开上下文菜单,选择Split Vertically or Split Horizontall改变分离窗口的摆放方式:右键标签页,打开上下文菜单,选择 Change Sp ...
- Android开发之EditText多行文本输入
<EditText android:id="@+id/add_content" android:layout_width="fill_parent" an ...
- Django框架(四)-- 路由控制:有名/无名分组、反向解析、路由分发、名称空间、伪静态、APPEND_SLASH、不同版本的Django区别、Django虚拟环境搭建
路由控制 一.简单路由配置 url(r'^booklist$', views.booklist) 第一个参数是正则表达式,第二个参数是视图函数 每个正则表达式前面的'r' 是可选的但是建议加上.它告诉 ...
- SpringBoot 定时任务篇
一. 基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响. 1.创建定时器 使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码便可完 ...
- 利用python制作在线视频播放器遇到的一些问题
经过前期的调研,我准备使用PyQT+PyAV+PyAudio+pillow.期间也尝试过使用ffmpeg-python,但最后还是选择了av这个更底层,自由度更大的库来完成音视频的处理. ==== ...
- python实现生产者消费者模型
生产者消费之模型就是,比如一个包子铺,中的顾客吃包子,和厨师做包子,不可能是将包子一块做出来,在给顾客吃,但是单线程只能这麽做,所以用多线程来执行,厨师一边做包子,顾客一边吃包子,当顾客少时,厨师做的 ...
- 文件转换神器pandoc
pandoc :可以在各种文件之间进行相互转化.比如从md文件转为pdf,docx转为tex文件,html文件和txt文件相互转化,等等. 在终端启用命令行执行命令. 我最近要完成的任务是把有很多个 ...
- Iconfont技术
什么是 IconFont 顾名思义,IconFont 就是字体图标.严格地说,就是一种字体,但是,它们不包含字母或数字,而是包含符号和字形.您可以使用 CSS 设置样式,就像设置常规文本一样,这使得 ...
- JS三座大山再学习 ---- 原型和原型链
本文已发布在西瓜君的个人博客,原文传送门 ## 前言 西瓜君之前学习了JS的基础知识与三座大山,但之后工作中没怎么用,印象不太深刻,这次打算再重学一下,打牢基础.冲鸭~~ 原型模式 JS实现继承的方式 ...