apeescape2.com
  • メイン
  • 収益性と効率性
  • 分散チーム
  • ブランドデザイン
  • デザイナーライフ
Webフロントエンド

バギーJavaコード:Java開発者が犯す最も一般的な間違いトップ10

Javaはプログラミング言語です 当初はインタラクティブテレビ用に開発されました 、しかし時間の経過とともに、ソフトウェアを使用できるあらゆる場所に広まりました。オブジェクト指向プログラミングの概念を使用して設計され、CまたはC ++、ガベージコレクション、アーキテクチャに依存しない仮想マシンなどの他の言語の複雑さを排除して、Javaは新しいプログラミング方法を作成しました。さらに、それは穏やかな学習曲線を持っており、それ自体のモットー「一度書いて、どこでも実行する」にうまく準拠しているように見えます。これはほとんど常に真実です。しかし、Javaの問題はまだ存在しています。最も一般的な間違いだと思う10のJavaの問題に対処します。

よくある間違い#1:既存のライブラリを無視する

それは間違いなく間違いです Java開発者 Javaで書かれた無数のライブラリを無視します。車輪の再発明を行う前に、利用可能なライブラリを検索してみてください。それらの多くは、長年にわたって洗練されており、自由に使用できます。これらは、logbackやLog4jなどのロギングライブラリ、またはNettyやAkkaなどのネットワーク関連ライブラリである可能性があります。 Joda-Timeなどの一部のライブラリは、事実上の業界標準になっています。

以下は私の以前のプロジェクトの1つからの個人的な経験です。 HTMLエスケープを担当するコードの部分は、最初から作成されました。それは何年もの間うまく機能していましたが、最終的にはユーザー入力に遭遇し、無限ループに陥りました。ユーザーは、サービスが応答しないことを発見し、同じ入力で再試行しようとしました。最終的に、このアプリケーションに割り当てられたサーバー上のすべてのCPUは、この無限ループによって占有されていました。この素朴なHTMLエスケープツールの作成者が、HTMLエスケープに使用できるよく知られたライブラリの1つを使用することを決定した場合。 HtmlEscapers から Google Guava 、これはおそらく起こらなかったでしょう。少なくとも、背後にコミュニティがある最も人気のあるライブラリに当てはまりますが、エラーはこのライブラリのコミュニティによって以前に発見され、修正されていたはずです。



よくある間違い#2:Switch-Caseブロックに「break」キーワードがない

これらのJavaの問題は非常に恥ずかしいものであり、本番環境で実行されるまで発見されない場合があります。多くの場合、switchステートメントのフォールスルー動作が役立ちます。ただし、そのような動作が望ましくないときに「break」キーワードを見逃すと、悲惨な結果につながる可能性があります。以下のコード例で「case0」に「break」を入れるのを忘れた場合、プログラムは「Zero」の後に「One」を書き込みます。これは、ここでの制御フローが「switch」ステートメント全体を通過するためです。それは「休憩」に達します。例えば:

public static void switchCasePrimer() { int caseIndex = 0; switch (caseIndex) { case 0: System.out.println('Zero'); case 1: System.out.println('One'); break; case 2: System.out.println('Two'); break; default: System.out.println('Default'); } }

ほとんどの場合、よりクリーンな解決策は、ポリモーフィズムを使用し、特定の動作を持つコードを別々のクラスに移動することです。このようなJavaの間違いは、静的コードアナライザーを使用して検出できます。 FindBugs そして PMD 。

よくある間違い#3:無料のリソースを忘れる

プログラムがファイルまたはネットワーク接続を開くたびに、Javaの初心者は、リソースの使用が終了したら、リソースを解放することが重要です。このようなリソースの操作中に例外がスローされた場合も、同様の注意が必要です。 FileInputStreamには、ガベージコレクションイベントでclose()メソッドを呼び出すファイナライザーがあると主張する人もいるかもしれません。ただし、ガベージコレクションサイクルがいつ開始されるかわからないため、入力ストリームはコンピュータリソースを無期限に消費する可能性があります。実際、Java 7には、特にこの場合に導入された、非常に便利できちんとしたステートメントがあります。 try-with-resources :

