阅读目录

  • 建议1:不要在常量和变量中出现易混淆的字母
  • 建议2:莫让常量蜕变成变量   
  • 建议3:三元操作符的类型务必一致  
  • 建议4:避免带有变长参数的方法重载
  • 建议5:别让null值和空值威胁到变长方法  

             The reasonable man adapts himself to the world; The unreasonable one persists in trying to adapt the world himself.

     明白事理的人使自己适应世界;不明事理的人想让世界适应自己。

                                -------萧伯纳

  本系类文章,用来记录《编写高质量代码 改善java程序的151个建议》这本书的读书笔记。方便自己查看,也方便大家查阅,在此感谢原书作者秦小波对java的独特见解,帮助java爱好者的成长。由于篇幅原因本人将读书笔记采取分批记忆的方式来进行记录分享,全书共12章,共有151条建议,其中1~3章针对java语法本身提出了51条建议;第4~9章重点针对JDK API的使用提出了80条建议;第10~12章针对程序的性能、开源的工具和框架、编码风格和编程思想等方面提出了20条建议。本人根据此书的目录结构,循序渐进的阅读此书,特记录于此。

  本人第一次阅读本书是1年半以前,当时看的不仔细,只是了解了一些问题,当时觉得这本书还可以,现在1年后又将他翻出来,重新开始看,觉得理解还是和第一次看的韵味有不同之处,所以建议大家看书时一定要细心的理解去看,不可追寻速度,要仔细品味,因为读多少书,不是用来出去吹牛逼的,我都读了哪些哪些书,用了很短的时间之外的.....   书是读给自己的,用来提升自我的,所以要细心理解,思考至上,学以致用。技术类的书籍大同小异,原理不变,所以用心读完一本技术的书籍,以后再读其他关于此类技术的书籍,你的理解会更深刻一些,相比快速的读了若干本关于一方面技术的书籍,最后还是一知半解,只知其然,不知其所以然。

  JAVA的世界丰富又多彩,但同时也布满了经济陷阱,大家一不小心就可能跌入黑暗的深渊,只有在了解了其通行规则后才能是自己在技术的海洋里遨游飞翔,恣意驰骋。千里之行,始于足下,本章主要讲述与JAVA语言基础相关的问题及建议的解决方案和变量的注意事项、如何安全的序列化、断言到底该如何使用等;

回到顶部

建议1:不要在常量和变量中出现易混淆的字母

包名全小写,类名首字母全大写,常量全部大写并用下划线分隔,变量采用驼峰命名法(Camel Case)命名等,这些都是最基本的Java编码规范,是每个javaer都应熟知的规则,但是在变量的声明中要注意不要引入容易混淆的字母。尝试阅读如下代码,思考打印结果的i是多少:

 1 public class Demo{2     public static void main(String[] args) {3         test01();4     }5     6     public static void test01(){7         long i=1l;8         System.out.println("i的两倍是:"+(i+i));9     }
10 }

肯定会有人说:这么简单的例子还能出错?运行结果肯定是22!实践是检验真理的唯一标准,将其Run一下看看,或许你会很奇怪,结果是2,而不是22.难道是编译器出问题了,少了个"2"?

因为赋给变量i的值就是数字"1",只是后面加了长整型变量的标示字母"l"而已。别说是我挖坑让你跳,如果有类似程序出现在项目中,当你试图通过阅读代码来理解作者的思想时,此情景就可能会出现。所以为了让你的程序更容易理解,字母"l"(包括大写字母"O")尽量不要和数字混用,以免使读者的理解和程序意图产生偏差。如果字母和数字混合使用,字母"l"务必大写,字母"O"则增加注释。

注意:字母"l"作为长整型标志时务必大写

回到顶部

建议2:莫让常量蜕变成变量   

  常量蜕变成变量?你胡扯吧,加了final和static的常量怎么可能会变呢?不可能为此赋值的呀。真的不可能吗?看看如下代码:

 1 import java.util.Random;2 3 public class Demo01 {4     public static void main(String[] args) {5         test02();6     }7 8     public static void test02() {9         System.out.println("常量会变哦:" + Constant.RAND_CONST);
10     }
11 }
12
13 interface Constant {
14     public static final int RAND_CONST = new Random().nextInt();
15 }

RAND_CONST是常量吗?它的值会变吗?绝对会变!这种常量的定义方式是绝对不可取的,常量就是常量,在编译期就必须确定其值,不应该在运行期更改,否则程序的可读性会非常差,甚至连作者自己都不能确定在运行期发生了何种神奇的事情。

