倚楼听风雨
淡看江湖路

阿里巴巴Java开发规约第一章-OOP(面向对象编程)规约篇

1.[强制] 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

老四附言:
老四之前在文章《Java面向对象之static关键字类成员浅析》中也提及到过:

类变量可以直接通过类来访问,即"类.方法"等,也可以通过类的对象来访问,然而,即使你用类的对象访问类成员,实质上也是通过类来访问类成员。不建议那样做。由于Java中支持类的实例调用类成员,所以一个有意思的事情就是一个为null的实例可以调用静态方法。

2.[强制] 所有的覆写方法,必须加@Override注解。

说明: getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

老四附言:
要不然怎么说良好的变成习惯会使我们更方便工作呢,更有效率处理问题呢。

3.[强制] 相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。

说明: 可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)

正例: public User getUsers(String type, Integer... ids) {...}

老四附言:
虽然说不提倡使用可变参数编程,但是我们依然要来复习一下可变参数的基本知识:

从JDK1.5之后,Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后增加三点(...),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。示例看孤尽老师的就可以了。

  • 形参个数可变的参数本质就是一个数组参数
  • 一个方法中最多只能有一个个数可变的形参
  • 数组形式的形参可以处于形参列表的任意位置,但是可变参数只能处于形参列表的最后
4.[强制] 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。

老四附言:
关于一方库、二方库、三方库的概念可以参考一下老四的《阿里巴巴Java开发手册第六章-二方库依赖篇》这篇文章中的第十条规约浅析内容。

5.[强制] 不能使用过时的类或方法。

说明: java.net.URLDecoder中的方法decode(String encodeStr)这个方法已经过时,应该使用双参数decode(String source, String encode)。接口提供方既然明确是过时接口.那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。

老四附言:
none!!

6.[强制] Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。

正例: "test".equals(object);
反例: object.equals("test");
说明: 推荐使用java.util.Objects#equals(JDK7引入的工具类)

老四附言:
即使你如果Objects用的比较少,也可以参考一下它的源码来写自己的equals方法,理解一下他的实现思路。我们首先来看一下Objects.equals的源码:

从源码中我们也可以看出他首先比较两个对象的是否是引用相等,然后再确定第一个对象不为空的情况下判断两个对象的值是否相等,所以我们在书写自己的equals方法时也要有这样的逻辑意识。

7.[强制] 所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。

说明: 对于Integer var = ?在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

老四附言:
关于这问题,老四之前在文章《阿里巴巴Java开发规约第二章-异常处理篇》中第十条规约中已经基本分析了Integer的这个问题,详情可以去参考一下。

8.[强制] 关于基本数据类型与包装数据类型的使用标准如下:
  • [强制] 所有的POJO类属性必须使用包装数据类型。
  • [强制] RPC方法的返回值和参数必须使用包装数据类型。
  • [推荐] 所有的局部变量使用基本数据类型。

说明: POJO(Plain Ordinary Java Object,简单的Java对象)类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。

正例: 数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。

反例: 比如显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这是不合理的,应该显示成中划线。所以包装数据类型的null值,能够表示额外的信息,如: 远程调用失败,异常退出。

老四附言:
关于Java中的自动装箱与自动拆箱老四也在文章《阿里巴巴Java开发规约第二章-异常处理篇》中第十条规约中已经基本复习了一遍,可以前区参考一下。

9.[强制] 定义DO(Domain Object,领域对象)/DTO(Data Transfer Object,数据传输对象)/VO(View Object,表现对象)等POJO类时,不要设定任何属性默认值。

反例: POJO(Plain Ordinary Java Object,简单的Java对象)类的gmtCreate默认值为new Date();但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。gmt_create表示记录创建时间,gmt_modified表示最近修改时间,如果记录没有修改,gmt_create和gmt_modified一致。

老四附言:
关于gmtCreate这个东西老四之前在文章《阿里巴巴Java开发手册第五章-建表规约篇》中第九条规约浅析中就提及到过,具体可以参考一下这篇文章。

10.[强制] 序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值。

说明: 注意serialVersionUID不一致会抛出序列化运行时异常。

老四附言:
Java序列化基础知识复习可以参考一下的老四的这篇《Java中I/O输入输出流之对象序列化浅析》文章。

11.[强制] 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。

老四附言:
这个的确是要这样的,构造器的设计不是用来做初始化的。

12.[强制] POJO类必须写toString 方法。使用IDE中的工具: source>generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString。

说明: 在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。

老四附言:
是哒!

13.[推荐] 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。

说明:

老四附言:
看一下split的源码我们就知道它是怎么处理的了。

我们可以看到在构造数组结果的时候,他是循环结果列表,如果结果列表为空,他会将当前集合大小减一,并且最后组合成没有空元素的数组返回。