private static void printFileJava7() throws IOException { try(FileInputStream input = new FileInputStream('file.txt')) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } }

このステートメントは、AutoClosableインターフェイスを実装する任意のオブジェクトで使用できます。これにより、ステートメントの終わりまでに各リソースが確実に閉じられます。

関連: 8つの重要なJavaインタビューの質問

よくある間違い#4:メモリリーク

Javaは自動メモリ管理を使用します。手動でメモリを割り当てたり解放したりすることを忘れても安心ですが、初心者のJava開発者がアプリケーションでのメモリの使用方法を認識してはならないという意味ではありません。メモリ割り当てに関する問題は依然として発生する可能性があります。プログラムが不要になったオブジェクトへの参照を作成する限り、それは解放されません。ある意味では、このメモリリークと呼ぶことができます。 Javaでのメモリリークはさまざまな方法で発生する可能性がありますが、最も一般的な理由は、オブジェクトへの参照が残っている間はガベージコレクタがヒープからオブジェクトを削除できないため、オブジェクト参照が永続することです。オブジェクトのコレクションを含む静的フィールドでクラスを定義し、コレクションが不要になった後でその静的フィールドをnullに設定するのを忘れることで、このような参照を作成できます。静的フィールドはGCルートと見なされ、収集されることはありません。

このようなメモリリークの背後にあるもう1つの潜在的な理由は、相互に参照するオブジェクトのグループであり、循環依存が発生するため、ガベージコレクタは、相互依存参照を持つこれらのオブジェクトが必要かどうかを判断できません。もう1つの問題は、JNIを使​​用した場合の非ヒープメモリのリークです。

プリミティブリークの例は次のようになります。

final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); final Deque numbers = new LinkedBlockingDeque(); final BigDecimal divisor = new BigDecimal(51); scheduledExecutorService.scheduleAtFixedRate(() -> { BigDecimal number = numbers.peekLast(); if (number != null && number.remainder(divisor).byteValue() == 0) { System.out.println('Number: ' + number); System.out.println('Deque size: ' + numbers.size()); } }, 10, 10, TimeUnit.MILLISECONDS); scheduledExecutorService.scheduleAtFixedRate(() -> { numbers.add(new BigDecimal(System.currentTimeMillis())); }, 10, 10, TimeUnit.MILLISECONDS); try { scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }

この例では、2つのスケジュールされたタスクを作成します。最初のタスクは、「numbers」と呼ばれる両端キューから最後の数値を取得し、数値が51で割り切れる場合に備えて、数値と両端キューのサイズを出力します。2番目のタスクは、数値を両端キューに入れます。両方のタスクは固定レートでスケジュールされ、10ミリ秒ごとに実行されます。コードが実行されると、両端キューのサイズが永続的に増加していることがわかります。これにより、最終的には、使用可能なすべてのヒープメモリを消費するオブジェクトで両端キューがいっぱいになります。このプログラムのセマンティクスを維持しながらこれを防ぐために、両端キューから数値を取得するための別の方法「pollLast」を使用できます。メソッド「peekLast」とは異なり、「pollLast」は要素を返し、両端キューから削除しますが、「peekLast」は最後の要素のみを返します。

Javaでのメモリリークの詳細については、を参照してください。 この問題をわかりやすく説明した記事 。

よくある間違い#5:過剰なゴミの割り当て

プログラムが短期間のオブジェクトを多数作成すると、過剰なガベージ割り当てが発生する可能性があります。ガベージコレクタは継続的に機能し、メモリから不要なオブジェクトを削除します。これは、アプリケーションのパフォーマンスに悪影響を及ぼします。 1つの簡単な例:

String oneMillionHello = ''; for (int i = 0; i <1000000; i++) { oneMillionHello = oneMillionHello + 'Hello!'; } System.out.println(oneMillionHello.substring(0, 6));

Java開発では 、文字列は不変です。したがって、反復ごとに新しい文字列が作成されます。これに対処するには、可変のStringBuilderを使用する必要があります。

