地図をループして重複を見つける

私のコードは次のことを行います。

プロパティを持つ2つの Map があります。同じキーと値を持つプロパティがすでに存在するかどうかを確認する必要があり、存在する場合は例外をスローします。

    Map existingProperties = requires.get(requiresIndex).getProperties();
    for (Map.Entry property : properties.entrySet()) {
        if (existingProperties.containsKey(property.getKey())){
            if (existingProperties.get(property.getKey()) instanceof String && property.getValue() instanceof String ){
                String existingValue = (String) existingProperties.get(property.getKey());
                String newValue = (String) property.getValue();
                if (existingValue.equals(newValue)){
                    throw new Exception("Property " + property.getKey() + " is existsing with value " + newValue + "in requires " + requiresName);
                }
            }
        }
    }

私たちはJava 8に取り組んでいます。それを書くためのより良い/より短い方法はありますか?

3
ru de
あなたがアップデートとして追加したコードは、あなたが私たちにあなたの仕事をしてほしいと思ったかのように私に感じさせます。更新されたコードにいくつかの提案を適用したならば、それはより心強いでしょう。しかし、その後、フォローアップの質問をすることがより適切でしょう。
追加された 著者 Michael Gilbert,
map.containsValueは、必要なことに役立ちますか?
追加された 著者 Allinyourhead,
もう少しコードを追加してください。 g。 require が定義されている場所。可能であれば、メソッド全体。コードスニペットで参照されているすべてのコードを見るのは良いでしょう。
追加された 著者 Ahkam Nihardeen,

4 答え

Sharon Ben Asherはストリームを使って良い答えを得ています。初心者はJavaのストリームに慣れていないことが多いので、元のコードについていくつか提案を加えたいと思います。

コードに冗長性があるため、コードが複数回実行されるため、読みやすさが低下したり、パフォーマンスが低下したり、バグが発生する可能性があります。この問題に取り組むために、メソッド呼び出しの結果を変数に保存し、その変数に複数回アクセスするだけで済みます。これにより、結果に新しい名前を付けることもできます。また、コード行数の点でコードが多少長くなる場合がありますが、コードが大幅にきれいになり、個々の行が短くなることもあります。

最も重要な例は、 property.getKey()property.getValue()を複数回呼び出すことです。代わりに、ループの始めにそれを行い、結果を変数に保存してください。