甭想着使用常量会变的这个功能来实现序列号算法、随机种子生成,除非这真的是项目中的唯一方案,否则就放弃吧,常量还是当常量使用。

注意:务必让常量的值在运行期保持不变。

回到顶部

建议3:三元操作符的类型务必一致  

 三元操作符是if-else的简化写法,在项目中使用它的地方很多,也非常好用,但是好用又简单的东西并不表示就可以随意使用,看看如下代码:

1 public static void test03() {
2         int i = 80;
3         String str = String.valueOf(i < 100 ? 90 : 100);
4         String str1 = String.valueOf(i < 100 ? 90 : 100.0);
5         System.out.println("两者是否相等:" + str.equals(str1));
6     }

分析一下这段程序,i是80,小于100,两者的返回值肯定都是90,再转成String类型,其值也绝对相等,毋庸置疑的。嗯,分析的有点道理,但是变量str中的三元操作符的第二个操作数是100,而str1中的第二个操作数是100.0,难道木有影响吗?不可能有影响吧,三元操作符的条件都为真了,只返回第一个值嘛,于第二个值有毛线关系,貌似有道理。

  运行之后,结果却是:"两者是否相等:false",不相等,why?

  问题就出在了100和100.0这两个数字上,在变量str中,三元操作符的第一个操作数90和第二个操作数100都是int类型,类型相同,返回的结果也是int类型的90,而变量str1中的第一个操作数(90)是int类型,第二个操作数100.0是浮点数,也就是两个操作数的类型不一致,可三元操作符必须要返回一个数据,而且类型要确定,不可能条件为真时返回int类型,条件为假时返回float类型,编译器是不允许如此的,所以它会进行类型转换int类型转换为浮点数90.0,也就是三元操作符的返回值是浮点数90.0,那么当然和整型的90不相等了。这里为什么是整型转成浮点型,而不是浮点型转成整型呢?这就涉及三元操作符类型的转换规则:

  1. 若两个操作数不可转换,则不作转换,返回值是Object类型;
  2. 若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字转换,int转为long,long转为float等;
  3. 若两个操作数中有一个是数字S,另外一个是表达式,且其类型标志位T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T的范围,则T转换为S;
  4. 若两个操作数都是直接量数字,则返回值类型范围较大者。

  知道什么原因了,相应的解决办法也就有了:保证三元操作符中的两个操作数类型一致,避免此错误的发生。

回到顶部

建议4:避免带有变长参数的方法重载

  在项目和系统开发中,为了提高方法的灵活度和可复用性,我们经常要传递不确定数量的参数到方法中,在JAVA5之前常用的设计技巧就是把形参定义成Collection类型或其子类类型,或者数组类型,这种方法的缺点就是需要对空参数进行判断和筛选,比如实参为null值和长度为0的Collection或数组。而Java5引入了变长参数(varags)就是为了更好地挺好方法的复用性,让方法的调用者可以"随心所欲"地传递实参数量,当然变长参数也是要遵循一定规则的,比如变长参数必须是方法中的最后一个参数;一个方法不能定义多个变长参数等,这些基本规则需要牢记,但是即使记住了这些规则,仍然有可能出现错误,看如下代码:

 1 public class Client {2     public static void main(String[] args) {3         Client client = new Client();4         // 499元的货物 打75折5         client.calPrice(499, 75);6     }7 8     // 简单折扣计算9     public void calPrice(int price, int discount) {
10         float knockdownPrice = price * discount / 100.0F;
11         System.out.println("简单折扣后的价格是:" + formatCurrency(knockdownPrice));
12     }
13
14     // 复杂多折扣计算
15     public void calPrice(int price, int... discounts) {
16         float knockdownPrice = price;
17         for (int discount : discounts) {
18             knockdownPrice = knockdownPrice * discount / 100;
19         }
20         System.out.println("复杂折扣后的价格是:" + formatCurrency(knockdownPrice));
21     }
22
23     public String formatCurrency(float price) {
24         return NumberFormat.getCurrencyInstance().format(price);
25     }
26 }

  这是一个计算商品折扣的模拟类,带有两个参数的calPrice方法(该方法的业务逻辑是:提供商品的原价和折扣率,即可获得商品的折扣价)是一个简单的折扣计算方法,该方法在实际项目中经常会用到,这是单一的打折方法。而带有变长参数的calPrice方法是叫较复杂的折扣计算方式,多种折扣的叠加运算(模拟类是比较简单的实现)在实际中也经常见到,比如在大甩卖期间对VIP会员再度进行打折;或者当天是你的生日,再给你打个9折,也就是俗话中的折上折。

  业务逻辑清楚了,我们来仔细看看这两个方法,它们是重载吗?当然是了,重载的定义是:"方法名相同,参数类型或数量不同",很明显这两个方法是重载。但是这个重载有点特殊,calPrice(int price ,int... discounts)的参数范畴覆盖了calPrice(int price,int discount)的参数范畴。那问题就出来了:对于calPrice(499,75)这样的计算,到底该调用哪个方法来处理呢?

  我们知道java编译器是很聪明的,它在编译时会根据方法签名来确定调用那个方法,比如:calPrice(499,75,95)这个调用,很明显75和95会被转成一个包含两个元素的数组,并传递到calPrice(int price,int...discounts)中,因为只有这一个方法符合这个实参类型,这很容易理解。但是我们现在面对的是calPrice(499,75)调用,这个75既可以被编译成int类型的75,也可以被编译成int数组{75},即只包含一个元素的数组。那到底该调用哪一个方法呢?运行结果是:"简单折扣后的价格是:374.25"。看来调用了第一个方法,为什么会调用第一个方法,而不是第二个变长方法呢?因为java在编译时,首先会根据实参的数量和类型(这里2个实参,都为int类型,注意没有转成int数组)来进行处理,也就是找到calPrice(int price,int discount)方法,而且确认他是否符合方法签名条件。现在的问题是编译器为什么会首先根据两个int类型的实参而不是一个int类型,一个int数组类型的实参来查找方法呢?

  因为int是一个原生数据类型,而数组本身是一个对象,编译器想要"偷懒",于是它会从最简单的开始"猜想",只要符合编译条件的即可通过,于是就出现了此问题。

  问题阐述清楚了,为了让我们的程序能被"人类"看懂,还是慎重考虑变长参数的方法重载吧,否则让人伤脑筋不说,说不定哪天就陷入这类小陷阱里了。