StringBuilder oneMillionHelloSB = new StringBuilder(); for (int i = 0; i <1000000; i++) { oneMillionHelloSB.append('Hello!'); } System.out.println(oneMillionHelloSB.toString().substring(0, 6));

最初のバージョンの実行にはかなりの時間がかかりますが、StringBuilderを使用するバージョンでは、大幅に短い時間で結果が生成されます。

よくある間違い#6:必要のないヌル参照の使用

nullの過度の使用を避けることは良い習慣です。たとえば、NullPointerExceptionを防ぐのに役立つため、nullではなくメソッドから空の配列またはコレクションを返すことをお勧めします。

以下に示すように、別のメソッドから取得したコレクションをトラバースする次のメソッドについて考えてみます。

List accountIds = person.getAccountIds(); for (String accountId : accountIds) { processAccount(accountId); }

アカウントがないときにgetAccountIds()がnullを返すと、NullPointerExceptionが発生します。これを修正するには、ヌルチェックが必要になります。ただし、nullの代わりに空のリストが返される場合は、NullPointerExceptionは問題ではなくなります。さらに、変数accountIdsをnullチェックする必要がないため、コードがよりクリーンになります。

nullを回避したい他のケースに対処するために、さまざまな戦略を使用できます。これらの戦略の1つは、空のオブジェクトまたは何らかの値のラップのいずれかであるオプションの型を使用することです。

Optional optionalString = Optional.ofNullable(nullableString); if(optionalString.isPresent()) { System.out.println(optionalString.get()); }

実際、Java8はより簡潔なソリューションを提供します。

Optional optionalString = Optional.ofNullable(nullableString); optionalString.ifPresent(System.out::println);

オプション型はバージョン8以降Javaの一部ですが、関数型プログラミングの世界では長い間よく知られています。これ以前は、以前のバージョンのJava用にGoogleGuavaで利用可能でした。

よくある間違い#7:例外を無視する

例外を未処理のままにしておくことはしばしば魅力的です。ただし、初心者と経験豊富なJava開発者のベストプラクティスは、それらを処理することです。例外は意図的にスローされるため、ほとんどの場合、これらの例外の原因となる問題に対処する必要があります。これらのイベントを見逃さないでください。必要に応じて、それを再スローするか、ユーザーにエラーダイアログを表示するか、ログにメッセージを追加することができます。少なくとも、他の開発者に理由を知らせるために、例外が未処理のままになっている理由を説明する必要があります。

selfie = person.shootASelfie(); try { selfie.show(); } catch (NullPointerException e) { // Maybe, invisible man. Who cares, anyway? }

例外の重要性を強調するより明確な方法は、次のように、このメッセージを例外の変数名にエンコードすることです。

try { selfie.delete(); } catch (NullPointerException unimportant) { }

よくある間違い#8:同時変更の例外

この例外は、イテレータオブジェクトによって提供されるメソッド以外のメソッドを使用してコレクションを反復処理しているときにコレクションが変更された場合に発生します。たとえば、帽子のリストがあり、イヤーフラップのある帽子をすべて削除したいとします。

List hats = new ArrayList(); hats.add(new Ushanka()); // that one has ear flaps hats.add(new Fedora()); hats.add(new Sombrero()); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hats.remove(hat); } }

このコードを実行すると、コードがコレクションを反復処理中に変更するため、「ConcurrentModificationException」が発生します。同じリストで動作している複数のスレッドの1つがコレクションを変更しようとしているときに、他のスレッドがコレクションを反復処理している場合、同じ例外が発生する可能性があります。複数のスレッドでのコレクションの同時変更は自然なことですが、同期ロック、同時変更に採用された特別なコレクションなど、同時プログラミングツールボックスの通常のツールで処理する必要があります。このJavaの問題の解決方法には微妙な違いがあります。シングルスレッドの場合とマルチスレッドの場合。以下は、これをシングルスレッドシナリオで処理できるいくつかの方法の簡単な説明です。

オブジェクトを収集し、別のループで削除します

後で別のループ内から削除するためにリストにイヤーフラップ付きの帽子を収集することは明らかな解決策ですが、削除する帽子を保存するための追加の収集が必要です。

List hatsToRemove = new LinkedList(); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hatsToRemove.add(hat); } } for (IHat hat : hatsToRemove) { hats.remove(hat); }

