Tips of Java5 通用程序设计

参考资料:《Effective Java》第八章 通用程序设计

1. 尽量使局部变量作用域最小化

这一条与 “使类和成员的可访问性最小化” 本质上是类似的。将局部变量的作用域最小化可以增强代码的可读性和可维护性,减少出错。有以下方法:

1.1 在第一次使用它的地方声明。过早声明局部变量造成混乱,对于读者,可能用到的时候早已经忘记了这个变量的用处。而且过早声明使得它的作用域过早的拓展,也结束的过晚

1.2 循环中提供了特殊机会来将变量的作用域最小化,如果在循环终止后不再需要循环变量的内容,for循环就优先while循环。

for(int index = 0, n = calcMaxNumber(); index < n ; index ++){
   ...
   ...
}

1.3 使方法小而集中。如果把两个操作合并到同一个方法中,与其中一个操作相关的局部变量就有可能出现在执行另一个操作的代码范围内。最好是让方法分成两个,每个方法各自执行自己的操作。

Plus:使变量作用域最小化不仅体现在循环、方法的处理上。在Extract Class提炼类一节中也有类似的思想。一个类应该是一个清楚的抽象,实现了某些具体的功能。但在实际工作中,类会不断成长拓展,这里加入一些功能,那里加入一些数据。随着责任的不断增加,类会变得过分复杂。这个时候就应该考虑提炼类。

举FGO从者信息面板为例子,一开始可能设计成一个Servant类用来记录从者的基本信息的字段,例如声优cv,人物阵营等,这些信息是从者的固有信息。除此之外还有从者等级、额外攻击力,这些各个玩家的数据,由固有信息和玩家数据组合成一个Servant类。随后又加入了语音功能,我可能会在Servant类中加一个ArrayList 这个数据结构, 并在Servant类中加入语音相关的方法,比如playVoice播放某一条语音。比如触发了某段剧情可以unlockVoice解锁新的语音。比如服务器向客户端同步已解锁的语音信息时,需要一个syncServantVoice同步信息的方法。之后可能还有别的功能。这样不断拓展的结果是Servant类会变得特别臃肿。

运用Extract Class方法可以将语音相关的部分抽离出来成为一个ServantVoiceData的类,并把语音相关的方法、变量挪到这里,并让Servant类持有一个ServantVoiceData的实例的引用。这样在实现原来功能的情况下,有两点好处,一是保持结构清晰,可读性强,原本,跟语音相关的变量是放在Servant类底下的,这样代表这个变量的作用范围是在整个Servant类,但是实际上仅仅只在语音部分被用到而已。把这个变量抽离到ServantVoiceData中使这个变量的作用范围最小化。二是方便拓展,后续的功能不需要再加入到一个庞杂的Servant类中,只需要模仿ServantVoiceData即可,新建功能类,让Servant持有一个功能类的实例引用。

Tips of Java5 通用程序设计_第1张图片

 

2. for-each循环优先于传统的for循环

for-each完全隐藏了迭代器和索引变量,减少了出错的可能。可读性也更好。

for-each不仅让你遍历集合和数组,还让你遍历任何实现了Iterable接口的对象,这个接口由单个方法组成。

public interface Iterable{
  //return an iterator over the elements in this utterable
  Iterator iterator();
}

Plus1:Java8中forEach也是同样隐藏了迭代器与索引变量,可读性也更好。优先使用for-each、forEach。

2.1 如果需要遍历集合删除指定元素,需要使用显式的迭代器。才可以调用remove方法。

2.2 如果需要遍历列表或者数组,并取代部分或者全部的元素值,仍然需要迭代器或者数组的索引,才可以对指定元素进行修改。

2.3 如果需要并行遍历多个集合,必须做好各个迭代器或者索引变量的同步。

Plus2:以上三个问题均在Java8中存在解决方案。

2.4 遍历集合删除指定元素可以使用lambda表达式,removeIf方法,内部实现也是使用了迭代器。

2.5 取代部分或者全部的元素值,lambda表达式中的map方法,但是最后 stream流会生成一个另外的数组或者集合。不能影响原数组。

2.6 并行遍历多个集合。使用Java8并行流parallelStream()进行处理。

 

3. 类库

 

4. 基本类型优先于装箱基本类型

区别:

4.1 基本类型只有值,装箱基本类型具有域它们的值不一样的同一性。两个基本类型可以有相同值,不同的同一性。例如new Integer(42) 和 new Integer相比是不一样的。

当Integer比较大小的时候,最好将装箱类型转化为基本类型再比较。

4.2. 装箱类型还具有额外的null值,并没有包含一个具体内容。

当在混用装箱类型和基本类型时,装箱类型会被自动拆箱成基本类型,一般来说是没有问题的。但是如果null对象自动拆箱时,就会得到一个空指针异常。

4.3. 基本类型比较节省时间、空间

 

5. 通过接口引用对象 

当你使用接口的时候,如果需要更改具体实现,只需要改变名称即可。例如论坛上一个评论列表,一开始我选择用ArrayList实现,List comments = new ArrayList<>(); 如果有新的评论就直接插入到List中。现在要更改功能,评论不直接插入到List末尾,而是要插入到0的位置。ArrayList对于这种频繁插入的效率是不高的。所以我们决定更改成LinkedList。需要做的事情只有 List comments = new LinkedList<>(); 即可。因为当我用接口来引用对象时,我所能使用的方法都是接口里规定的方法。即使变换了具体实现我仍然可以使用这些方法。但是如果需要用到某一种实现里的特殊方法,也就是不包括在接口里方法时,就必须用对应的实现类型。

 

6. 接口优先于反射机制

 

7. 遵守普遍接受的命名规则

7.1 类、接口、枚举、注解采用首单词首字母大写的驼峰命名法。例如 Apple类、List接口、@Override

7.2 方法、域采用首单词首字母小写的驼峰命名法。例如 getNumber()、currentValue.

7.3 常量域名称应该包含一个或者多个大写的单词,中间用下划线隔开。例如 MIN_VALUE

7.4 执行某个动作的方法通常用动词或者动词短语命名。例如append、drawImage.   

对于返回boolean值的方法,名称一般以“is”开头,比较少用“has” 例如isEmpty、isEnabled、hasSiblings。

对于返回一个域值的方法。可以用“get”开头,比如getAge()。不一定需要严格遵守,比如List中获取大小可以直接用size取得。

实际编程中可以使用阿里巴巴代码规范的插件。

你可能感兴趣的:(Java)