Josh Blochの効果的なJavaルールよりも微妙な10のJavaコーディングプラクティスのリストを以下に示します。学習が容易で毎日の状況に焦点を当てているジョシュブロッホのリストと比較して、このリストには、API/SPIデザインの珍しいものが含まれる状況が含まれています。
JOOQの書き込みと維持中にこれらに遭遇しました(JavaでSQLモデルの内部DSL)。内部DSLとして、JOOQは、Josh Blochがこのような広範なAPIに推奨しない可能性のあるジェネリック、可変パラメーター、および過負荷を組み合わせて、Javaコンパイラとジェネリックに最大限の程度に挑戦します。
Javaコーディングのための10の微妙なベストプラクティスをあなたと共有させてください:
1. C ++の破壊者を覚えておいてください
C ++のデストラクタを覚えていますか?覚えていませんか?それから、オブジェクトの削除がリリースされなかった後に割り当てられたメモリのためにメモリリークをデバッグする必要がないため、あなたは本当に幸運です。実装されたごみ収集メカニズムについては、Sun/Oracleに感謝します!
それにもかかわらず、Destructorは興味深い機能を提供します。メモリを解放する逆割り当て順序を理解しています。これは、クラスデストラクタの構文を操作するときのJavaの場合であることを忘れないでください。
他にもさまざまなユースケースがあります。イベントリスナーのSPIを実装する方法の具体的な例を次に示します。
@overridepublic void beforevent(eventContext e){super.beforevent(e); //私のコードの前のスーパーコード} @OverridePublic void afterEvent(eventContext e){//コードの後のスーパーコードafterevent(e);}悪名高い哲学者の食事問題は、それが重要な理由のもう1つの良い例です。哲学者の食事に関する質問については、リンクを確認してください。
http://adit.io/posts/2013-05-11-thing-philosophers-problem-with-swanson.html
ルール:前後に使用するときはいつでも、Alocate/free、Semanticsを取得/返信してロジックを実装します。逆の順序で実行後/無料/返却操作を検討します。
顧客にSPIメソッドを提供し、ライブラリ/コードにカスタム動作を簡単に注入できます。あなたのSPI進化の判断があなたを混乱させるかもしれないことに注意してください。もちろん、機能を早すぎることはありません。ただし、SPIを公開したら、セマンティックバージョンに従うことにしたら、別のパラメーターが必要になる場合があることに気付いたときに、SPIに愚かな単一パラメーターを追加することを本当に後悔するでしょう。
インターフェイスEventListener {//悪いvoidメッセージ(Stringメッセージ);}メッセージIDとメッセージソースが必要な場合はどうなりますか? API進化により、上記のタイプにパラメーターを追加できなくなります。もちろん、Java 8を使用すると、初期に悪いデザインの決定を「防御」するディフェンダー方法を追加できます。
Interface EventListener {//悪いデフォルトvoid message(string message){message(message、null、null); } // より良い? void message(string message、integer id、messagesourceソース);}残念ながら、Defenderメソッドは最終的な修飾子を使用できないことに注意してください。
ただし、多くの方法を使用してSPIを汚染するよりも、コンテキストオブジェクト(またはパラメーターオブジェクト)を使用する方がはるかに良いでしょう。
インターフェイスMESSAGECONTEXT {string message(); integer id();メッセージソースソース();}インターフェイスEventListener {// Awesome! voidメッセージ(mesagecontextコンテキスト);} EventListner SPIよりもMessageContext APIを簡単に進化させることができます。これは、実装するユーザーがほとんどないためです。
ルール:SPIが指定されているときはいつでも、固定パラメーターを使用してメソッドを書き込む代わりに、コンテキスト/パラメーターオブジェクトを使用することを検討してください。
注:Builder APIを使用して構築できる専用のMessageresultタイプを介して結果を交換することも良い考えです。これにより、SPIの進化の柔軟性が大幅に向上します。
スイングプログラマーは通常、いくつかのショートカットキーを押して、数百または数千の匿名クラスを生成します。ほとんどの場合、インターフェイスに従い、SPIサブタイプのライフサイクルが違反していない限り、そうすることは害はありません。ただし、簡単な理由で匿名、ローカル、または内部クラスを頻繁に使用しないでください。外部クラスへの参照を保存します。どこに行っても、外部カテゴリは従わなければなりません。たとえば、ローカルクラスの外部操作が不適切な場合、オブジェクトグラフ全体が微妙な変更を受け、メモリの漏れを引き起こす可能性があります。
ルール:匿名、ローカル、または内部のクラスを書く前に、それを静的なクラスまたは通常のトップレベルのクラスに変換できるかどうかをよく考えてください。したがって、オブジェクトを外部レベルのドメインに返す方法を避けてください。
注:ダブルカーリーブレースを使用して、単純なオブジェクトを初期化します。
new hashmap <string、string>(){{put( "1"、 "a"); put( "2"、 "b");}}このメソッドは、JLS§8.6仕様で説明されているインスタンス初期化を使用します。表面的には良さそうですが、実際にはお勧めしません。完全に独立したハッシュマップオブジェクトを使用する場合、インスタンスは常に外部オブジェクトへの参照を保持するとは限らないためです。さらに、これにより、クラスローダーがより多くのクラスを管理できるようになります。
Java8のペースが近づいています。 Java 8を使用すると、Lambdaの表現は、好きかどうかにかかわらず、Lambdaの表現をもたらします。 APIユーザーはそれを好むかもしれませんが、可能な限り頻繁に使用できることを確認してください。したがって、APIがint、long、string、dateなどの単純な「スカラー」タイプを受信しない限り、APIにできるだけ頻繁にSAMを受信させます。
サムとは何ですか? SAMは単一の抽象的方法[タイプ]です。関数インターフェイスとも呼ばれ、まもなく@FunctionAlinterfaceとして注釈が付けられます。これはルール2と一致し、EventListenerは実際にはSAMです。最高のSAMには1つのパラメーターしかありません。これにより、ラムダ式の書き込みがさらに簡素化されるためです。書くことを想像してみてください
listeners.add(c-> system.out.println(c.message()));
交換します
listeners.add(new EventListener(){@Override public void message(messageContext c){system.out.println(c.message())); }});JooxでXMLを処理することを想像してください。 Jooxには多くのSAMが含まれています:
$(document)// id .find(c-> $(c).id()!= null)の要素を見つける//子供の要素を見つけます。
ルール: APIユーザーに親切にして、これからSAM/FUNCTIONインターフェイスの書き込みを開始します。
注: Java8 Lambdaの表現と改善されたコレクションAPIに関する興味深いブログがたくさんあります。
Java Nullsに関する1つまたは2つの記事を書いており、Java 8に新しいオプションクラスの導入について説明しました。学問的または実用的な観点から、これらのトピックは非常に興味深いものです。
この段階では、nullとnullpointerexceptionはJavaの欠陥ですが、問題はないAPIを設計することができます。 APIを設計するときは、ユーザーがチェーン内のメソッドを呼び出すことができるため、可能な限りNULLを返すことを避ける必要があります。
initialise(someargument).calculate(data).dispatch();
上記のコードからわかるように、メソッドはnullを返す必要はありません。実際、nullの使用は通常、同等の不均一性と見なされます。 JqueryやJooxのようなライブラリは、反復可能なオブジェクトでnullを完全に放棄しました。
nullは通常、遅延初期化に使用されます。多くの場合、パフォーマンスに深刻な影響を与えることなく、遅延初期化も避ける必要があります。実際、関係するデータ構造が大きすぎる場合、遅延初期化を注意して使用する必要があります。
ルール:メソッドはいつでもnullを返すことを避ける必要があります。 Nullは、「存在しない」または「存在しない」のセマンティクスを表すためにのみ使用されます。
場合によってはヌル値でメソッドを返しても問題ありませんが、空の配列や空のコレクションを返さないでください! java.io.file.list()メソッドを参照してください。次のように設計されています。
このメソッドは、指定されたディレクトリ内のすべてのファイルまたはディレクトリの文字列の配列を返します。ディレクトリが空の場合、返された配列は空です。指定されたパスが存在しない場合、またはI/Oエラーが発生した場合はnullを返します。
したがって、この方法は通常、次のように使用されます。
file directory = // ... if(directory.isdirectory()){string [] list = directory.list(); if(list!= null){for(string file:list){// ...}}}}nullチェックが必要だと思いますか?ほとんどのI/O操作はiOExceptionsを生成しますが、このメソッドはNULLのみを返します。 nullはI/Oエラーメッセージを保存できません。したがって、このようなデザインには次の3つの欠点があります。
コレクションの観点から問題を見ると、空の配列またはコレクションが「存在しない」の最良の実装です。空の配列またはコレクションを返すことは、遅延の初期化に使用されない限り、ほとんど実用的ではありません。
ルール:返された配列またはコレクションはnullであってはなりません。
HTTPの利点は無国籍です。関連するすべての状態は、各要求と応答で転送されます。これは、休息の命名の本質です:状態転送(表現状態転送)。 Javaでこれを行うことも素晴らしいことです。メソッドが状態パラメーターオブジェクトを受信した場合、ルール2の観点からこれについて考えてください。状態が外部から状態を操作するのではなく、そのようなオブジェクトを介して送信される場合、事態は簡単になります。例としてJDBCを取ります。次の例では、保存されたプログラムのカーソルを読み取ります。
callablestatement s = connection.preparecall( "{?= ...}"); //ステートメント状態の冗長操作:S.registeroutParameter(1、cursor); s.setString(2、 "abc"); s.execute(); resultet rs = s.getobject(1);これにより、JDBC APIはとても奇妙になります。各オブジェクトはステートフルで操作が困難です。具体的には、2つの主な問題があります。
ルール:機能スタイルのより多くの実装。メソッドパラメーターを介して状態を転送します。非常に少数の動作オブジェクト状態。
これは比較的簡単な操作方法です。比較的複雑なオブジェクトシステムでは、すべてのオブジェクトのequalsメソッドで最初に平等な判断を下す限り、大幅なパフォーマンスの改善を実現できます。
@OverridePublic Boolean Equals(Object Other){if(this == other)return true; //その他の平等判断ロジック...}他の短絡チェックにはヌル値チェックが含まれる場合があるため、追加する必要があることに注意してください。
@OverridePublic Boolean Equals(Object Other){if(this == other)return true; if(other == null)falseを返します。 //平等ロジックの残り...}ルール:パフォーマンスを向上させるために、すべてのEquals()メソッドで短絡を使用します。
この方法をデフォルトにすることは、Java開発者の習慣に反しているため、一部の人々はこれに反対するかもしれません。ただし、コードを完全に制御できる場合は、メソッドをデフォルトで最終的にすることは確かに正しいです。
これは、静的な方法に特に適しています。この場合、「オーバーレイ」(実際には閉塞)はほとんど機能しません。私は最近、Apache Tikaの非常に悪いシェーディング静的方法の例に出会いました。見てください:
TikainputStreamは、taggedInputStreamを拡張して、比較的異なる実装で静的get()メソッドをマスクします。
従来の方法とは異なり、コールがコンパイル時に静的メソッド呼び出しにバインドされるため、静的方法を互いに上書きすることはできません。あなたが不運であれば、あなたは誤って間違った方法を得るかもしれません。
ルール: APIを完全に制御できる場合は、できるだけ多くの方法をデフォルトで最終的に作成します。
特別な場合には、「Accept-all」変数パラメーターメソッドを使用してオブジェクトを受信できます...パラメーター:
void Acceptall(オブジェクト...すべて);
そのような方法を書くと、Javaエコシステムに少しJavaScriptの感覚がもたらされます。もちろん、文字列などの実際の状況に基づいて実際のタイプを制限することをお勧めします。あまり制限したくないので、オブジェクトを一般的なTに置き換えることは良い考えだと思うかもしれません:
void Acceptall(t ... all);
しかし、そうではありません。 Tは常にオブジェクトとして推測されます。実際、上記の方法ではジェネリックを使用できないと考えるかもしれません。さらに重要なことは、上記の方法に過負荷できると思うかもしれませんが、できません。
void acceptall(t ... all); void acceptall(string message、t ... all);
これは、オプションで文字列メッセージをメソッドに渡すことができるように見えます。しかし、この呼びかけはどうなりますか?
Acceptall( "Message"、123、 "ABC");
コンパイラはtを<?シリアル化可能で比較可能な<?>>を拡張します。これにより、コールが不明確になります!
したがって、(たとえそれが一般的であっても)「Accept-all」署名があるときはいつでも、それをタイプすることは決してありません。 API消費者は、コンパイラが「誤って」幸運なときに「正しい」メソッドを選択できるようにすることができます。ただし、Accept-Allメソッドを使用するか、いかなる方法も呼び出さないことも可能です。
ルール:可能であれば、「すべて」の署名を避けてください。そうでない場合は、そのような方法を過負荷にしないでください。
ジャワは獣です。他のより理想的な言語とは異なり、それは今日のものにゆっくりと進化しました。これは良いことかもしれません。なぜなら、Java開発の速度ではすでに何百もの警告があり、これらの警告は長年の経験を通してのみ把握できるからです。
このトピックのトップ10リストの詳細をお楽しみに!
オリジナルリンク:JOOQ翻訳:ImportNew.com-リケン
翻訳リンク:http://www.importnew.com/10138.html
[再版時に元のソース、翻訳者、翻訳リンクを保管してください。 ]