for (Map.Entry property : properties.entrySet()) {
    String key = property.getKey();
    Object value = property.getValue();

それから、コード内でそれらにアクセスするので、同じ値がどこで使用されているかがわかりやすくなります。別のマップの別の要素にアクセスするために同じメソッド呼び出しを使用しているので、ここでも変数を使用して結果を保存し、コードをより明確で短くすることができます。

    if (existingProperties.containsKey(key)) {
        Object existingObject = existingProperties.get(key);
        if (existingObject instanceof String && value instanceof String) {
            String existingValue = (String) existingObject;
            String newValue = (String) value;

非常に長い行(長い文字列の連結など)を2行または3行にフォーマットして、読みやすくすることができます。改行を入れて論理部分に分割できます。

            if (existingValue.equals(newValue)) {
                throw new Exception("Property " + key +
                                    " is existing with value " + newValue +
                                    " in requires " + requiresName);
            }

ちなみに、 "in require" という文字列の先頭にスペースが欠けているようで、 "のタイプミスが" という値で存在します。

5
追加された

あなたの現在のコードは黒い文字の大きなブロックのように見えます。その中にはたくさんの単語があるので、読むのは難しいと思います。したがって、私の最初のアイデアは、このコードをIDEに貼り付けて、IDEに読みやすくさせることでした。

static void checkForDuplicates() {
    Map existingProperties = requires.get(requiresIndex).getProperties();
    for (Map.Entry property : properties.entrySet()) {
        if (existingProperties.containsKey(property.getKey())) {
            if (existingProperties.get(property.getKey()) instanceof String && property.getValue() instanceof String) {
                String existingValue = (String) existingProperties.get(property.getKey());
                String newValue = (String) property.getValue();
                if (existingValue.equals(newValue)) {
                    throw new Exception("Property " + property.getKey() + " is existsing with value " + newValue + "in requires " + requiresName);
                }
            }
        }
    }
}

最初に、適切な名前のメソッドでコードをラップしました。これは、入力内容と入力元を明確にするために、ここにコードを投稿する前にすべきことです。

それから、私は矛盾したスペースを取り除くためにコードをフォーマットしました。コードの基本的なレイアウトを監視することは人道的な作業ではないので、IDEがあなたのためにそれをするべきです。

今、コードはコンパイルされません。厳密な意味で、これは既にあなたの質問をこのサイトの話題から外す可能性があり、それには動作するコードが必要です。そしてコンパイルさえしないコードは定義により動作しません。

すべての未知の変数をパラメータに変換した後のコードは、

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) throws Exception {

    for (Map.Entry property : properties.entrySet()) {
        if (existingProperties.containsKey(property.getKey())) {
            if (existingProperties.get(property.getKey()) instanceof String && property.getValue() instanceof String) {
                String existingValue = (String) existingProperties.get(property.getKey());
                String newValue = (String) property.getValue();
                if (existingValue.equals(newValue)) {
                    throw new Exception("Property " + property.getKey() + " is existsing with value " + newValue + "in requires " + requiresName);
                }
            }
        }
    }
}

それでも読みやすさの改善はありません。このコードをより美しくすることができるかどうかIDEに尋ねてみましょう。地図上で反復するには、Java 8より前のJavaでは多くのコードが必要です。しかし、まず最初に、変数に keyvalue を抽出して、コードをもう少し軽くします。

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) throws Exception {

    for (Map.Entry property : properties.entrySet()) {
        String key = property.getKey();
        Object value = property.getValue();

        if (existingProperties.get(key) instanceof String && value instanceof String) {
            String existingValue = (String) existingProperties.get(key);
            String newValue = (String) value;
            if (existingValue.equals(newValue)) {
                throw new Exception("Property " + key + " is existsing with value " + newValue + "in requires " + requiresName);
            }
        }
    }
}

快方に向かっている。黒いコードはゆっくりと消え、より多くの色のための場所を作ります。

次に、 new new Exceptionthrow new IllegalStateException に置き換えます。後者はメソッド定義で明示的に宣言する必要がないためです( throws Exceptionをスローします) )そして次のステップで素晴らしいリファクタリングを可能にします。

そして今やIDE(私の場合はIntelliJ)は自動的にこの大きなコードを同等の短いコードに変換することができます。そのためには、 for キーワードにカーソルを置き、Alt + Enterを押して Map.forEachに置換を選択します。

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) {

    properties.forEach((key, value) -> {
        if (existingProperties.get(key) instanceof String && value instanceof String) {
            String existingValue = (String) existingProperties.get(key);
            String newValue = (String) value;
            if (existingValue.equals(newValue)) {
                throw new IllegalStateException("Property " + key + " is existsing with value " + newValue + "in requires " + requiresName);
            }
        }
    });
}

これはすでに大いに役に立ちました。 2段落のコードではなく、1段落だけが残ります。

次のステップでは、 existingProperties.get(key)の呼び出しを変数(Ctrl + Alt + V)に展開し、 existingValue と呼びます。同じ名前の別の変数が既に存在するため、これによりコンパイルエラーが発生します。 if 節の中に2つの変数は実際には必要ないので、それらを削除して外側の変数に置き換えるだけです。

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) {

    properties.forEach((key, value) -> {
        Object existingValue = existingProperties.get(key);
        if (existingValue instanceof String && value instanceof String) {
            if (existingValue.equals(value)) {
                throw new IllegalStateException("Property " + key + " is existsing with value " + value + "in requires " + requiresName);
            }
        }
    });
}

in require の前に含まれている例外のテキストにスペースがありませんが、それは簡単にはわかりません。このコードを読みやすくするには、文字列内のどこかにカーソルを置いてAlt + Enterキーを押して、 + 演算子を String.format に置き換えます。メニューから「+」を「String.format」に置き換えるを選択します。

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) {

    properties.forEach((key, value) -> {
        Object existingValue = existingProperties.get(key);
        if (existingValue instanceof String && value instanceof String) {
            if (existingValue.equals(value)) {
                throw new IllegalStateException(String.format("Property %s is existsing with value %sin requires %s", key, value, requiresName));
            }
        }
    });
}

