java151和152_每天10个编码坑(《编写高质量代码 改善Java程序的151个建议》)

本文列举了10个Java编程中常见的问题和陷阱,包括常量与变量的命名规范,三元操作符类型一致性,变长参数方法重载的注意事项,null值与空值对变长方法的影响,以及静态导入的使用建议等。通过对这些问题的解析,帮助开发者提升代码质量,避免潜在的运行时错误。
摘要由CSDN通过智能技术生成

NO.1 不要在常量和变量中出现易混淆的字母

6cead41b5ff8d0500968b23e6586781f.png

给long类型的变量赋值时,将长整型变量的标示字母“l”进行大写。

NO.2 莫让常量蜕变成变量

f0a62775660ecebf01bab50488972013.png

这种常量的定义方式不可取,常量就是常量,务必让常量的值在运行期保持不变

NO.3 三元操作符的类型务必一致

92f717f5bea94f5b910e1a93fb9d9c10.png

这段代码的结果是false,在使用三目运算符时,第一个操作数是int,第二个操作数是double,会默认进行隐式类型转换返回范围最大的数据类型,即double,那么s2的结果实际上是90.0,最终导致结果是false。

那么会有小伙伴提出疑问了,为什么是整形转为浮点,而不是浮点转为整形呢?这就涉及到三目运算符的转换规则:

(1)若两个操作数不可转换,则不做转换,返回值类型为Object

(2)若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int转为long,long转为float等

(3)若两个操作数中有一个是数字S,另一个是表达式,且其类型标示为T,那么,若数字S在T的范围内,则转换为T类型,若S超出了T的范围,则T转换为S类型

(4)若两个操作数都是直接量数字,则返回值类型为取值范围最大者

所以,为减少错误的产生,要保证三目运算符中的两个操作数类型一致

NO.4 避免带有变长参数的方法重载

public class Client {

//简单折扣计算

public void calPrice(double price, int discount) {

double knockdownPrice = price * discount / 100;

System.out.println("简单折扣后价格:" + formatCurrency(knockdownPrice));

}

//简单折扣计算

public void calPrice(double price, int... discounts) {

double knockdownPrice = price;

for (int discount : discounts) {

knockdownPrice = knockdownPrice * discount / 100;

}

System.out.println("简单折扣后价格:" + formatCurrency(knockdownPrice));

}

private String formatCurrency(double price) {

return NumberFormat.getCurrencyInstance().format(price / 100);

}

public static void main(String[] args) {

Client client = new Client();

client.calPrice(49900, 75);

}

}

程序执行结果如下:

6f174beb86dad563a7a64d40dc5e185b.png

这是一个计算商品价格折扣的类,里面有两个方法,一个是简单折扣,一个是复杂折扣,可以理解为折上折,那么这两个方法是重载吗?当然是,符合重载方法名一致,参数类型或参数个数不一致的特征,并且,第二个方法实际上是包含第一个方法在内的,那么为什么编译器在执行时会选择简单折扣方法,而不是第二个变长参数的方法呢?

这是因为Java在编译时,会首先根据实参的数量和类型进行处理,也就是查找到calPrice(double,int)方法,并没有把int转为int数组。

所以,为了程序能被“人类”看懂,还是慎重选择变长参数的方法重载吧,否则伤人脑筋不说,说不定哪天就陷入小陷阱了。

NO.5 别让null值和空值威胁到变长方法

public void methodA(String str, Integer... is){ }

public void methodA(String str, String... strs){ }

public static void main(String[] args) {

Client client = new Client();

client.methodA("china", 0);

client.methodA("china", "people");

client.methodA("china");

client.methodA("china",null);

}

上面的代码是编译不过的,有两处出现问题,client.methodA(“china”)和client.methodA(“china”,null),两处的提示是相同的:方法模糊不清,编译器不知道该使用哪个方法,但是这两处反映的代码坏味道却是不同的。

对于client.methodA(“china”),根据实参“china”,判定两个方法都符合形参格式,编译器不知道该选择哪个,所以报错。此处是由于设计者违反了KISS原则(Keep It Simple,Stupid,即懒人原则),按照此规则设计的方法应该很容易调用,可是在遵循规范的情况下,程序竟然出错了,这对设计者和开发者而言都是严禁出现的。

对于client.methodA(“china”,null),直接量null是没有类型的,所以对于两个重载方法都是满足的,就导致编译器不知道选择哪个,此处除了不符合懒人原则外,也隐藏了一个非常不好的编码习惯,调用者隐藏了实参类型,这是非常危险的,不仅调用者需要猜测该调用哪个方法,被调用者也可能产生内部逻辑混乱的情况。对于本例应该做如下修改:

public static void main(String[] args) {

Client client = new Client();

String[] strs = null;

client.methodA("china",strs);

}

让编译器知道这个strs是String[]类型的,也就减少了错误的发生。

NO.6 覆写变长方法也循规蹈矩

static class Base{

void fun(double price, int... discounts){

System.out.println("Base..........fun");

}

}

static class Sub extends Base{

@Override

void fun(double price, int[] discounts) {

System.out.println("Sub...........fun");

}

}

public static void main(String[] args) {

Base base = new Sub();

base.fun(100, 50);

Sub sub = new Sub();

sub.fun(100, 50);

}