Iterator.removeメソッドを使用します

このアプローチはより簡潔であり、追加のコレクションを作成する必要はありません。

Iterator hatIterator = hats.iterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); } }

ListIteratorのメソッドを使用する

変更されたコレクションがリストインターフェイスを実装する場合は、リストイテレータを使用するのが適切です。 ListIteratorインターフェースを実装するイテレーターは、削除操作だけでなく、追加および設定操作もサポートします。 ListIteratorはIteratorインターフェースを実装しているため、例はIteratorremoveメソッドとほぼ同じように見えます。唯一の違いは、ハットイテレータのタイプと、「listIterator()」メソッドを使用してそのイテレータを取得する方法です。以下のスニペットは、「ListIterator.remove」メソッドと「ListIterator.add」メソッドを使用して、各帽子をソンブレロ付きのイヤーフラップに置き換える方法を示しています。

法人LLCとは
IHat sombrero = new Sombrero(); ListIterator hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); hatIterator.add(sombrero); } }

ListIteratorを使用すると、removeメソッドとaddメソッドの呼び出しを1回のset呼び出しに置き換えることができます。

IHat sombrero = new Sombrero(); ListIterator hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.set(sombrero); // set instead of remove and add } }

Java8で導入されたストリームメソッドをJava8で使用する プログラマー コレクションをストリームに変換し、いくつかの基準に従ってそのストリームをフィルタリングする機能があります。これは、ストリームAPIがハットをフィルタリングして「ConcurrentModificationException」を回避するのにどのように役立つかを示す例です。

hats = hats.stream().filter((hat -> !hat.hasEarFlaps())) .collect(Collectors.toCollection(ArrayList::new));

「Collectors.toCollection」メソッドは、フィルターされたハットを使用して新しいArrayListを作成します。これは、フィルタリング条件が多数のアイテムによって満たされ、結果としてArrayListが大きくなる場合に問題になる可能性があります。したがって、注意して使用する必要があります。 Java8で提示されたList.removeIfメソッドを使用するJava8で利用可能な別のソリューションであり、明らかに最も簡潔なのは、「removeIf」メソッドの使用です。

hats.removeIf(IHat::hasEarFlaps);

それでおしまい。内部では、「Iterator.remove」を使用して動作を実行します。

専門のコレクションを使用する

最初に「ArrayList」の代わりに「CopyOnWriteArrayList」を使用することにした場合、「CopyOnWriteArrayList」は変更されない変更メソッド(set、add、removeなど)を提供するため、まったく問題はありませんでした。コレクションのバッキング配列ではなく、コレクションの新しい変更バージョンを作成します。これにより、「ConcurrentModificationException」のリスクなしに、コレクションの元のバージョンとその変更を同時に繰り返すことができます。そのコレクションの欠点は明らかです-変更のたびに新しいコレクションが生成されます。

さまざまなケースに合わせて調整された他のコレクションがあります。 「CopyOnWriteSet」および「ConcurrentHashMap」。

コレクションの同時変更で発生する可能性のあるもう1つの間違いは、コレクションからストリームを作成し、ストリームの反復中にバッキングコレクションを変更することです。ストリームの一般的なルールは、ストリームのクエリ中に基になるコレクションが変更されないようにすることです。次の例は、ストリームを処理する誤った方法を示しています。

List filteredHats = hats.stream().peek(hat -> { if (hat.hasEarFlaps()) { hats.remove(hat); } }).collect(Collectors.toCollection(ArrayList::new));

メソッドpeekはすべての要素を収集し、それぞれに対して提供されたアクションを実行します。ここで、アクションは基になるリストから要素を削除しようとしていますが、これは誤りです。これを回避するには、上記の方法のいくつかを試してください。

よくある間違い#9:契約違反