例外を含む行はまだ非常に長いので、例外メッセージを変数(Ctrl + Alt + V)に抽出し、それをうまくフォーマットします。次に、 if 条件をもっと短く同等のものに置き換えます。

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) {

    properties.forEach((key, value) -> {
        Object existingValue = existingProperties.get(key);
        if (existingValue instanceof String && Objects.equals(existingValue, value)) {
            String message = String.format(
                    "Property %s is existsing with value %sin requires %s",
                    key, value, requiresName);
            throw new IllegalStateException(message);
        }
    });
}

最後のステップは、例外メッセージの誤字( existingin )を修正することです。これで完了です。

static void checkForDuplicates(
        Map existingProperties,
        Map properties,
        String requiresName) {

    properties.forEach((key, value) -> {
        Object existingValue = existingProperties.get(key);
        if (existingValue instanceof String && Objects.equals(existingValue, value)) {
            String message = String.format(
                    "Property %s is existing with value %s in requires %s",
                    key, value, requiresName);
            throw new IllegalStateException(message);
        }
    });
}
2
追加された

それはコメントで要求されたのでさて、ここに行きます

  1. You can replace the for loop with a java 8 stream() of the EntrySet() collection of the properties map.

  2. You can replace all the nested if statements with a filter() you can have your choice of separate filter()s for each if statement, or just concatenate all of them together with && operator.

  3. after the filter(), you need to tell the stream to end (aka Short-circuit) by finding the first item that matches the filter.

  4. The filter() operation returns an Optional since it is possible that no item satisfies the filter's predicate. in the question, it is required to take an action only if a match is found, so you can add an ifPresent() that takes a Consumer which does something to the matched item and returns void. ifPresent() does nothing if no item satisfies the filter's predicate.

注:投稿されたコードは、 throw ステートメントに記載されている newValue および requireName の宣言方法を指定していません。ラムダ式に含めるには、これらが最後でなければならないことに注意してください。

properties.entrySet().stream()
    .filter(property -> 
        existingProperties.containsKey(property.getKey()) &&
        existingProperties.get(property.getKey()) instanceof String  && 
        property.getValue() instanceof String  &&
        existingProperties.get(property.getKey()).equals(property.getValue()))
    .findFirst()
    .ifPresent(property -> {
        throw new Exception("Property " + property.getKey() + " is existsing with value " + newValue
                + "in requires " + requiresName);
    });

stream 機能を使用したい理由

  1. コレクションに対する実際の繰り返しはストリームライブラリに委ねられ、ストリームライブラリはシーケンシャルループの代わりにこれを最適化することを決定するかもしれません。
  2. コードはより簡潔で明確です。
2
追加された
新しい投稿を作成したので投稿を編集できません。ストリームフィルタ "> codereview.stackexchange.com/questions/186350/…
追加された 著者 inder,
大丈夫なら私のアップデートを見てください...
追加された 著者 inder,
私はあなたの例のようにそれをやろうとしました、しかし、私はこれで終わります、もし私がそれらを避けることができるならば何か考え?
追加された 著者 inder,
たくさんありがとう@SharonBenAsher - 確認後、 filter に警告(ログ)を追加することは可能ですか
追加された 著者 inder,
答えてくれてありがとう、あなたはそれが既存の実装からどのように優れているかを行(コード)で説明してもらえますか?ストリーム/フィルタ、なぜそれが良いのかどうもありがとう!
追加された 著者 inder,
初心者向けではありません。また、元のコードの何が問題になっているのかも説明してください。それ以外の場合、同じコードの2つのバージョンがあります。レビューではありません。
追加された 著者 Ahkam Nihardeen,
コードの新しいバージョンを追加するだけでなく、元のコードのレビューとコードの説明も追加してください。
追加された 著者 Ahkam Nihardeen,
throw ステートメントを含むブロックに必要なコードを追加できます。
追加された 著者 Shelvacu,
どのような更新?
追加された 著者 Shelvacu,
ストリーム機能の追加の利点
追加された 著者 Shelvacu,
うーん...そしてここで私はストリームの流暢なAPIは自明のことだと思っていた...
追加された 著者 Shelvacu,