回到顶部

建议5:别让null值和空值威胁到变长方法  

 上一建议讲解了变长参数的重载问题,本建议会继续讨论变长参数的重载问题,上一建议的例子是变长参数的范围覆盖了非变长参数的范围,这次讨论两个都是变长参数的方法说起,代码如下:

 1 public class Client5 {2 3     public void methodA(String str, Integer... is) {4 5     }6 7     public void methodA(String str, String... strs) {8 9     }
10
11     public static void main(String[] args) {
12         Client5 client5 = new Client5();
13         client5.methodA("china", 0);
14         client5.methodA("china", "people");
15         client5.methodA("china");
16         client5.methodA("china", null);
17     }
18 }

  两个methodA都进行了重载,现在的问题是:上面的client5.methodA("china");client5.methodA("china", null);编译不通过,提示相同:方法模糊不清,编译器不知道调用哪一个方法,但这两处代码反应的味道是不同的。

  对于methodA("china")方法,根据实参"china"(String类型),两个方法都符合形参格式,编译器不知道调用那个方法,于是报错。我们思考一下此问题:Client5这个类是一个复杂的商业逻辑,提供了两个重载方法,从其它模块调用(系统内本地调用系统或系统外远程系统调用)时,调用者根据变长参数的规范调用,传入变长参数的参数数量可以是N个(N>=0),那当然可以写成client5.methodA("china")方法啊!完全符合规范,但是这个却让编译器和调用者郁闷,程序符合规则却不能运行,如此问题,谁之责任呢?是Client5类的设计者,他违反了KISS原则(Keep it Smile,Stupid,即懒人原则),按照此设计的方法应该很容一调用,可是现在遵循规范却编译不通过,这对设计者和开发者而言都是应该禁止出现的。

  对于Client5.methodA("China",null),直接量null是没哟类型的,虽然两个methodA方法都符合调用要求,但不知道调用哪一个,于是报错了。仔细分析一下,除了不符合上面的懒人原则之外,还有一个非常不好的编码习惯,即调用者隐藏了实参类型,这是非常危险的,不仅仅调用者需要"猜测调用那个方法",而且被调用者也可能产生内部逻辑混乱的情况。对于本例来说应该如此修改:

1 public static void main(String[] args) {
2         Client5 client5 = new Client5();
3         String strs[] = null;
4         client5.methodA("china", strs);
5     }

也就是说让编译器知道这个null值是String类型的,编译即可顺利通过,也就减少了错误的发生。

