web-dev-qa-db-ja.com

最初の非ヌル値を返します

私にはいくつかの機能があります:

String first(){}
String second(){}
...
String default(){}

デフォルトを除き、それぞれがヌル値を返すことがあります。 各関数は異なるパラメータを取ることができます。たとえば、最初に引数を取らない、2番目に文字列を取る、3番目に3つの引数を取る、などです

ObjectUtils.firstNonNull(first(), second(), ..., default());

問題は、関数呼び出しのために、これが熱心な評価を行うことです。どこで早く終了したいのですか?2番目の関数の後(関数呼び出しは高価になる可能性があるため、API呼び出しを考えるなど)他の言語では、これと同様のことができます:

return first() || second() || ... || default()

Javaでは、次のようなことができることを知っています。

String value;
if (value = first()) == null || (value = second()) == null ...
return value;

すべての== nullチェックのため、IMOは非常に読みやすいものではありません。 ObjectUtils.firstNonNull() は最初にコレクションを作成し、次に反復します。関数が遅延評価される限り問題ありません。

提案? (ifsの束を行うことに加えて)

21
lorenzocastillo
String s = Stream.<Supplier<String>>of(this::first, this::second /*, ... */)
                 .map(Supplier::get)
                 .filter(Objects::nonNull)
                 .findFirst()
                 .orElseGet(this::defaultOne);

最初の非ヌル値で停止するか、defaultOneから返される値を設定します。シーケンシャルである限り、安全です。もちろん、これにはJava 8以降が必要です。

Null以外の値が最初に発生したときに停止する理由は、 Stream が各ステップを処理する方法によるものです。 map中間操作 であるため、 filter です。反対側の findFirst短絡操作 です。そのため、フィルターに一致するまで次の要素で続行します。一致する要素がない場合、空のオプションが返されるため、 orElseGet -supplierが呼び出されます。

this::firstなどは単なるメソッド参照です。静的な場合は、YourClassName::firstなどに置き換えます。

メソッドのシグネチャが異なる場合の例を次に示します。

String s = Stream.<Supplier<String>>of(() -> first("takesOneArgument"),
                                       () -> second("takes", 3, "arguments")
                                   /*, ... */)
                 .map(Supplier::get)
                 .filter(Objects::nonNull)
                 .findFirst()
                 .orElseGet(this::defaultOne);

Suppliergetを呼び出したときにのみ評価されることに注意してください。そうすれば、怠laな評価の振る舞いが得られます。 supplier-lambda-expression内のmethod-parametersは、最終または実質的に最終でなければなりません。

34
Roland

これは、Suppliersのストリームを使用して、きれいに実行できます。

_Optional<String> result = Stream.<Supplier<String>> of(
     () -> first(), 
     () -> second(),
     () -> third() )
  .map( x -> x.get() )
  .filter( s -> s != null)
  .findFirst(); 
_

これが機能する理由は、外観にもかかわらず、実行全体がfindFirst()によって駆動され、filter()から項目を取得し、map()から項目を遅延取得するためです。 get()を呼び出して、各プルを処理します。 findFirst()は、1つのアイテムがフィルターを通過したときにストリームからのプルを停止するため、後続のサプライヤーはget()が呼び出されません。

私は個人的により明確で表現力豊かな宣言的なStreamスタイルを見つけますが、haveを使用してStreamを使用してSuppliersを操作したくない場合は、スタイル:

