Java编码坏习惯改进
总结自公众号:阿里技术,一方面做笔记,一方面觉得很有道理分享出来。
让性能更高
1.需要 Map 的主键和取值时,应该迭代 entrySet()
需要同时使用map的key和value取值时,迭代 entrySet()才是更高效的方法。比起先获取KeySet再迭代获取value性能更好,因为get方法是先根据key获取Node,再取得Node的value值。
反例:
Map<String, String> map = ...;
for (String key : map.keySet()) {
String value = map.get(key);
...
}
正例:
Map<String, String> map = ...;
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
...
}
2.使用Collection.isEmpty()检测空
Collection.isEmpty() 实现复杂度都是O(1), 但某些Collection.size()实现的复杂度是O(n)(说实话,哪些Collection的复杂度是O(n)的, 作者没有找到,读者评论能提示一下么,C++我知道mingw和msvc的库实现中size是这样的,但java中还没有看到过);
3.集合初始化尽量指定大小
集合是有容量的,一旦超过,就需要扩容和复制,为了减少扩容和复制,尽量指定大小。
4.字符串拼接使用StringBuilder
一般的字符串拼接会在编译器就行优化,但是在循环中的字符串拼接,java编译无法做到优化(why?),所以使用StringBuilder替换。
反例:
String s = "";
for (int i = 0; i < 10; i++) {
s += i;
}
正例:
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c; // 没问题,java编译器会进行优化
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
sb.append(i); // 循环中,java编译器无法进行优化,所以要手动使用StringBuilder
}
5.list的随机访问
当获取到的对象是List时,我们不能知道它的内部具体是数组还是链表,而数组的随机访问很简单,链表则需要一个个逐个访问,如何判断数组还是链表?可以判断他是否实现RandomAccess接口。
// 调用别人的服务获取到
listList<Integer> list = otherService.getList();
if (list instanceof RandomAccess) {
// 内部数组实现,可以随机访问
System.out.println(list.get(list.size() - 1));
}
else {
// 内部可能是链表实现,随机访问效率低
}
6.频繁调用Collections.contains方法请使用set
List的contains方法时间复杂度是O(n)的,如果需要频繁调用,最好将list转化为HashSet,那样的复杂度是O(1)的。
ArrayList<Integer> list = otherService.getList();
Set<Integer> set = new HashSet(list);
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
// 时间复杂度O(1)
set.contains(i);
}
让代码更优雅
1.特殊数据类型在后面跟上类型标记
如使用长整形时,后面加上L,必须大写,小写容易和1混淆。
long value = 1L;
long max = Math.max(1L, 5L);
2.不要使用魔法值
魔法值容易使代码的意义变得不明确,也许你编码时很清楚,但是过了很久,容易迷惑。
反例:
for (int i = 0; i < 100; i++){
...
}
if (a == 100) {
...
}
正例:
private static final int MAX_COUNT = 100;
for (int i = 0; i < MAX_COUNT; i++){
...
}
if (count == MAX_COUNT) { ...}
3.建议使用try-with-resources语句
java7中引入了这个语法糖,这个语句能保证资源自动关闭,比起原来的try-catch-finally简洁不少。
反例:
private void handle(String fileName) {
BufferedReader reader = null;
try {
String line;
reader = new BufferedReader(new FileReader(fileName));
while ((line = reader.readLine()) != null) {
...
}
} catch (Exception e)
{
...
} finally
{
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
...
}
}
}
}
正例:
private void handle(String fileName) {
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) { String line;
while ((line = reader.readLine()) != null) {
...
}
} catch (Exception e) {
...
}
}
4.删除多余无用的代码,字段,括号和参数。
5.工具类应该屏蔽构造函数
工具类是一堆静态字段和函数的集合,不应该被实例化,为了防止别人使用错误,应该屏蔽构造函数。
正例:
public class MathUtils {
public static final double PI = 3.1415926D;
private MathUtils() {}
public static int sum(int a, int b) { return a + b; }
}
6.公有静态常量应该使用类访问,而不要使用类的实例来访问。
7.不要使用NullPointerException来判断空
空指针异常应该用代码规避(比如检测不为空),而不是用捕获异常的方式处理。
反例:
public String getUserName(User user) {
try {
return user.getName();
} catch (NullPointerException e) {
return null;
}
}
正例:
public String getUserName(User user) {
if (Objects.isNull(user)) { return null; }
return user.getName();
}
8.使用String.valueOf(value)代替“”+value
当要把其它对象或类型转化为字符串时,使用 String.valueOf(value) 比""+value 的效率更高。
9.过时的代码使用@Deprecated注解注明
让代码远离bug
1.不要使用构造方法BigDecimal(double)
BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
反例:
BigDecimal value = new BigDecimal(0.1D); *// 0.100000000000000005551115...*
正例:
BigDecimal value = BigDecimal.valueOf(0.1D);; // 0.1
2.返回空数组和空集合而不是null
返回null,需要调用方检测null,否则会抛出异常,返回空数组和空集合,可以有效避免,并且让代码更简洁。
反例:
public static List<Result> getResultList() { return null;}
正例:
public static List<Result> getResultList() { return Collections.emptyList();}
3.优先使用常量或者确定值来调用equeals方法
对象的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals 方法。当然,使用 java.util.Objects.equals() 方法是最佳实践。
反例:
public void isFinished(OrderStatus status) {
// status可能为空,导致异常
return status.equals(OrderStatus.FINISHED); // 可能抛空指针异常
}
正例:
public void isFinished(OrderStatus status) {
return OrderStatus.FINISHED.equals(status);
}
public void isFinished(OrderStatus status) {
return Objects.equals(status, OrderStatus.FINISHED);
}
4.小心使用String.split(String regex)
String的split,传入的分割符是正则表达式,部分关键字,如(.|)需要转义
反例:
"a.ab.abc".split("."); // 结果为[]"a|ab|abc".split("|"); // 结果为["a", "|", "a", "b", "|", "a", "b", "c"]
正例:
"a.ab.abc".split("\\."); // 结果为["a", "ab", "abc"]
"a|ab|abc".split("\\|"); // 结果为["a", "ab", "abc"]
5.枚举的属性字段必须是私有不可变的
枚举通常被当做常量使用,如果枚举中存在公共属性字段或设置字段的方法,会导致枚举常量的属性值很容易被修改。理想情况下,枚举类型的属性字段是私有的,并且在私有构造函数中赋值,没有对应的setter方法,最好加上final修饰符。
反例:
public enum UserStatus {
DISABLED(0, "禁用"), ENABLED(1, "启用");
public int value;
private String description;
private UserStatus(int value, String description) {
this.value = value;
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
正例:
public enum UserStatus {
DISABLED(0, "禁用"), ENABLED(1, "启用");
private final int value;
private final String description;
private UserStatus(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
}