14.[推荐] 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读,此条规则优先于第15条规则。

老四附言:
那我们接下来看看第15条规则是什么....

15.[推荐] 类内方法定义的顺序依次是: 公有方法或保护方法>私有方法>getter/setter方法。

说明: 公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类关心,也可能是"模板设计模式"下的核心方法;而私有方法外部一般不需要特别关心,是一个黑盒实现;因为承载的信息价值较低,所有Service和DAO的getter/setter方法放在类体最后。

老四附言:
所以14和15条规则组合在一起就是构造->方法->getter/setter->toString(POJO)。关于模板设计模式可以参考一下老四的《浅析设计模式第十章-模板方法模式》这篇文章。

16.[推荐] setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。

反例:

老四附言:
但也不是强制性质的,比如说简单的分页功能,为了实现当前第几页的功能,我们有的时候也不可避免的需要在getter/setter中处理一些业务逻辑问题。如下代码:

这是一段简单的分页实体类,我们需要在getter方法中实现获取当前页数的业务逻辑。

17.[推荐] 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。

说明: 反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
反例:

老四附言:
如果你不相信,你可以自己写一段代码反编译试一下,关于反编译可以参考一下老四的《介绍几个著名的实用的Java反编译工具,提供下载》和《你可能不知道的两个逆天的Java反编译在线网站 墙裂推荐》这两篇文章。

18.[推荐] final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
  • 不允许被继承的类,如: String类。
  • 不允许修改引用的域对象,如: POJO类的域变量。
  • 不允许被重写的方法,如: POJO类的setter方法。
  • 不允许运行过程中重新赋值的局部变量。
  • 避免上下文重复使用一个变量,使用final描述可以强制重新定义一个变量,方便更好地进行重构。

老四附言:

关于final关键字的分析也可以参考一下老四的这篇《Java面向对象之final修饰符》。另外,关于以上这几种情况代码举例如下:

19.[推荐] 慎用Object的clone方法来拷贝对象。

说明: 对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现属性对象的拷贝。

老四附言:

简单的惠顾一下关于Object中的clone()方法,该方法用于帮助其他对象来实现"自我克隆"--就是得到一个当前对象的副本,而且二者之间完全隔离。"自我克隆"的实现步骤:

  • 自定义的类实现Cloneable接口。这是一个标记接口,实现该接口的对象可以实现"自我克隆",接口不定义任何方法。
  • 自定义类实现自己的clone()方法。
  • 实现clone()方法的时候通过super.clone();调用Object实现的clone()方法来得到当前对象的副本,Ben且返回该副本。

注意:Object类提供的clone()方法十分高效,简单易用,但是它只是一种"浅克隆"--它只克隆该对象的所有成员变量值,但是对引用类型的成员变量不会进行克隆。另外老四再来说一下Java中的深拷贝与浅拷贝。深拷贝与浅拷贝的概念就是针对引用类型的成员变量而产生的讨论。

  • 浅拷贝: 对基本数据类型进行值传递,对引用数据类型进行引用传递
  • 深拷贝: 对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容

前面说到Object提供的clone()方法中在进行自我克隆的时候,如果不对引用数据类型的成员变量进行处理的话,那就是浅拷贝,如果需要深拷贝,则需要我们也要讲引用类型实例变量在进行一次clone()操作,即需要我们自己进行"递归"克隆,保证所有引用类型的成员变量值所引用的对象也都被复制了。基本的深拷贝示例代码如下:

20.[推荐] 类成员与方法访问控制从严:
  • 如果不允许外部直接通过new来创建对象,那么构造方法必须是private。
  • 工具类不允许有public或default构造方法。
  • 类非static成员变量并且与子类共享,必须是protected。
  • 类非static成员变量并且仅在本类使用,必须是private。
  • 类static成员变量如果仅在本类使用,必须是 private。
  • 若是static成员变量,必须考虑是否为final。
  • 类成员方法只供类内部调用,必须是private。
  • 类成员方法只对继承类公开,那么限制为protected。

说明: 任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。
思考: 如果是一个private的方法,想删除就删除,可是一个public的service成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

老四附言:

是时候再来复习一波访问范围修饰符控制关键字的差异与区别了。

更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请赞助盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额,老四这里抱拳了。赞助时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的赞赏钱财也会被用于小站的服务器运维上面,再次抱拳。

赞(19) 给你买杜蕾斯
本站原创文章受自媒体平台原创保护,未经允许不得转载高老四博客 » 阿里巴巴Java开发规约第一章-OOP(面向对象编程)规约篇
分享到: 更多 (0)

开始你的表演 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下老四,鼓励我更好的创作

支付宝扫一扫打赏

微信扫一扫打赏