1 String.split(String regex), 传入的参数是正则表达式,有一些特殊字符(比如.[]()| 等)需要转义。
2 关于枚举类型,一般用作常量,理想情况下,枚举中的属性字段是私有的,并在私有构造函数中赋值,没有对应的 Setter 方法,最好加上 final 修饰符。
public enum PersonSex2 {
MALE("", "man"),
FEMALE("", "female");
// value/description用final,因为一般枚举类型被用作常量,不可以修改
private final String value; // value可以是其他类型,一般用int
private final String description; // description 一般用String。
// 构造函数用private,是因为不想在外部被实例化。
private PersonSex2(String value, String description) {
this.value = value;
this.description = description;
}
// 只有get方法,没有set方法,是因为不会setter。
public String getValue() {
return value;
}
public String getDescription() {
return description;
}
}
3 返回空数组和空集合而不是 null
返回 null ,需要调用方强制检测 null ,否则就会抛出空指针异常。返回空数组或空集合,有效地避免了调用方因为未检测 null 而抛出空指针异常,还可以删除调用方检测 null 的语句使代码更简洁。
public static List<String> getResults() { return new ArrayList<>(0); } public static List<String> getResultList() { return Collections.emptyList(); } public static Map<String, String> getResultMap() { return Collections.emptyMap(); }
4 禁止使用构造方法 BigDecimal(double)
浮点数进行计算,有精度损失,不准确,如下(Java中float的精度为6-7位有效数字。double的精度为15-16位):
System.out.println(0.05 + 0.01); System.out.println(1.0 - 0.42); System.out.println(4.015 * 100); System.out.println(123.3 / 100); 0.060000000000000005 0.5800000000000001 401.49999999999994 1.2329999999999999
BigDecimal b = new BigDecimal(1.02); BigDecimal c = new BigDecimal("1.01"); BigDecimal d = new BigDecimal("1.02");
System.out.println(a.add(b)); // a和b是用float构造的,也有精度损失
System.out.println(c.add(d)); // c和d是用String 类型构造的,没有精度损失 2.0300000000000000266453525910037569701671600341796875 2.03
用BigDecimal value = BigDecimal.valueOf(0.1D)代替BigDecimal value = new BigDecimal(0.1D);
/** * @author: Ji YongGuang. * @date: 19:50 2017/12/14. */ public class BigDecimalUtil { private BigDecimalUtil() { } public static BigDecimal add(double v1, double v2) {// v1 + v2 BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2); } public static BigDecimal sub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2); } public static BigDecimal mul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2); } public static BigDecimal div(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); // 2 = 保留小数点后两位 ROUND_HALF_UP = 四舍五入 return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// 应对除不尽的情况 } }
5 过时代码添加 @Deprecated 注解
6 不要用NullPointerException判断空
空指针异常应该用代码规避(比如检测不为空),而不是用捕获异常的方式处理。
public String getUserName(User user) { if (Objects.isNull(user)) { return null; } return user.getName(); }
在JDK7版本的时候,Java引入了java.util.Objects
工具类,用于封装一些平时使用频度很高或容易出错的操作,这些操作形成了Objects的各个方法,下来我们来看看这些方法。
equals()
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
有别于Object.equals()
,这个方法可以避免空指针异常。
deepEquals()
Object.equals()
用于比较两个对象的引用是否相同,而deepEquals()
却扩展成了可以支持数组。
toString()
归根结底,其内部最终调用了对象的toString()
方法。额外多了空指针判断。
compare()
用于比较两个对象。
requireNonNull()
在对象为空指针时,抛出特定message的空指针异常
isNull() 和 nonNull()
这两个方法用于判断对象为null和对象不为null。通常情况下,我们不会直接使用这两个方法,而是使用比较操作符==
和!=
。这两个方法主要用在jdk8开始支持的流计算里面。
7 公有静态常量应该通过类访问
而不是用实例去访问,因为容易让人误解,以为每一个实例有一个静态常量。
8 用 catch 语句捕获异常后,什么也不进行处理,就让异常重新抛出,这跟不捕获异常的效果一样(因为如果异常不捕获,就会一直往上层函数抛),可以删除这块代码或添加别的处理。
9 工具类应该屏蔽构造函数
工具类是一堆静态字段和函数的集合,不应该被实例化。但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数。所以,为了避免 java "小白"使用有误,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数。
10 建议使用 try-with-resources 语句更优雅的关闭资源
传统的关闭资源的方式:在finally中判断,如果资源句柄不是NULL,关闭,而且还有再catch exception
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) { ... } } } }
java 1.7后,如果关闭的资源实现了AutoCloseable接口,就可以使用更简洁的try-with-resources
public interface Closeable extends AutoCloseable
private void handle(String fileName) { try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) { String line; while ((line = reader.readLine()) != null) { ... } } catch (Exception e) { ... } }
11 频繁调用 Collection.contains 方法请使用 Set代替List,效率更高。
12 List 的随机访问
大家都知道数组和链表的区别:数组的随机访问效率更高。当调用方法获取到 List 后,如果想随机访问其中的数据,并不知道该数组内部实现是链表还是数组,怎么办呢?可以判断它是否实现* RandomAccess *接口。
13 字符串拼接使用 StringBuilder
String: 拼接字符串时候会产生很多无用的中间对象,如果频繁的进行这样的操作对性能有影响。
StringBuilder: 一般的字符串拼接在编译期 java 会进行优化,但是在循环中字符串拼接, java 编译期无法做到优化,所以需要使用 StringBuilder 进行替换。性能好,但是线程不安全。
StringBuffer: 提供append和add方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了synchronized。但是保证了线程安全是需要性能的代价的
14 集合初始化尽量指定大小
集合也是有大小限制的。每次扩容的时间复杂度很有可能是 O(n) ,所以尽量指定可预知的集合大小,能减少集合的扩容次数。
15 需要 Map 的主键和取值时,应该迭代 entrySet()
当循环中只需要 Map 的主键时,迭代 keySet() 是正确的。但是,当需要主键和取值时,迭代 entrySet() 才是更高效的做法,比先迭代 keySet() 后再去 get 取值性能更佳。(经过测试,后一种比前一种是快)
for (String key : map.keySet()) { String value = map.get(key); } for (Map.Entry<String, String> entry : map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); }