Java 開発者の 90% が犯す 5 つの間違い

この記事が役に立った場合は、子供を救ってください。たくさん投票できます投票チャンネル 、どうもありがとうございました~~

序文

Java 開発プログラマーとして、信じられないほどのバグに遭遇したかどうかはわかりません。これらのエラーは通常、解決するのに数時間かかります。それらを見つけたら、黙って自分をばかだと呼ぶかもしれません。はい、これらのばかげたバグは基本的に、基本的な知識を無視することによって引き起こされます。実際、それらはすべて非常に低レベルの間違いです。今日は、よくあるコーディングの間違いをいくつか要約し、解決策を示します。日常のコーディングでこのような問題を回避できることを願っています。

1. Objects.equals を使用してオブジェクトを比較する

この方法は誰にでも馴染み深いと思われ、多くの人がよく利用しています。これは、JDK7 が提供するメソッドであり、オブジェクトの比較を迅速に実現し、煩わしい null ポインター チェックを効果的に回避できます。しかし、この方法は間違った使い方をしやすいです。たとえば、次のようになります。

Long longValue = 123L;
System.out.println(longValue==123); //true
System.out.println(Objects.equals(longValue,123)); //false
复制代码

==で置換するとObjects.equals()異なる結果になるのはなぜですか? これは、==コンパイラを使用すると、パッケージ タイプlongValueに対応する基本データ型が取得され、この基本データ型と比較されるためです。これは、コンパイラが定数をパッケージ タイプではなく比較基本データ型に自動的に変換することを意味します。

このObjects.equals()メソッドを使用した後、コンパイラの既定の定数の基本データ型は ですint以下は、コンパイラが既に定数を型であると見なしているため、判定対象のオブジェクト型をObjects.equals()使用a.equals(b)するソース コードです。したがって、比較結果は である必要がありますLong.equals()intfalse

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
    
public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}
复制代码

理由がわかれば、解決策は簡単です。定数のデータ型を直接宣言しますObjects.equals(longValue,123L)実際、ロジックが厳密であれば、上記の問題は発生しません。私たちがしなければならないことは、良いコーディングの習慣を維持することです。

2. 間違った日付形式

日々の開発では、日付をフォーマットする必要があることがよくありますが、多くの人が間違ったフォーマットを使用しているため、予期しない状況が発生します。以下の例を参照してください。

Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));//2022-12-31 08:00:00
复制代码

以上用于YYYY-MM-dd格式化, 年从2021 变成了 2022。为什么?这是因为 javaDateTimeFormatter 模式YYYYyyyy之间存在细微的差异。它们都代表一年,但是yyyy代表日历年,而YYYY代表星期。这是一个细微的差异,仅会导致一年左右的变更问题,因此您的代码本可以一直正常运行,而仅在新的一年中引发问题。12月31日按周计算的年份是2022年,正确的方式应该是使用yyyy-MM-dd格式化日期。

这个bug特别隐蔽。这在平时不会有问题。它只会在新的一年到来时触发。我公司就因为这个bug造成了生产事故。

3. 在 ThreadPool 中使用 ThreadLocal

如果创建一个ThreadLocal 变量,访问该变量的线程将创建一个线程局部变量。合理使用ThreadLocal可以避免线程安全问题。

但是,如果在线程池中使用ThreadLocal ,就要小心了。您的代码可能会产生意想不到的结果。举个很简单的例子,假设我们有一个电商平台,用户购买商品后需要发邮件确认。

private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);

private ExecutorService executorService = Executors.newFixedThreadPool(4);

public void executor() {
    executorService.submit(()->{
        User user = currentUser.get();
        Integer userId = user.getId();
        sendEmail(userId);
    });
}
复制代码

如果我们使用ThreadLocal来保存用户信息,这里就会有一个隐藏的bug。因为使用了线程池,线程是可以复用的,所以在使用ThreadLocal获取用户信息的时候,很可能会误获取到别人的信息。您可以使用会话来解决这个问题。

4. 使用HashSet去除重复数据

在编码的时候,我们经常会有去重的需求。一想到去重,很多人首先想到的就是用HashSet去重。但是,不小心使用 HashSet 可能会导致去重失败。

User user1 = new User();
user1.setUsername("test");

User user2 = new User();
user2.setUsername("test");

List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());// the size is 2
复制代码

注意深い読者は、失敗の理由をすでに推測しているはずです。ハッシュ テーブルのアドレス指定に使用し、HashSetメソッドを使用してオブジェクトが等しいかどうかを判断します。カスタム オブジェクトにオーバーライドされたメソッドと equals メソッドがない場合、親オブジェクトのメソッドとメソッドがデフォルトで使用されますしたがって、これらは 2 つの異なるオブジェクトであると見なされるため、重複排除は失敗します。hashcodeequalshashcodehashcodeequalsHashSet

5. スレッド プールの例外が食べられる

ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(()->{
    //do something
    double result = 10/0;
});
复制代码

上記のコードは、スレッド プールが例外をスローするシナリオをシミュレートします。私たちの実際のビジネスコードは、考えられるさまざまな状況に対処する必要があるため、特定の理由でトリガーされる可能性が非常に高くなりますRuntimeException

ただし、特別な処理がなければ、この例外はスレッド プールによって食べられます。これは、非常に深刻な結果である、あなたが知らない問題につながります。したがって、スレッド プールでtry catch例外をキャッチすることをお勧めします。

要約する

この記事では、開発プロセスで犯しやすい 5 つの間違いをまとめました. コーディングの習慣を身につけていただければ幸いです.

この記事が役に立った場合は、子供を救ってください。たくさん投票できます投票チャンネル 、どうもありがとうございました~~

おすすめ

転載: juejin.im/post/7182184496517611576