以上代码在sub.fun(100, 50)是编译不通过的,但是base调用时却是正确的,这是因为base对象把子类对象Sub做了向上转型,也就是说形参列表是由父类决定的,由于是变长参数,所以50这个实参会被编译成“{50}”数组,在由子类执行,这是没有错的。

但是直接调用子类时,第二个形参的类型也是数组,但是编译器不会在两个没有继承关系的类之间做类型转换,所以编译器会去找fun(double,int)这个方法,类型不匹配编译器自然就会拒绝执行,并提示错误。

当然,这只是个特例,你会发现覆写的方法参数列表竟然和父类不同,这违背了覆写的定义。覆写的方法参数与父类相同,不仅仅是类型、数量,还包括显示形式

NO.7 警惕自增的陷阱

public static void main(String[] args) {

int count = 0;

for (int i = 0; i < 10; i++) {

count = count++;

}

System.out.println("count=" + count);

}

看这个问题之前,首先要说明一下i++和++i,大学C++老师就说过,自增有两种形式,i++和++i,i++是先赋值在自增,++i是先自增在赋值,这样理解的很多年也没出现问题,知道出现这段代码才开始怀疑自己。

因为这短代码的结果是:0.

count++是一个表达式,是有返回值的,返回值就是count自增前的值,也就是0

Java对自增是这样处理的:

step1:JVM把count的值拷贝到临时变量区

step2:count值加1,此时count=1

step3:返回临时变量区的值,注意这个值是0,没有被修改过

step4:返回值赋值给count,此时count被重置为0

解决方法其实很简单:只需要把count=count++修改为count++即可

NO.8 不要让旧语法困扰你

static void saveDefault(){}

static void save(int fee){}

public static void main(String[] args) {

int fee = 200;

saveDefault():save(fee);

}

这段代码看的我也很迷,毕竟我在敲这段代码的时候已经编译不通过了,报的语法错误,但是这是在《编写高质量代码 改善Java程序的151个建议》书中作者实际遇到的一段代码,据说当时并没有编译错误,甚至还可以运行,不过现在是无法复现了,只是举个例子避免这种情况。

saveDefault():save(fee)是一种C语言中的goto语法,其中的“:”称之为标号,goto语句中有着“double face”作用的关键字,它可以让程序从多层循环中跳出,不需要一层层退出,这点确实很好,但同时带来了代码结构混乱的问题,而且程序跳来跳去,出现问题时调试起来让人头晕。

但其实众所周知,Java上已经没有goto语句了,但是依然保留了该关键字,只是不进行语义处理而已,并且Java扩展了break和continue关键字,他们的后面都可以加上标号做跳转,完全实现了goto的功能,只不过也把goto的诟病也带了出来,所以在阅读大佬的源码时,几乎很少见到break和continue后跟标号的情况,甚至都很少看到break和continue,这可是提高代码可读性的一剂良药,还是舍弃旧语法吧,新语法真香!

NO.9 少用静态导入

public class MathUtils {

public static double calCircleArea(double r) {

return Math.PI * r * r;

}

public static double calBallArea(double r) {

return 4 * Math.PI * r * r;

}

}

这是一个计算面积的工具类,当其中的方法过多时,需要过多的书写Math.PI(圆周率常量,Math类自带),繁琐且多余,所以此时可以使用静态导入的方法来解决:

import static java.lang.Math.PI;

public class MathUtils {

public static double calCircleArea(double r) {

return PI * r * r;

}

public static double calBallArea(double r) {

return 4 * PI * r * r;

}

}

由此可见,使用了静态导入,代码中就不需要再写类名了,但是我们知道“类是一类事物的描述”,缺少了类名的修饰,静态属性和静态方法的表象意义可以被无限放大,这会让读者很难弄清楚这个属性和方法的意义何在,特别是在一个类中有多个静态导入语句时,若同时用了通配符,简直是一场灾难。举个例子:

7d5959888b9ef5c7344fd1a7a43223f4.png

这段代码看着就让人头大,分不清哪个方法是哪个类的,有什么作用。

所以,对于静态导入,一定要遵循两个原则:

(1)不使用通配符,除非是导入静态常量类

(2)方法名是具有明确、清晰表象意义的工具类

何为具有明确、清晰表象意义的工具类?

25fa376b42d8a25ff3648c83f8d4e316.png

assertEquals断言是否相等

assertFalse断言是否为假

这样子的方法清晰直观,可用静态导入

NO.10 不要在本类中覆盖静态导入的变量和方法

import static java.lang.Math.PI;

import static java.lang.Math.abs;

pulic class client{

public static final String PI="祖冲之";

public static final int abs(int abs){

return 0;

}

public static void main(String[] args) {

System.out.println("PI="+PI);

System.out.println("abs(100)="+abs(-100));

}

}

运行结果:

feebfae20ae19dcd6bb764e442f2ef62.png

很明显是本类中的静态属性和方法被引用了,那为什么不是Math类中的属性和方法呢,那是因为编译器有一个“最短路径”原则,如果能够在本类中查找到的变量,常量,方法,就不会到其他包或父类、接口中查找,以确保本类中的属性、方法优先。

因此,如果要变更一个被静态导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖。

标签:151,10,Java,int,double,void,count,static,public

来源: https://blog.csdn.net/qq_45145801/article/details/111311824

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值