【Java实战】常用编码规范整理和解析
前言
写代码的这些年,关于代码的规范,有和同事们探讨、网络上摸索和各种插件提醒,在这边想总结一下关于编码规范对于程序运行的种种影响。
一般方法
【魔法值 & 硬编码】
- 魔法值: 该值的存在,大大降低了代码的可读性、维护性和灵活性。
//示例代码
String a = "a";
//以下"b"会被提示为“魔法值”
if ("b".equals(a)) {
System.err.println(a);
}
建议声明一个拥有字面意义的常量,如下:
String a = "a";
//声明拥有字面意义的常量
public static final String LOWERCASE_B = "b"
if (LOWERCASE_B.equals(a)) {
System.err.println(a);
}
这样做的好处是:
1、它更容易阅读和理解。
2、改变数字的值更容易,因为它没有冗余地重复。更改一个神奇数字的值是容易出错的,因为相同的值经常在程序的不同位置使用多次。
3、它促进了参数化。
- 硬编码: 硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同。
//示例代码
String key = "mima123456"; //硬编码
if (Constant.KEY.equals(key)) {
System.out.println("登录成功!");
}
编写代码时,不应该出现硬编码,因为硬编码不便于维护,可读性也低。
如果需要多次使用的话,可以声明常量或枚举值进行代替。
常量示例如下:
//示例代码
/**
* 某平台密钥
*/
public final static String KEY = "mima123456"; //常量
if (Constant.KEY.equals(KEY)) {
System.out.println("登录成功!");
}
枚举值示例如下:
//示例代码
public enum ServiceResultEnum {
LOGIN_SUCCESS("登录成功!欢迎您!")
private String result;
ServiceResultEnum(String result) {
this.result = result;
}
public String getResult() {
return result;
}
}
class Sample {
public static void main(String[] args) {
System.out.println(LOGIN_SUCCESS.getResult());
}
}
【三目运算符、if else和switch case的选择】
在写代码的过程中,逻辑是非常重要的一环,除了对业务的了解之外,在选择判断方法上可以更进一步提高效率。
- 三目运算符:
//代码示例
int a = 0, b = 1;
String result = a == b ? "Yes" : "No";
在运算过程中,需要考虑类型转换的问题。
可读性:低
灵活性:低
运算效率:高
适用场景:比较适合在条件较少情况下使用。
- if else:
//示例代码
int a = 3;
if (a == 1) {
System.out.println(a);
} else if (a == 2) {
System.out.println(b);
} else if (a == 3) {
System.out.println(c);
} else {
System.out.println("Nothing");
}
每一个条件都要依次往下进行逻辑判断。
可读性:高
灵活性:低
运算效率:低
适用场景:使用前要思考判断的可行性,尽可能降低判断的次数。
- switch case:
//示例代码
int a = 1;
switch (a) {
case 1:
System.out.println("a is 1");
break;
case 2:
System.out.println("a is 2");
break;
case 3:
System.out.println("a is 3");
break;
default:
System.out.println("unknown");
}
只做一次运算,然后将表达式的值和case的值逐个比较,直到匹配正确,要求case为常量。
可读性:高
灵活性:中
运算效率:高(相对于条件较多情况下)
//补充lambda表达式写法
int a = 5;
switch (a) {
case 1 -> System.out.println("a is 1");
case 2 -> System.out.println("a is 2");
//可多个值进行匹配
case 3, 4, 5 -> System.out.println("a is " + a);
//default可有可无
default -> System.out.println("unknown");
}
总结:对于相同类型二选一的判断都会选择用三目,由于为了维持代码可读性,三个以上的判断都会以if else或switch case为主。(if else基本上写第一个条件都是使用概率>50%以上的值,其余判断条件只是为了排查问题并进行筛选,条件太多依旧选择switch case)
【var关键字的使用】
此内容参考自【Java基础核心知识学习——Java var关键字】
在Java10以后的版本,新增了var关键字来声明一个变量,可以理解为Java的语法糖,在编译时就会发现
可以从变量的初始值推导变量的类型,这样就可以用var关键字声明局部变量,而无需指定类型。
这边举个栗子:
Employee tony = new Employee(name, salary, year, month, day);
使用var关键字,则可写成这样:
var tony = new Employee(name, salary, year, month, day);
var关键字个人觉得可以使代码更为简洁,建议使用
注意点
- 只能用于局部变量上
- 声明时必须初始化
- 不能用作方法参数和字段
- 尽量不要对数值类型使用var,因为对于int,long,double类型,需要当心0、0L、0.0之间的区别。
HashMap
【HashMap初始值】
相信有用过阿里代码规范插件的童鞋,在New一个HashMap<>()的时候有这个提醒“【HashMap】初始化时,尽量指定初始值大小”,这边就来说明一下为何这个插件要提醒我们。
原理:HashMap的扩容,会调用resize方法进行rehash操作,每次扩容都会重建Hash表,这个动作比较影响性能。
总结:HashMap的实例有两个参数影响其性能:初始容量 和 加载因子。
-
初始容量:容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。
-
加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度,默认为0.75,是在时间和空间成本上寻求的一种折中。
比如说,我需要在HashMap里装载100个元素,那么HashMap的初始容量应为多少?
首先考虑时间上和空间上能达到最佳性能:
100 / 0.75 = 133.33... 100 / 0.75 = 133.33... 100/0.75=133.33...
为了避免HashMap进行rehash操作,我们对这个值进行向上取整,所以结果为134。
然而,还有一个需要考虑的问题:哈希碰撞
要将“哈希碰撞”的概率降到最低,HashMap的长度必须为 2 n 2^n 2n 。
和 133.33… 的最接近的是 2 7 = 128 2^7 = 128 27=128 。
因为 128 < 133.33… ,这样会导致一次rehash
为了要避免rehash操作,所以最终的选择是 2 8 = 256 2^8 = 256 28=256 。
但是在JDK1.8以后,HashMap的结构从 { 数组 + 链表 } 变成 { 数组 + 链表 + 红黑树 }
关于HashMap从链表和红黑树的相互转换的阈值,有以下两种情况:
- 当链表中节点数量大于等于 8 时,链表会转成红黑树。
- 当链表中节点数量小于等于 6 时,红黑树会转成链表。
由于JDK1.8版本以后新增了转换机制优化了HashMap的算法,所以现在实测上对于初始容量大小的定义所导致效率的问题,影响其实已经很小了。
【HashMap垃圾回收】
有过Java编程经验的童鞋们,多多少少会遇到“内存泄漏”的问题,其中一个主要原因很有可能就是HashMap使用的问题。
在Map使用完毕时,由于key和value之间存在着"强引用",不再使用的Map不会被垃圾回收。
垃圾回收器(GC)跟踪活动对象,只要映射对象是活动的,其中所有的桶也是活动的,所以该活动对象不能被回收。
总结:将不再使用的Map设置为null。( map = null )
PS:Map的clear()方法只是把key中的value清空,Map对象还是存在的。
结语
因为今年特别忙,所以这篇放了大半年以上都没更新也没发布,暂时先发布一下,有时间再回来更新 ^ _ ^