最近细读了秦小波老师的《编写高质量代码改善Jaav程序的151个倡议》,要说是151个倡议,其实更适合的说是防止Java的一些冷门的坑,上面整顿了20个比拟乏味的倡议重新学习了一遍。
三元操作符的类型务必统一
三元操作符运算也称为三目运算,其表达式形如:”条件表达式 ? 表达式1 : 表达式2″,在大部分语言中都有这样的三元操作符,其目标就是为了简化if-else,当条件表达式为真时执行表达式1,否则执行表达式2。 来剖析一下上面这段代码:
<code class="java">public static void main(String[] args){ int i = 80; String s = String.valueOf(i < 100 ? 80 : 100); String s1 = String.valueOf(i < 100 ? 80 : 100.0); boolean equals = s.equals(s1); // 两者是否相等:false, 字符串s的值:80, 字符串s1的值:80.0 System.out.println("两者是否相等:" + equals); System.out.println("字符串s的值:" + s); System.out.println("字符串s1的值:" + s1); }
阐明:如果三目运算符的类型不统一,返回的后果也不统一。
防止带有变长参数的办法重载
<code class="java">public class Client { private static final Logger log = LoggerFactory.getLogger(Client.class); public static void main(String[] args) { Client client = new Client(); client.calPrice(5000, 80); } /** * calPrice 简略折扣计算 * * @param price 价格 * @param discount 折扣 * @description * @author luokangyuan * @date 2019-4-2 14:58 * @version 1.0.0 */ private void calPrice(int price, int discount) { float knockdownPrice = price * discount / 100.0F; log.info("简略的折扣后的价格:{}", knockdownPrice); } /** * calPrice * * @param price 价格 * @param discounts 折扣 * @description 简单折扣计算 * @author luokangyuan * @date 2019-4-2 15:08 * @version 1.0.0 */ private void calPrice(int price, int... discounts) { float knockdownPrice = price; for (int discount : discounts) { knockdownPrice = knockdownPrice * discount / 100; } log.info("简单折扣后的价格:{}", knockdownPrice); } }
阐明:办法重载就是办法名雷同,参数类型或者参数数量不同,在上述例子中Java
编辑器会依据办法签名找到适合的适合的办法,上述测试调用的就是简略的折扣计算,而非简单折扣计算。
不要只替换一个类
<code class="java">public class Constant{ public final static int MAX_AGE = 150; } public class Client{ public static void main(String[] args){ System.out.println("人类寿命极限是:" + Constant.MAX_AGE); } }
对于final润饰的根本类型和String类型,编译器会认为它是稳固态的(Immutable Status)所以在编译时就间接把值编译到字节码中了,防止了在运行期援用(Run-time Reference),以进步代码的执行效率。对于咱们的例子来说,Client类在编译时字节码中就写上了”150″,这个常量,而不是一个地址援用,因而无论你后续怎么批改常量类,只有不从新编译Client类,输入还是依旧。
对于final润饰的类(即非根本类型),编译器会认为它不是稳固态的(Mutable Status),编译时建设的则是援用关系(该类型也叫作Soft Final)。如果Client类引入的常量是一个类或实例,及时不从新编译也会输入最新值。
用偶判断,不必奇判断
<code class="java">String s = n % 2 == 1 ? "奇数" : "偶数"; String s1 = n % 2 == 0 ? "偶数" : "奇数";
阐明:通常应用第二种偶数判断,应用第一种的话。-1
也会被判断为偶数。
用整数类型解决货币
<code class="java">// 0.40000000000000036 System.out.println(10.00 - 9.60);
阐明:Java中的浮点数是不精确的,在解决货币上应用浮点数就会存在问题,因而应用BigDecimal
,类来进行计算,或者应用整数,将须要计算的数放大100倍,计算后在放大。
1、应用BigDecimal
BigDecimal
是专门为补救浮点数无奈准确计算的缺憾而设计的类,并且它自身也提供了加减乘除的罕用数学算法。特地是与数据库Decimal类型的字段映射时,BigDecimal是最优的解决方案。
2、应用整型
把参加运算的值扩充100倍,并转为整型,而后在展示时再放大100倍,这样解决的益处是计算简略,精确,个别在非金融行业(如批发行业)利用较多。此办法还会用于某些批发POS机,他们输出和输入的全副是整数,那运算就更简略。
应用String间接赋值
<code class="java">public static void main(String[] args) { String str1 = "China"; String str2 = "China"; String str3 = new String("China"); String str4 = str3.intern(); System.out.println(str1 == str2); // true System.out.println(str1 == str3); // false System.out.println(str1 == str4); // true }
阐明:倡议应用String str1 = "China";
这中形式对字符串赋值,而不是通过new String("China");
这种形式,在Java中会给定义的常量寄存在一个常量池中,如果池中存在就不会在反复定义,所以str1 == str2
返回true
。new
出的是一个对象,不会去查看字符串常量池是否存在,所以str1 == str3
是不同的援用,返回false
。通过intern()
解决后,返回true
,是因为intern()
会去对象常量池中查看是否存在字面上雷同得援用对象。
asList产生的list不可批改
<code class="java">private static void arraysTest() { String[] week = {"Mon", "Tue", "Wed", "Thu"}; List<String> strings = Arrays.asList(week); strings.add("Fri"); }
阐明:运行报错,asList产生的list不可批改。
别让null值和空值威逼到变长办法
<code class="java">public void countSum(String type, Integer... is){} public void countSum(String type, String... strs){} public static void main(String[] args) { ClientReload clientReload = new ClientReload(); clientReload.countSum("China", 0); clientReload.countSum("China", "People"); // 编译报错 clientReload.countSum("China"); // 编译报错 clientReload.countSum("China", null); }
阐明:同样是含有变长参数的重载办法,内部调用的应用应用NULL
或者空值都会呈现编译不通过的谬误,这是应为NULL
和空值在上述重载的办法中都满足参数条件,所以编译器不晓得调什么办法,在重载办法的设计中违反了KISS
准则,同时在内部调用的时候,内部调用这暗藏了实参的类型,如将调用代码做如下批改,就不存在编译报错了。
<code class="java">String strs = null; clientReload.countSum("China", strs);
警觉自增的陷阱
<code class="java">public static void main(String[] args) { int count = 0; for (int i = 0; i < 100; i++) { int i1 = count++; count = i1; System.out.println("每次count++的值:" + i1); } System.out.println(count); }
阐明:后果是0
,而不是咱们100
,这是count++
是一个表达式,返回的是自加之前count
的值。
break不可遗记
<code class="java">public static void main(String[] args) { String s = toChineseNumber(2); log.info("转换后果:{}", s); } private static String toChineseNumber(int n) { String chineseNumber = ""; switch (n) { case 0 : chineseNumber = "零"; case 1 : chineseNumber = "壹"; case 2 : chineseNumber = "贰"; case 3 : chineseNumber = "叁"; case 4 : chineseNumber = "肆"; case 5 : chineseNumber = "伍"; case 6 : chineseNumber = "陆"; case 7 : chineseNumber = "柒"; case 8 : chineseNumber = "捌"; case 9 : chineseNumber = "玖"; case 10 : chineseNumber = "拾"; } return chineseNumber; }
阐明:在switch
中break
肯定不能少。
不要让类型轻轻转换
<code class="java">/** 光速*/ private static final int LIGHT_SPEED = 30 * 10000 * 1000; public static void main(String[] args) { long dis = LIGHT_SPEED * 60 * 8; // -2028888064 System.out.println(dis); }
阐明:LIGHT_SPEED * 60 * 8
计算后是int
类型,可能存在越界问题,尽管,咱们在代码中写了转换为Long
型,然而,在Java中是先运算后在进行类型转换的,也就是LIGHT_SPEED * 60 * 8
计算后是int
型,超出了长度,从头开始,所以为负值,批改为显示的定义类型。如下:
<code class="java">/** 光速*/ private static final long LIGHT_SPEED = 30L * 10000 * 1000; public static void main(String[] args) { long dis = LIGHT_SPEED * 60 * 8; System.out.println(dis); }
防止带有变长参数的办法重载
<code class="java">public class MainTest { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println(PriceTool.calPrice(12, 1)); // 1 } } class PriceTool { public static int calPrice(int price, int discount) { return 1; } public static int calPrice(int price, int... discount) { return 2; } }
阐明:编译器会从最简略的开始猜测,只有合乎编译条件的即采纳。
少用动态导入
<code class="java">import static java.lang.Math.PI; public double calCircleArea(double r) { return Math.PI * r * r; } public double calBallArea (double r) { return 4 * PI * r * r; }
阐明:动态导入能够缩小代码的量,但不易于浏览,可读性差。
提防包装类型的null值
<code class="java">public static int f(List<Integer> list){ int count = 0; for (Integer i : list){ count += (i != null) ? i : 0; } return count; }
阐明:包装对象和拆箱对象能够自在转换,这不假,然而要剔除null值,null值并不能转换为根本类型。对于此问题,咱们谨记一点:包装类型参加运算时,要做null值校验。
审慎包装类型的大小比拟
举个例子。i==j false
。Integer是援用类型。
<code class="java">public static void main(String[] args){ Integer i = new Integer(100); Integer j = new Integer(100); System.out.println(i == j); }
防止instanceof非预期后果
instanceof是一个简略的二元操作符,它是用来判断一个对象是否是一个类实例的,两侧操作符须要有继承或实现关系。
<code class="java">public static void main(String[] args) { // String对象是否是Object的实例 - true,"String"是要给字符串,字符串继承了Object,当然是Object的实例。 boolean b1 = "String" instanceof Object; // String对象是否是String类的实例 - true,一个类的对象当然是一个类的实例。 boolean b2 = new String() instanceof String; // Object对象是否是String的实例,编译报错,Object是父类。 boolean b3 = new Object() instanceof String; // 拆箱类型是否是装箱类型的实例,编译报错,“A”是一个Char型,是一个根本类型,instanceof只能用于对象判断。 boolean b4 = "A" instanceof Character; // 空对象是否是String的实例 - false,instanceof的规定,右边为null,无论左边什么类型,都返回false。 boolean b5 = null instanceof String; // 类型转换后的空对象是否是String的实例 - false,null能够说是没有类型,类型转换后还是null。 boolean b6 = (String) null instanceof String; // Date对象是否是String的实例,编译报错,Date类和String类没有继承关系 boolean b7 = new Date() instanceof String; }
不要轻易设置随机数种子
在Java中,随机数的产生取决于种子,随机数和种子之间的关系听从以下两个准则: 种子不同,产生不同的随机数 ;种子雷同,即便实例不同也产生雷同的随机数。
<code class="java">public static void main(String[] args) { Random r = new Random(); for (int i = 1; i < 4; i++) { System.out.println("第" + i + "次:" + r.nextInt()); } } 运行后果: 第1次:846339168 第2次:756051503 第3次:1696875906
程序启动后,生成的随机数会不同。然而每次启动程序,生成的都会是三个随机数。产生随机数和种子之间的关系如下:
1)种子不同,产生不同的随机数。
2)种子雷同,即便实例不同也产生雷同的随机数。
Random的默认种子(无参结构)是System.nanoTime()
的返回值(jdk1.5以前是System.currentTimeMillis()),这个值是间隔某一个固定工夫点的纳秒数,不同的操作系统和硬件有不同的固定工夫点,随机数天然也就不同了。
防止在构造函数中初始化其它类
<code class="java">public class Client35 { public static void main(String[] args) { Son son = new Son(); son.doSomething(); } } // 父类 class Father { public Father() { new Other(); } } // 相干类 class Other { public Other() { new Son(); } } // 子类 class Son extends Father { public void doSomething() { System.out.println("Hi, show me Something!"); } }
阐明:造成构造方法循环调用。
优先应用整型池
Integer缓存了-128-127的Integer对象。所以通过装箱(Integer.valueOf())取得的对象能够复用。
<code class="java"> public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
性能思考,首选数组
<code class="java">private static int getSumForArray(int[] array) { int sum = 0; for (int i = 0; i < array.length; i++) { sum += array[i]; } return sum; } private static int getSumForList(List<Integer> list) { return list.stream().mapToInt(Integer::intValue).sum(); }