標準ライブラリまたはサードパーティベンダーによって提供されるコードは、物事を機能させるために従う必要のあるルールに依存している場合があります。たとえば、hashCodeとequalsコントラクトを使用すると、Javaコレクションフレームワークからのコレクションのセット、およびhashCodeとequalsメソッドを使用する他のクラスの動作が保証されます。コントラクトに従わないことは、常に例外を引き起こしたり、コードのコンパイルを中断したりするようなエラーではありません。危険の兆候なしにアプリケーションの動作を変更することがあるため、より注意が必要です。誤ったコードは製品リリースに組み込まれ、望ましくない影響を大量に引き起こす可能性があります。これには、UIの動作不良、データレポートの誤り、アプリケーションパフォーマンスの低下、データ損失などが含まれます。幸いなことに、これらの悲惨なバグはあまり発生しません。すでにhashCodeとequalsコントラクトについて説明しました。これは、HashMapやHashSetなどのオブジェクトのハッシュと比較に依存するコレクションで使用されます。簡単に言えば、契約には2つのルールが含まれています。

  • 2つのオブジェクトが等しい場合、それらのハッシュコードは等しくなければなりません。
  • 2つのオブジェクトが同じハッシュコードを持っている場合、それらは等しい場合と等しくない場合があります。

コントラクトの最初のルールに違反すると、ハッシュマップからオブジェクトを取得しようとするときに問題が発生します。 2番目のルールは、同じハッシュコードを持つオブジェクトが必ずしも等しいとは限らないことを示しています。最初のルールを破った場合の影響を調べてみましょう。

public static class Boat { private String name; Boat(String name) { this.name = name; } @Override public boolean equals(Object o) getClass() != o.getClass()) return false; Boat boat = (Boat) o; return !(name != null ? !name.equals(boat.name) : boat.name != null); @Override public int hashCode() { return (int) (Math.random() * 5000); } }

ご覧のとおり、BoatクラスはequalsメソッドとhashCodeメソッドをオーバーライドしています。ただし、hashCodeは呼び出されるたびに同じオブジェクトのランダムな値を返すため、コントラクトが破られています。次のコードは、以前にその種のボートを追加したにもかかわらず、ハッシュセットに「Enterprise」という名前のボートが見つからない可能性があります。

public static void main(String[] args) { Set boats = new HashSet(); boats.add(new Boat('Enterprise')); System.out.printf('We have a boat named 'Enterprise' : %b ', boats.contains(new Boat('Enterprise'))); }

契約の別の例には、ファイナライズメソッドが含まれます。これは、その機能を説明する公式のJavaドキュメントからの引用です。

ファイナライズの一般的な契約は、JavaTM仮想マシンが、(まだ死んでいない)スレッドがこのオブジェクトにアクセスできる手段がなくなったと判断した場合に呼び出されることです。ファイナライズの準備ができている他のオブジェクトまたはクラスのファイナライズによって実行されるアクション。 finalizeメソッドは、このオブジェクトを他のスレッドで再び使用できるようにするなど、任意のアクションを実行できます。ただし、ファイナライズの通常の目的は、オブジェクトが取り返しのつかないほど破棄される前にクリーンアップアクションを実行することです。たとえば、入出力接続を表すオブジェクトのfinalizeメソッドは、オブジェクトが完全に破棄される前に、明示的なI / Oトランザクションを実行して接続を切断する場合があります。

ファイルハンドラーなどのリソースを解放するためにfinalizeメソッドを使用することを決定することもできますが、それは悪い考えです。これは、ファイナライズがガベージコレクション中に呼び出されるため、ファイナライズがいつ呼び出されるかについての時間保証がなく、GCの時間が決定できないためです。

よくある間違い#10:パラメータ化されたタイプの代わりにRawタイプを使用する

Java仕様によると、raw型は、パラメーター化されていない型、またはRのスーパークラスまたはスーパーインターフェイスから継承されていないクラスRの非静的メンバーです。Javaで汎用型が導入されるまで、raw型に代わるものはありませんでした。 。バージョン1.5以降、ジェネリックプログラミングをサポートしており、ジェネリックは間違いなく大幅に改善されています。ただし、下位互換性の理由により、型システムを壊す可能性のある落とし穴が残っています。次の例を見てみましょう。

List listOfNumbers = new ArrayList(); listOfNumbers.add(10); listOfNumbers.add('Twenty'); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

