web-dev-qa-db-ja.com

マップで挿入順序が重要であることをどのように伝えるか?

データベースからタプルのセットをフェッチして、それをマップに入れています。データベースクエリはコストがかかります。

マップ内の要素の明確な自然な順序付けはありませんが、それでも挿入順序は重要です。マップの並べ替えは負荷の高い操作になるので、クエリの結果が既に希望どおりに並べ替えられていることを考えると、そのような操作は避けたいと思います。したがって、クエリ結果をLinkedHashMapに格納し、DAOメソッドからマップを返すだけです。

public LinkedHashMap<Key, Value> fetchData()

私はメソッドprocessDataを持っています。これはマップ上でいくつかの処理を行う必要があります-いくつかの値を変更し、いくつかの新しいキー/値を追加します。次のように定義されます

public void processData(LinkedHashMap<Key, Value> data) {...}

ただし、いくつかのリンター(ソナーなど)は、「データ」のタイプは「LinkedHashMap」ではなく「Map」などのインターフェースである必要があると不平を言っていますイカS1319 )。
基本的には、

public void processData(Map<Key, Value> data) {...}

しかし、メソッドシグネチャにマップの順序が重要であることを伝えたい-これはprocessDataのアルゴリズムに関係があるため、ランダムマップだけがメソッドに渡されないようにします。

SortedMapを使用したくありません。これは( Java.util.SortedMap のjavadocから)「自然順序付けそのキー、またはComparatorによって、通常、ソートされたマップの作成時に提供されます。 "

私のキーには自然順序がなく、何もしないComparatorを作成すると冗長になります。

そして、私はそれがマップであって、putを利用して重複キーなどを回避することを望んでいます。そうでない場合、dataList<Map.Entry<Key, Value>>であった可能性があります。

それで、私のメソッドがすでにソートされているマップを望んでいるとどうやって言うのですか?残念ながら、Java.util.LinkedMapインターフェースはありません。

24
Vidar S. Ramdal

たくさんの良い提案と思考の糧をありがとう。

新しいマップクラスの作成を拡張し、processDataをインスタンスメソッドにしました。

class DataMap extends LinkedHashMap<Key, Value> {

   processData();

}

次に、マップを返さないようにDAOメソッドをリファクタリングしましたが、代わりにtargetマップをパラメーターとして使用します。

public void fetchData(Map<Key, Value> target) {
  ...
  // for each result row
  target.put(key, value);
}

したがって、DataMapにデータを入力してデータを処理することは、他の場所から来るアルゴリズムの一部である他のいくつかの変数があるため、2ステップのプロセスになりました。

public DataMap fetchDataMap() {
  var dataMap = new DataMap();
  dao.fetchData(dataMap);
  return dataMap;
}

これにより、私のMap実装がエントリの挿入方法を制御できるようになり、順序付け要件が隠されます。これはDataMapの実装の詳細です。

1
Vidar S. Ramdal

したがって、LinkedHashMapを使用します。

はい、可能な限り特定の実装ではMapを使用してくださいyes、これはisのベストプラクティスです。

とはいえ、これはMapの実装が実際に重要である奇妙な状況です。これは、コード内のMapを使用する場合の99.9%のケースには当てはまりませんが、ここでは、この0.1%の状況です。 Sonarはこれを知ることができないため、Sonarは特定の実装を使用しないように指示します。これは、ほとんどの場合それが正しいためです。

特定の実装を使用することを主張できる場合は、豚に口紅を付けないでください。 LinkedHashMapではなくMapが必要です。

これは、プログラミングに不慣れでこの答えに出くわした場合、ベストプラクティスに反することができないとは思わないでください。しかし、ある実装を別の実装に置き換えることが受け入れられない場合、あなたができる唯一のことは、その特定の実装を使用し、Sonarに屈することです。

56
Neil

あなたは3つのことを戦っています:

まず、Javaのコンテナライブラリです。その分類法には、クラスが予測可能な順序で反復するかどうかを判断する方法はありません。 IteratesInInsertedOrderMapによって実装できるLinkedHashMapインターフェイスがないため、型チェック(および同じように動作する代替実装の使用)が不可能になります。それはおそらく設計によるものです。その精神は、抽象Mapのように動作するオブジェクトを実際に処理できるはずだからです。

第二に、あなたのリンターが言うことは福音として扱われるべきであり、それが言うことを無視することは悪いことだという信念です。最近の良い習慣とは対照的に、リンター警告は、コードを適切に呼び出すための障害となることは想定されていません。彼らはあなたが書いたコードについて推論し、あなたの経験と判断を使って警告が正当化されるかどうかを判断するよう促されます。正当化されていない警告は、ほとんどすべての静的分析ツールが、コードを調査したことを伝えるメカニズムを提供する理由であり、あなたがやっていることは大丈夫であり、将来彼らはそれについて文句を言うべきではないと思います。

第三に、これはおそらくその要です。LinkedHashMapは、この仕事には不適切なツールかもしれません。マップはランダムであり、順序付けされたアクセスではありません。 processData()が単にレコードを順番に繰り返し処理し、他のレコードをキーで検索する必要がない場合は、Mapの特定の実装にList。一方、両方が必要な場合は、LinkedHashMapが適切なツールです。これは、必要なことを実行することがわかっており、必要な場合よりも正当な理由があるためです。

21
Blrfl

LinkedHashMapから得ているのは重複を上書きする機能だけですが、実際にそれをListとして使用している場合は、その使用方法を自分自身と通信することをお勧めしますカスタムList実装。既存のJavaコレクションクラスに基づいて、addおよびremoveメソッドをオーバーライドするだけで、バッキングストアを更新し、キーを追跡して確認できます。一意性:これにProcessingListのような独特の名前を付けると、processDataメソッドに提供される引数を特定の方法で処理する必要があることが明確になります。