作者:阿赫瓦里
出处:http://www.cnblogs.com/selene/
本文以学习、研究和分享为主,版权归作者和博客园共有,欢迎转载,如果文中有不妥或者错误的地方还望大神您不吝指出。如果觉得本文对您有所帮助不如【推荐】一下吧!如果你有更好的建议,不如留言一起讨论,共同进步!此外,大家也可以支持一下自家苹果, 再次感谢您耐心的读完本篇文章。

转载于:https://www.cnblogs.com/LH923613603/p/6848488.html

转载----编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议1~5)...相关推荐

  1. 编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议1~5)...

                 The reasonable man adapts himself to the world; The unreasonable one persists in trying ...

  2. 转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议16~20)...

    阅读目录 建议16:易变业务使用脚本语言编写 建议17:慎用动态编译 建议18:避免instanceof非预期结果 建议19:断言绝对不是鸡肋 建议20:不要只替换一个类 回到顶部 建议16:易变业务 ...

  3. 转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议11~15)...

    阅读目录 建议11:养成良好习惯,显示声明UID 建议12:避免用序列化类在构造函数中为不变量赋值 建议13:避免为final变量复杂赋值 建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题 ...

  4. 转载-----编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议6~10)...

    阅读目录 建议6:覆写变长方法也循规蹈矩 建议7:警惕自增的陷阱 建议8:不要让旧语法困扰你 建议9:少用静态导入 建议10:不要在本类中覆盖静态导入的变量和方法 回到顶部 建议6:覆写变长方法也循规 ...

  5. java代码优化的方法和准则_编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议16~20)...

    建议16:易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP,Ruby,Groovy.Javascript等,这些入侵者都有一个共同特征:全是同一类语言-----脚本语言,它 ...

  6. 编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议6~10)...

    建议6:覆写变长方法也循规蹈矩 在JAVA中,子类覆写父类的中的方法很常见,这样做既可以修正bug,也可以提供扩展的业务功能支持,同时还符合开闭原则(Open-Closed Principle). 符 ...

  7. 编写高质量代码改善C#程序的157个建议——建议148:不重复代码

    建议148:不重复代码 如果发现重复的代码,则意味着我们需要整顿一下,在继续前进. 重复的代码让我们的软件行为不一致.举例来说,如果存在两处相同的加密代码.结果在某一天,我们发现加密代码有个小Bug, ...

  8. 编写高质量代码改善C#程序的157个建议——建议86:Parallel中的异常处理

    建议86:Parallel中的异常处理 建议85阐述了如何处理Task中的异常.由于Task的Start方法是异步启动的,所以我们需要额外的技术来完成异常处理.Parallel相对来说就要简单很多,因 ...

  9. 编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型...

    建议87:区分WPF和WinForm的线程模型 WPF和WinForm窗体应用程序都有一个要求,那就是UI元素(如Button.TextBox等)必须由创建它的那个线程进行更新.WinForm在这方面 ...

最新文章

  1. ref与out的区别
  2. Spring MVC拦截器实现
  3. Android之linux基础教学之七 中断下半部之软中断
  4. IT人士在职场中需要哪些Skill?
  5. 《Linux内核设计与实现》读书笔记(十八)- 内核调试
  6. mysql group by与order by的研究--分类中最新的内容
  7. 智能车复工日记【N】:图像处理——环岛debug记录(持续更新)
  8. IOS之导航控制器与表视图
  9. jq 鼠标指向隐藏显示区域
  10. 页面重绘和回流以及优化
  11. 电池图标不见了怎么解决
  12. Linux内核中Makefile、Kconfig和.config的关系
  13. hadoop 权威指南学习笔记ing(1)
  14. 电脑小白和黑客的对白
  15. 调整连字符号分隔字母的个数
  16. 2018/3/9 省选模拟赛 0分
  17. 姓名投票c语言未给定候选人,C语言上机
  18. 有线网与无线网(WIFI)网速的限制因素与Wifi信道选择
  19. Excel中对合并单元格后不同行数对应数据处理的三种特技
  20. metasploit中用shodan模块进行网络摄像头查找

热门文章

  1. Zabbix 监控主机是否在线
  2. 看守所视频AI行为分析算法
  3. 脉冲信号是数字信号吗?为什么时间离散的信号在幅值上可以是连续的?
  4. 上海公积金社保业务办理
  5. 对我影响最大的4句名言
  6. 时域卷积定理的证明 | 卷积的傅里叶变化等于傅里叶变换的乘积
  7. 串口设备模拟器DevSimulate
  8. 迁移学习xlm-roberta-base模型应用于分类任务
  9. 操作系统常见面试题总结
  10. Java判断url是否合法