1. 需要Map的主键和取值时,应该迭代
entrySet()
当循环中只需要Map的主键时,迭迭代keySet()
时正确的。但是,当需要主键和取值时,迭代entrySet()
才是更高效的做法,比迭代keySet()
后再去get
取值性能更佳。
Map<String, String> map = ...;
for (String key : map.keySet()) {
String valueString = 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.size()
来检测空逻辑上没有问题,但是使用Collection.isEmpty()
使得代码更易读,并且可以获得更好的性能。任何Collection.isEmpty()
实现的时间复杂度都是O(1),但是某些Collection.size()
实现的时间复杂度可能是O(n)。
if (collection.size() == 0) {
...
}
if (collection.isEmpty()) {
...
}
如果还需要检测null ,可采用:
3.不要把集合对象传给自己
将集合作为参数传递给集合自己的方法要么是一个错误,要么是无意义的代码。
此外,由于某些方法要求参数在执行期间保持不变,因此将集合传递给自身可能会导致异常行为。
List<String> list=new ArrayList<String>();
list.add("hello");
list.add("world");
if (list.contains(list)) {//无意义,总返回true
}
list.remove(list);//性能差,直接使用clear
4.集合初始化尽量指定大小
java 的集合类用起来十分方便,但是看源码可知,集合也是有大小限制的。每次库容的时间复杂度很可能是O(n),所以尽量制定可预知的集合大小,能减少集合的扩容次数。
int[] arr = new int[] { 1, 2, 3 };
List<Integer> list2 = new ArrayList<>();
for (int i : arr) {
list2.add(i);
}
int[] arr = new int[] { 1, 2, 3 };
List<Integer> list2 = new ArrayList<>(arr.length);
for (int i : arr) {
list2.add(i);
}
5.频繁调用Collection.contains方法请使用Set
在java集合类库中,List的Contains方法普遍时间按复杂度是O(n),如果在代码中需要频繁调用contains方法查找数据,可以先将list转换成HashSet实现,将O(n)的时间复杂度降为O(1)。
List<Integer> list =...;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
// 时间复杂度O(n)
list.contains(i);
}
List<Integer> list = ...;
Set<Integer> set=new HashSet<Integer>();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
// 时间复杂度O(n)
set.contains(i);
}
6. 返回空数组和空集而不是null
返回null,需要调用方强制检测null,否则就会抛出空指针异常。返回空数组或空集合,有效地避免了调用方因未检测null而抛出空指针异常。返回空数组或空集合,有效地避免了调用方因未检测null而抛出空指针异常,还可以删除调用方检测null的语句使代码更简洁。
class Result {
}
public static Result[] getResults() {
return null;
}
public static List<Result> getResultList() {
return null;
}
public static Map<String, Result> getResultMap() {
return null;
}
public static void main(String[] args) {
Result[] results = getResults();
if (results != null) {
for (Result result : results) {
...
}
}
List<Result> resultList = getResultList();
if (results != null) {
for (Result result : resultList) {
...
}
}
Map<String, Result> resultMap = getResultMap();
if (resultMap != null) {
for (Map.Entry<String, Result> entry : resultMap.entrySet()) {
...
}
}
}
class Result {
}
public static Result[] getResults() {
return new Result[0];
}
public static List<Result> getResultList() {
return Collections.emptyList();
}
public static Map<String, Result> getResultMap() {
return Collections.emptyMap();
}
public static void main(String[] args) {
Result[] results = getResults();
for (Result result : results) {
}
List<Result> resultList = getResultList();
for (Result result : resultList) {
}
Map<String, Result> resultMap = getResultMap();
for (Map.Entry<String, Result> entry : resultMap.entrySet()) {
}
}
7.List的随机访问
数组和链表的区别:数组的随机访问效率更高。当调用方法获取到List
后,如果想随机访问其中的数据,并不知道该数组内部实现是链表还是数组,怎么办呢?可以判断它是否实现RandomAccess
接口。
// 调用别人的服务获取到list
List<Integer> list = otherService.getList();
if (list instanceof RandomAccess) {
// 内部数组实现,可以随机访问
System.out.println(list.get(list.size() - 1));
} else {
// 内部可能是链表实现,随机访问效率低
}
8.字符串拼接使用StringBuilder
一般的字符串拼接在编译期java会进行优化,但是在循环中字符串拼接,java编辑器无法做到优化,所以需要使用StringBuilder
进行替换。
String string = "";
for (int i = 0; i < 10; i++) {
string += 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
}
9.长整型常量后添加大写L
在使用长整型常量值时,后面需要添加L,必须是大写的L,不能是小写l,小写l容易跟数字1混淆而造成误解。
long value = 1l;
long max = Math.max(1L, 6);
long value = 1L;
long max = Math.max(1L, 6L);
10.不要使用魔法值
当你编写一段代码时,使用魔法值肯那个看起来很明确,但在调试时它们却不显得那么明确了。这就是为什么需要把魔法值定义为可读取常量的原因。但是,-1 、0 和 1不被视为魔法值。
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 (a == MAX_COUNT) {
}
不要使用集合实现来赋值静态成员变量
对于集合类型的静态成员变量,不要使用用集合实现来赋值,应该使用静态代码快复制。
private static Map<String, Integer> map = new HashMap<String, Integer>() {
{
put("a", 1);
put("b", 2);
}
};
private static List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
}
};
private static Map<String, Integer> map = new HashMap<String, Integer>();
static {
map.put("a", 1);
map.put("b", 2);
}
private static List<String> list = new ArrayList<String>();
static {
list.add("a");
list.add("b");
}
建议使用try-with-resources语句
java 7 中引入try-with-resources语句,该语句能保证将相关资源关闭,优于原来的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 e2) {
...
}
}
}
}
try (BufferedReader reader = new BufferedReader( new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
...
}
} catch (Exception e) {
...
}
工具类应该屏蔽构造函数
工具类是一堆静态字段函数的集合,不应该被实例化。但是,Java为每个没有明确定义构造函数的类添加了隐式公有构造函数。所以,为了避免别有使用有误,应该显示定义私有构造函数来屏蔽这个隐式公有构造函数。
public class MathUtils {
public static final double PI = 3.1415926D;
public static int sum(int a, int b) {
return a + b;
}
}
public class MathUtils {
private MathUtils() {}
public static final double PI = 3.1415926D;
public static int sum(int a, int b) {
return a + b;
}
}
公有静态常量应该通过类访问
虽然通过类的实例访问共有静态常量是允许的,但是容易让人误认为每个类的实例都有一个共有静态常量。所以,公有静态常量应该直接通过类访问。
public class User{
public static final String CONST_NAME="name";
}
User user=new User();
String nameKey=user.CONST_NAME;
public class User{
public static final String CONST_NAME="name";
}
String nameKey=User.CONST_NAME;
使用
String.valueOf(value)
代替""+value
当要把其他对象或类型转化为字符串时,使用String.valueOf(value)
比""+value
的效率更高。
int i = 1;
String s="" + i;
int i = 1;
String s=String,valueOf(i);
禁止使用构造方法
BigDecimal(double)
BigDecimal(double)
存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
BigDecimal value=new BigDecimal(0.1D);
BigDecimal value=BigDecimal.valueOf(0.1D);
优先使用常量或确定值来调用
equals
方法
对象的equals
方法容易抛空指针异常,应使用常量或确定有值得对象来调用equals
方法。
当然,使用java.util.Objects.equals()
方法是最佳实践。