1. Java的坑之字符串拼接
Java在字符串拼接的时候,如果使用的是加号拼接,每次拼接过程都会生成一个新的字符串对象,会对性能造成一定影响。因此,在Java中进行字符串拼接时,建议使用StringBuilder或者StringBuffer进行拼接,这两种类可以降低不必要的开销。
// bad example
String result = "";
for (int i = 0; i < list.size(); i++) {
result += list.get(i);
}
// good example
StringBuilder builder = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
builder.append(list.get(i));
}
String result = builder.toString();
1.1 字符串格式化
Java中的字符串格式化也是需要注意的地方。如果在多次拼接字符串时,需要使用格式化,可以使用String.format()方法进行格式化。不能使用字符串拼接或加号进行格式化,因为这会生成多个中间字符串对象,非常低效。
// bad example
String result = "Name: " + name + ", Age: " + age;
// good example
String result = String.format("Name: %s, Age: %d", name, age);
2. Java的坑之控制流语句
Java中的控制流语句也存在一些误区。
2.1 for循环中的变量
在for循环中定义的变量,会在循环结束后继续存在于作用域中。因此,在循环外部不能再定义同名变量。这一点容易被忽略。
// bad example
for (int i = 0; i < list.size(); i++) {
// ...
}
int i = 10; // error: variable i is already defined in method
// good example
int i;
for (i = 0; i < list.size(); i++) {
// ...
}
i = 10; // OK
2.2 Break和Continue
Java的break和continue语句也需要注意。如果在循环中使用break或continue,一定要注意代码执行的顺序,否则可能会陷入无限循环。
// bad example: 无限循环
while (true) {
for (int i = 0; i < 10; i++) {
if (i == 5) {
continue; // 跳过5,进入下一次循环
}
System.out.println(i);
}
}
// good example: 正常退出
outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i == 5 && j == 5) {
break outer; // 退出所有循环
}
System.out.println("i=" + i + ", j=" + j);
}
}
3. Java的坑之异常处理
Java中的异常处理是一个非常重要的话题。在处理异常时,一定要注意抛出异常的时机和异常的处理方式。
3.1 不要过度捕获异常
在捕获异常时,一定要注意不要过度捕获异常。如果过度捕获异常,会使得代码逻辑变得不清晰,也会对性能产生影响。
// bad example
try {
// ...
} catch (Exception e) {
// do nothing
}
// good example
try {
// ...
} catch (IOException e) {
// handle IOException
} catch (SQLException e) {
// handle SQLException
}
3.2 不要忽略异常
在处理异常时,遇到异常一定要加以处理,并且要在处理时给出明确的提示。不能简单地忽略异常或者只打印日志。
// bad example
try {
// ...
} catch (Exception e) {
e.printStackTrace();
}
// good example
try {
// ...
} catch (IOException e) {
System.out.println("Failed to read file: " + e.getMessage());
} catch (SQLException e) {
System.out.println("Failed to query database: " + e.getMessage());
}
3.3 使用finally释放资源
在处理资源时,一定要使用finally语句块来释放资源。不能简单地使用try语句块释放资源,因为如果在try块中发生了异常,资源就不能被释放,这会导致资源泄露。
// bad example
FileInputStream input = null;
try {
input = new FileInputStream("file.txt");
// ...
} catch (IOException e) {
// handle IOException
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
// ignore IOException
}
}
}
// good example
try (FileInputStream input = new FileInputStream("file.txt")) {
// ...
} catch (IOException e) {
// handle IOException
}
4. Java的坑之并发编程
Java的并发编程也需要注意一些问题。
4.1 线程安全
在多线程环境中,一定要注意线程安全。线程不安全的代码会产生非常严重的后果。
// bad example
static int count = 0;
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
count++;
}).start();
}
Thread.sleep(1000); // wait for all threads finish
System.out.println("count=" + count);
}
// good example
static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
count.incrementAndGet();
}).start();
}
Thread.sleep(1000); // wait for all threads finish
System.out.println("count=" + count);
}
4.2 死锁
在多线程环境中,死锁是一个非常危险的问题。因此,在并发编程中一定要注意避免死锁。
// bad example
Thread t1 = new Thread(() -> {
synchronized(A) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized(B) {}
}
});
Thread t2 = new Thread(() -> {
synchronized(B) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized(A) {}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// good example
Thread t1 = new Thread(() -> {
try {
if (A.tryLock()) {
try {
Thread.sleep(100);
if (B.tryLock()) {
try {
// ...
} finally {
B.unlock();
}
}
} finally {
A.unlock();
}
}
} catch (InterruptedException e) {
// ...
}
});
Thread t2 = new Thread(() -> {
try {
if (B.tryLock()) {
try {
Thread.sleep(100);
if (A.tryLock()) {
try {
// ...
} finally {
A.unlock();
}
}
} finally {
B.unlock();
}
}
} catch (InterruptedException e) {
// ...
}
});
t1.start();
t2.start();
t1.join();
t2.join();
5. Java的坑之集合类
Java的集合类也需要注意。
5.1 集合类初始化
在集合类初始化时,一定要指定集合的初始容量。如果没有指定初始容量,会带来不必要的开销。
// bad example
List<String> list = new ArrayList<>();
// good example
List<String> list = new ArrayList<>(32);
5.2 遍历集合类
在遍历集合类时,一定要使用迭代器进行遍历。不能使用for循环或者forEach循环进行遍历,因为它们可能会导致遍历过程中集合的结构发生修改,从而产生ConcurrentModificationException异常。
// bad example
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String s : list) {
if (s.equals("b")) {
list.remove(s); // error: ConcurrentModificationException
}
}
// good example
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String s = iter.next();
if (s.equals("b")) {
iter.remove();
}
}
5.3 使用equals()比较对象
在比较对象内容时,一定要使用equals()方法。不能简单地使用==比较,因为==比较的是对象的引用是否相等,而不是对象的内容是否相等。
String s1 = "abc";
String s2 = new String("abc");
boolean b1 = (s1 == s2); // false
boolean b2 = s1.equals(s2); // true