Understanding String.equals(Object)

String.equals(Object)は上書きできないため、エントリの値が String であることをテストするだけで安全です。つまり、 instanceof String チェックは1つだけ必要です。

小さなループを繰り返す

さて、これはちょっと時期尚早な最適化の匂いがしますが、 Map の1つが他のものよりもかなり小さいことが確実でそのままになる可能性が高い場合は、小さいもので繰り返すことを検討できますループ。

Map.remove(Object、Object)

Streams are a little harder to understand, but I don't think that means the effort shouldn't be put in ;). There is a slightly shorter, and therefore maybe easier to understand, solution that relies on Java 8's new Map.remove(Object、Object) method to do the entry value comparison for us too.

Map copy = new HashMap<>(requires.get(requiresIndex).getProperties());
Optional> duplicate = properties.entrySet().stream()
        .filter(property -> property.getValue() instanceof String
                                && copy.remove(property.getKey(), property.getValue()))
        .findFirst();
if (duplicate.isPresent()) {
    Map.Entry property = duplicate.get();
    throw new Exception("Property " + property.getKey() + " exists with value "
            + property.getValue() + " in requires " + requiresName);
}

このアプローチでは、2番目のフィルタ条件は、「require map」の copyproperties のエントリから削除されるのと同じエントリが含まれているかどうかを確認します。最初のフィルタ条件は、質問ごとに、少なくとも1つの値が String であることを確認するために必要です。

それでも、これはすべてのエントリの値が Object.equals(Object)の一般規約に厳密に準拠することを前提としています。 )== true ( String.equals(MyType)== false より)。

You should also consider using a more appropriately-typed Exception class instead of the checked Exception. Over here, lambdas do no deal with checked exceptions, so I have used RuntimeException as an example.

編集

値チェックを実行する前に、重複するキーもすべてログに記録する必要があるため、 Stream.peek(Consumer) ステップを(現在2つの)フィルタステップの間に入れます。

Optional> duplicate = properties.entrySet().stream()
        .filter(property -> property.getValue() instanceof String
                                && copy.containsKey(property.getKey()))
        .peek(property -> LOG.warn("Property with key {} already exists in requires {}", 
                                property.getKey(), requires.get(requiresIndex).getName()))
        .filter(property -> copy.remove(property.getKey(), property.getValue()))
        .findFirst();
1
追加された
あなたが助けることができるなら私の更新を見てください...
追加された 著者 inder,
どうもありがとう!
追加された 著者 inder,
最後の質問:)、私は私のアップデートがそれでいいことを確認するためにループを分割する必要があるという警告を提供する必要があるので私はマイナーチェンジであなたの提案を使用しよう if のステートメントを避けるためにありがとう!
追加された 著者 inder,
runtimeException は機能しますが、例外から継承する例外クラスがあります。これにより未処理のエラーが発生します。例外クラスを使用する方法はありますかどうもありがとう
追加された 著者 inder,
あなたはそれを RuntimeException か何か他のものに変更しますか?
追加された 著者 inder,
おかげで、あなたは私が例外を処理する方法をコードで例を提供できますか?ありがとう
追加された 著者 inder,
おかげで私はそれを試してみて、私はそれがストリーム内にあるので、エラーのない手渡しの例外を得た、何か考え?
追加された 著者 inder,
ストリームは Exception のようなチェックされた例外を好みません。その点を明確にするために回答を少し更新しました。
追加された 著者 Tim,
更新された、そしてそれは実際にそれほどストリーム処理ではないラムダ部分です。
追加された 著者 Tim,
要件をよりよく反映するように更新されました
追加された 著者 Tim,
ロギング要件を説明するために回答を更新しました...
追加された 著者 Tim,
そうではありません...あなたの要求を考えると、1つの if 条件を回避する方法はないと思います。
追加された 著者 Tim,