跳至主要內容

10个Java开发者必须改掉的过时编程习惯

DD编辑部原创JavaJava大约 5 分钟

10个Java开发者必须改掉的过时编程习惯

"如果你一直拖着过去不放,就无法向前进步。"这正是我在代码审查时对团队说的话。然而,每次我打开一个拉取请求时,我仍然看到过时的Java特性、坏习惯和应该在几年前就被淘汰的编码习惯。所以,如果你今天在使用Java,无论你是初级、中级还是高级开发者,这篇文章都适合你 🫵 。其中一些观点可能会让你感到不舒服。有些可能与你所学的相违背。但这正是你应该读到最后的原因。

1. 错误使用 Optional.get()

Optional是一个很棒的特性,但我看到许多开发者误用了它:

Optional<String> value = getValue();
String result = value.get(); // 可能抛出 NoSuchElementException!

这是错误的!

如果你在使用**Optional**,就要拥抱它的API:

String result = value.orElse("default");
// 或者
value.ifPresent(val -> System.out.println(val));
// 或者
String result = value.orElseThrow(() -> new IllegalArgumentException("Value missing"));

2. 硬编码值而不是使用常量

这是我在代码中看到的最大罪过之一:

if (status == 3) {
    // do something
}

一个新的初级开发者加入团队,他不知道 3 是什么意思。然后他错误地修改了逻辑。砰。生产环境出现了bug。

正确的方式:

// 使用 static final
public static final int STATUS_COMPLETED = 3;

if (status == STATUS_COMPLETED) {
    // do something
}

// 更好的方式 - 使用枚举:
if (status == Status.COMPLETED) {
    // do something
}

为什么? 因为这样你的代码变得自文档化且安全。

3. 有缺陷的单例模式的双重检查锁定

经典错误:

public class MyClass {
    private static MyClass instance;

    private MyClass() {
        // 私有构造函数来强制单例模式
    }

    public static MyClass getInstance() {
        if (instance == null) {
            synchronized(MyClass.class) {
                if (instance == null) {
                    instance = new MyClass();
                }
            }
        }
        return instance;
    }
}

除非使用volatile完美地完成,否则这在Java中是有缺陷的。在并发环境中可能出现微妙的错误。

这里是修复方法:

public class MyClass {
    private static volatile MyClass instance; // <<< 修复:添加 'volatile'

    private MyClass() {
        // 私有构造函数来强制单例模式
    }

    public static MyClass getInstance() {
        if (instance == null) {
            synchronized(MyClass.class) {
                if (instance == null) {
                    instance = new MyClass();
                }
            }
        }
        return instance;
    }
}

更好的方式:

// 使用枚举单例:
public enum Singleton {
    INSTANCE;
    
    public void doSomething() {
        // 实现
    }
}
// 或者使用 static final:
public class MySingleton {
    public static final MySingleton INSTANCE = new MySingleton();
}

这些方式安全、简单,并且避免了并发错误。

4. Vector 和 Hashtable

现在是2025年。然而,有时我仍然看到这样的代码:

Vector<String> vector = new Vector<>();
Hashtable<String, String> table = new Hashtable<>();

VectorHashtable都是Java早期的遗留类。它们是同步的,因此比现代替代方案慢得多

应该使用什么替代:

// 对于List:
List<String> list = new ArrayList<>();
// 对于Map:
Map<String, String> map = new HashMap<>();

如果你需要线程安全,使用:

List<String> list = Collections.synchronizedList(new ArrayList<>());
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// 或者更好的:
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

为什么要停止使用VectorHashtable

  • 在现代应用中性能差
  • 存在更好的替代方案,对同步有更精细的控制

5. 原始类型

我仍然审查到这样的PR:

List list = new ArrayList();
list.add("Hello");
list.add(123); // 什么?在字符串列表中放整数?

这是危险的💀。

原始类型移除了类型安全性,导致在运行时爆发的微妙错误。

正确的方式:

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误 - 很好!

提示: 总是使用泛型。你的IDE会帮你早期发现错误。

6. StringBuffer(当你不需要同步时)

许多开发者使用这个:

StringBuffer sb = new StringBuffer();
sb.append("Hello ");
sb.append("World");  

但除非你在做多线程工作,否则StringBuffer是不必要的慢。

应该使用什么替代:

StringBuilder sb = new StringBuilder();
sb.append("Hello ");
sb.append("World");

StringBuilder更快,对于单线程操作来说已经足够了,这是你大部分代码的情况。

7. 直接使用 SimpleDateFormat

这是连高级开发者都会犯的经典错误:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(new Date());

问题在哪里?🤔 SimpleDateFormat不线程安全的。如果这段代码在Web应用或多线程环境中运行,它可能抛出奇怪的日期格式化错误

应该使用什么替代:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String date = LocalDate.now().format(formatter);

新的java.time包(从Java 8开始)是线程安全的,并且远远优越。

8. 使用 System.out.println 进行日志记录

我实际上见过企业应用程序中有数百个**System.out.println()**语句。
当你调试生产问题时,你会后悔的。

正确的方式:

使用日志框架,如SLF4J with LogbackLog4j2

private static final Logger logger = LoggerFactory.getLogger(MyClass.class);

logger.info("Processing order: {}", orderId);
logger.error("Error processing payment", ex);
  • 你可以获得日志级别(info、debug、error)
  • 你可以将日志重定向到文件
  • 你可以控制日志格式和保留期

🙅‍♂️永远不要在生产代码中部署System.out.println

9. 过度使用同步而不是使用现代并发

许多开发者仍然这样做:

synchronized(this) {
    // do something
}

但Java已经发展了🪴。java.util.concurrent提供了强大、更安全、性能更好的工具:

  • 使用ReentrantLock进行显式锁定
  • 使用ConcurrentHashMap而不是同步映射
  • 使用AtomicIntegerAtomicBoolean
  • 使用ExecutorService而不是手动管理线程

**除非绝对必要,否则避免使用synchronized。**并发很难——让现代库来帮助你。

10. 过时的集合API方法

我有时仍然看到这样的代码:

Enumeration<String> e = myVector.elements();
while (e.hasMoreElements()) {
    System.out.println(e.nextElement());
}

❌ 当你可以使用现代for-each循环时,停止使用EnumerationIterator

for (String item : myList) {
    System.out.println(item);
}
// 或者更好的 - 使用流:
myList.forEach(System.out::println);

这些更清洁、现代、更可读。