_Optional<String> firstNonNull(List<Supplier<String>> suppliers {
    for(Supplier<String> supplier : suppliers) {
        String s = supplier.get();
        if(s != null) {
            return Optional.of(s);
        }
    }
    return Optional.empty();
}
_

リストからオプションを使い果たした場合、Optionalを返す代わりにStringを等しく返すことができ、null(yuk)、デフォルト文字列を返す、または例外をスローすることができます。

9
slim

相互にいかなる種類の接続も表現しない多数の個別の関数を扱っているため、読みやすくありません。それらを一緒にしようとすると、方向性の欠如が明らかです。

代わりに試してください

_ public String getFirstValue() {
      String value;
      value = first();
      if (value != null) return value;
      value = second();
      if (value != null) return value;
      value = third();
      if (value != null) return value;
      ...
      return value;
 }
_

長くなりますか?多分。しかし、あなたはあなたのアプローチに対して友好的でないインターフェースの上にコードを適用しています。

これで、インターフェイスを変更できる場合は、インターフェイスをより使いやすくすることができます。可能な例は、ステップを「ValueProvider」オブジェクトにすることです。

_public interface ValueProvider {
    public String getValue();
}
_

そして、あなたはそれを次のように使うことができます

_public String getFirstValue(List<ValueProvider> providers) {
    String value;
    for (ValueProvider provider : providers) {
       value = provider.getValue();
       if (value != null) return value;
    }
    return null;
}
_

また、他にもさまざまなアプローチがありますが、よりオブジェクト指向になるようにコードを再構築する必要があります。 Javaはオブジェクト指向プログラミング言語であるという理由だけで、常にオブジェクト指向の方法で使用されることを意味するわけではありません。first()... last()メソッドリストは、Listをモデル化していないため、オブジェクト指向ではありません。メソッド名は表現力豊かですが、Listにはメソッドがあります。 forループやIteratorsなどのツールと簡単に統合できます。

3
Edwin Buck

Java 8を使用している場合、これらの関数呼び出しをラムダに変換できます。

public static<T> T firstNonNull(Supplier<T> defaultSupplier, Supplier<T>... funcs){
    return Arrays.stream(funcs).filter(p -> p.get() != null).findFirst().orElse(defaultSupplier).get();
}

汎用的な実装を望まず、Stringsのみに使用する場合は、TStringに置き換えます。

public static String firstNonNull(Supplier<String> defaultSupplier, Supplier<String>... funcs){
    return Arrays.stream(funcs).filter(p -> p.get() != null).findFirst().orElse(defaultSupplier).get();
}

そして、次のように呼び出します:

firstNonNull(() -> getDefault(), () -> first(arg1, arg2), () -> second(arg3));

追伸btw defaultは予約済みのキーワードであるため、メソッド名として使用できません:)

編集:OK、これを行う最良の方法は、オプションを返すことです。その後、デフォルトのサプライヤを個別に渡す必要はありません。

@SafeVarargs
public static<T> Optional<T> firstNonNull(Supplier<T>... funcs){
    return Arrays.stream(funcs).filter(p -> p.get() != null).map(s -> s.get()).findFirst();
}
2
Nestor Sokil

ユーティリティメソッドにパッケージ化する場合、各関数を実行を遅延させるものにラップする必要があります。おそらくこのようなもの:

_public interface Wrapper<T> {
    T call();
}

public static <T> T firstNonNull(Wrapper<T> defaultFunction, Wrapper<T>... funcs) {
    T val;
    for (Wrapper<T> func : funcs) {
       if ((val = func.call()) != null) {
           return val;
       }
    }
    return defaultFunction.call();
}
_

独自のWrapperクラスを定義する代わりに_Java.util.concurrent.Callable_を使用できますが、Callable.call()がスローするように宣言されているという例外に対処する必要があります。

これは、次を使用して呼び出すことができます。

_String value = firstNonNull(
    new Wrapper<>() { @Override public String call() { return defaultFunc(); },
    new Wrapper<>() { @Override public String call() { return first(); },
    new Wrapper<>() { @Override public String call() { return second(); },
    ...
);
_

Java 8、@ dorukayhanが指摘しているように、独自のWrapperクラスを定義せずに、Supplierインターフェースを使用できます。また、呼び出しはラムダを使用すると、よりきれいに実行できます。

_String value = firstNonNull(
    () -> defaultFunc(),
    () -> first(),
    () -> second(),
    ...
);
_

(@Oliver Charlesworthが示唆するように)ラムダ式の省略形としてメソッド参照を使用することもできます。

_String value = firstNonNull(
    MyClass::defaultFunc,
    MyClass::first,
    MyClass::second,
    ...
);
_

私はどちらがより読みやすいかについて2つの心を持っています。

または、他の多くの回答が提案しているストリーミングソリューションの1つを使用できます。

1
Ted Hopp

次のような1つの関数でクラスを作成します。

class ValueCollector {
  String value;
  boolean v(String val) { this.value = val; return val == null; }
}

ValueCollector c = new ValueCollector();
if c.v(first()) || c.v(second()) ...
return c.value;
0
DenisSt