ここに、生のArrayListとして定義された数値のリストがあります。その型はtypeパラメータで指定されていないため、任意のオブジェクトを追加できます。しかし、最後の行では、要素をintにキャストし、それを2倍にして、2倍にした数値を標準出力に出力します。このコードはエラーなしでコンパイルされますが、実行すると、文字列を整数にキャストしようとしたため、実行時例外が発生します。明らかに、型システムは、必要な情報を隠してしまうと、安全なコードを書くのに役立ちません。この問題を修正するには、コレクションに保存するオブジェクトのタイプを指定する必要があります。

List listOfNumbers = new ArrayList(); listOfNumbers.add(10); listOfNumbers.add('Twenty'); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

オリジナルとの唯一の違いは、コレクションを定義する行です。

List listOfNumbers = new ArrayList();

整数のみを格納することが期待されるコレクションに文字列を追加しようとしているため、修正されたコードはコンパイルされませんでした。コンパイラはエラーを表示し、文字列「Twenty」をリストに追加しようとしている行を指し示します。ジェネリック型をパラメーター化することは常に良い考えです。このようにして、コンパイラーは可能なすべての型チェックを行うことができ、型システムの不整合によって引き起こされるランタイム例外の可能性が最小限に抑えられます。

結論

プラットフォームとしてのJavaは、洗練されたJVMと言語自体の両方に依存して、ソフトウェア開発の多くのことを簡素化します。ただし、手動のメモリ管理や適切なOOPツールの削除などの機能は、すべての問題を排除するわけではなく、通常のJava開発者が直面する問題です。いつものように、このような知識、実践、およびJavaチュートリアルは、アプリケーションエラーを回避して対処するための最良の手段です。したがって、ライブラリを理解し、Javaを読み、JVMドキュメントを読み、プログラムを作成します。静的コードアナライザーも、実際のバグを指摘して潜在的なバグを強調する可能性があるため、忘れないでください。

関連: 高度なJavaクラスチュートリアル:クラスのリロードのガイド

B2B UX –一般的な障害と達成可能なソリューション

Uxデザイン

B2B UX –一般的な障害と達成可能なソリューション
階層構造でスリムなPHPMVCフレームワークを維持する

階層構造でスリムなPHPMVCフレームワークを維持する

技術

人気の投稿
ポストフラッシュ時代のWebアニメーション
ポストフラッシュ時代のWebアニメーション
Swiftプロパティのラッパーにアプローチする方法
Swiftプロパティのラッパーにアプローチする方法
42億ドルは合理的ですか?インスタカートの評価を評価する方法
42億ドルは合理的ですか?インスタカートの評価を評価する方法
スマートソフトウェアの価格戦略の究極のガイド
スマートソフトウェアの価格戦略の究極のガイド
ビジネスインテリジェンスの歴史を探る
ビジネスインテリジェンスの歴史を探る
 
ApeeScapeがフリーランサー向けの無料タイムトラッキングアプリTopTrackerをリリース
ApeeScapeがフリーランサー向けの無料タイムトラッキングアプリTopTrackerをリリース
シニアフルスタックエンジニア、タレントポストハイヤーチーム
シニアフルスタックエンジニア、タレントポストハイヤーチーム
いいねの予測:単純なレコメンデーションエンジンのアルゴリズムの内部
いいねの予測:単純なレコメンデーションエンジンのアルゴリズムの内部
ツリーカーネル:ツリー構造化データ間の類似性の定量化
ツリーカーネル:ツリー構造化データ間の類似性の定量化
Angular 4フォームの操作:ネストと入力の検証
Angular 4フォームの操作:ネストと入力の検証
人気の投稿
  • Javaメモリリーク検出ツール
  • C ++ロボットプログラミング
  • 音楽業界の大きさ
  • PHPエラーログはどこにありますか
  • 1099 vsw2時給計算機
カテゴリー
設計プロセス 投資家と資金調達 革新 財務プロセス ブランドデザイン トレンド 技術 アジャイル ヒントとツール モバイル

© 2021 | 全著作権所有

apeescape2.com