15
TMN

「LinkedHashMapを生成するシステムの一部があり、システムの別の部分では、他のプロセスによって生成されたものはリンクされないため、最初の部分によって生成されたLinkedHashMapオブジェクトのみを受け入れる必要がある」正しく動作します。」

つまり、ここでの問題は実際にはLinkedHashMapを使用しようとしているということです。これは、ほとんどの場合、探しているデータに適合しますが、実際には、作成したインスタンス以外のインスタンスで置き換えることはできません。実際にやりたいことは、最初の部分が作成し、2番目の部分が消費する独自のインターフェース/クラスを作成することです。 「実際の」LinkedHashMapをラップし、Mapゲッターを提供したり、Mapインターフェースを実装したりできます。

これはCandiedOrangeの回答とは少し異なります。実際のMapを拡張するのではなく、カプセル化する(そして必要に応じて呼び出しを委任する)ことをお勧めします。それは時々これらのスタイルの聖戦の1つですが、それは「いくつかの追加のマップ」ではなく、「マップで内部的に表現する可能性のある有用な状態情報のバッグ」であるように思えます。

このように渡す必要のある2つの変数がある場合、おそらくそれについてあまり考えずに、そのためのクラスを作成したでしょう。しかし、メンバー変数が1つだけであっても、「値」ではなく「後で実行する必要がある操作の結果」というように論理的に同じであるため、クラスがあると便利な場合があります。

11
user101289

LinkedHashMapは、探している挿入順序機能を備えた唯一のJavaマップです。したがって、依存関係の逆転の原則を破棄するのは魅力的で、おそらく実際的です。最初に、 [〜#〜] solid [〜#〜] が何をするように要求するかを示します。

注:Ramdalという名前を、このインターフェースのコンシューマーがこのインターフェースの所有者であることを示すわかりやすい名前に置き換えます。これにより、挿入順序が重要かどうかを決定する権限が与えられます。これをInsertionOrderMapと呼ぶだけでは、本当に要点を逃しています。

public interface Ramdal {
    //ISP asks for just the methods that processData() actually uses.
    ...
}

public class RamdalLinkedHashMap extends LinkedHashMap implements Ramdal{} 

Ramdal<Key, Value> ramdal = new RamdalLinkedHashMap<>();

ramdal.put(key1, value1);
ramdal.put(key2, value2);

processData(ramdal);

これは大きなデザインですか?たぶん、LinkedHashMap以外の実装が必要になる可能性があるかどうかによって異なります。しかし、あなたがDIPに従っていないのは、それが大変な苦痛であるというだけの理由であるなら、ボイラープレートがこれ以上苦痛であるとは思えません。これは、手に負えないコードが実装していないインターフェイスを実装したいときに使用するパターンです。最も苦しい部分は本当に良い名前を考えることです。

4
candied_orange

だから、ここであなたのコンテキストを理解してみましょう:

...挿入順序が重要...マップのソートは重い操作になるでしょう...

...クエリ結果はすでにソートされています希望どおり

さて、あなたは現在何をしているか:

データベースからタプルのセットをフェッチしていて、マップにputting it ...

そして、これがあなたの現在のコードです:

public void processData(LinkedHashMap<Key, Value> data) {...}

私の提案は次のことをすることです:

  • 依存性注入を使用し、MyTupleRepositoryを処理メソッドに注入します(MyTupleRepositoryは、通常DBからTupleオブジェクトを取得するオブジェクトによって実装されるインターフェースです)。
  • internallyを処理メソッドに追加し、リポジトリ(別名DB、既に順序付けされたデータを返す)からのデータを特定のLinkedHashMapコレクションに入れます。これは、処理アルゴリズムの内部詳細であるためです(これは、データはデータ構造に配置されます);
  • これは既に行っていることのほとんどですが、この場合、処理メソッド内で行われることに注意してください。リポジトリが別の場所でインスタンス化されている(すでにデータを返すクラスがあり、これはこの例ではリポジトリです)

コード例

public interface MyTupleRepository {
    Collection<MyTuple> GetAll();
}

//Concrete implementation of data access object, that retrieves 
//your tuples from DB; this data is already ordered by the query
public class DbMyTupleRepository implements MyTupleRepository { }

//Injects some abstraction of repository into the processing method,
//but make it clear that some exception might be thrown if data is not
//arranged in some specific way you need
public void processData(MyTupleRepository tupleRepo) throws DataNotOrderedException {

    LinkedHashMap<Key, Value> data = new LinkedHashMap<Key, Value>();

    //Represents the query to DB, that already returns ordered data
    Collection<MyTuple> myTuples = tupleRepo.GetAll();

    //Optional: this would throw some exception if data is not ordered 
    Validate(myTuples);

    for (MyTupleData t : myTuples) {
        data.put(t.key, t.value);
    }

    //Perform the processing using LinkedHashMap...
    ...
}

これはソナー警告を取り除き、処理メソッドで必要なデータの特定のレイアウトをシグネチャで指定すると思います。

0
Emerson Cardoso

使用したデータ構造が理由でそこにあることを伝えたい場合は、メソッドのシグネチャの上にコメントを追加します。将来、別の開発者がこのコード行に遭遇し、ツールの警告に気付いた場合、彼らもコメントに気づき、問題の「修正」を控えるかもしれません。コメントがない場合、署名の変更を止めることはできません。

警告の抑制は、抑制自体は警告が抑制された理由を述べていないため、私の意見ではコメントよりも劣っています。警告の抑制とコメントの組み合わせも問題ありません。

0
Kapol