【问题标题】:Return first non-null value返回第一个非空值
【发布时间】:2017-09-02 23:04:24
【问题描述】:

我有很多功能:

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

每个都可以返回一个空值,默认值除外。 每个函数可以采用不同的参数。例如,第一个可以不带参数,第二个可以带一个字符串,第三个可以带三个参数,等等。我想做的是这样的:

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

问题在于,由于函数调用,this 会进行急切评估。在第二个函数之后说,我想提前退出哪里(因为函数调用可能很昂贵,想想 API 调用等)。在其他语言中,您可以执行类似的操作:

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

在 Java 中,我知道我可以执行以下操作:

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

由于所有 == null 检查,IMO 的可读性不太好。ObjectUtils.firstNonNull() 首先创建一个集合,然后进行迭代,只要函数被延迟评估就可以了。

建议? (除了做一堆ifs)

【问题讨论】:

  • 使用方法引用来避免急切的评估?
  • 你有java 8吗?
  • Java 8+ 没问题
  • default 不可能是你的方法名:P

标签: java lazy-evaluation coalesce


【解决方案1】:

上面的例子似乎太长了,不能只在 2 个变量之间进行选择,我会选择这样的东西(除非你有更长的变量列表可供选择):

Optional.ofNullable(first).orElse(Optional.ofNullable(second).orElse(default));

【讨论】:

    【解决方案2】:

    如果你想把它打包成一个实用方法,你必须把每个函数打包成一个延迟执行的东西。也许是这样的:

    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();
    }
    

    您可以使用java.util.concurrent.Callable 而不是定义自己的Wrapper 类,但是您必须处理声明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 接口。此外,使用 lambda 可以更干净地完成调用:

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

    您还可以(正如@Oliver Charlesworth 建议的那样)使用方法引用作为 lambda 表达式的简写:

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

    对于哪个更具可读性,我有两种看法。

    或者,您可以使用许多其他答案提出的流式解决方案之一。

    【讨论】:

    • 你不能排队 ||如果函数不返回布尔值,则在 Java 中
    • @lorenzocastillo - D'oh。是的,第一个选项是行不通的。太多的混合 Java 和 JavaScript 编程。 :(
    • 你应该能够用最后一个 sn-p 中的方法引用替换 lambdas。
    • java.util.function.Supplierjava.util.concurrent.Callable 的更好替代品。唯一的区别是它唯一的方法叫做“get”而不是“call”。
    • @dorukayhan - 好点。我将更新答案的 Java 8 部分。 (还有一个区别:Supplier.get() 没有声明抛出Exception,所以它实际上比Callable.call() 更容易使用。)
    【解决方案3】:

    只需创建一个具有这样一个函数的类:

    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;
    

    【讨论】:

      【解决方案4】:

      这可以通过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() 将停止从流中拉取,因此后续供应商将不会调用get()

      虽然我个人发现声明式 Stream 风格更简洁、更具表现力,但如果你不这样做,你就必须使用 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();
      }
      

      应该很明显,如果您用尽列表中的选项,您可以同样返回 String,而不是返回 String,或者返回 null (yuk)、默认字符串,或者抛出异常。

      【讨论】:

        【解决方案5】:
        String s = Stream.<Supplier<String>>of(this::first, this::second /*, ... */)
                         .map(Supplier::get)
                         .filter(Objects::nonNull)
                         .findFirst()
                         .orElseGet(this::defaultOne);
        

        它在第一个非空值上停止,否则设置从defaultOne 返回的值。只要你保持顺序,你就是安全的。当然这需要 Java 8 或更高版本。

        它在第一次出现非空值时停止的原因是Stream 处理每个步骤的方式。 mapintermediate operationfilter 也是。另一边的findFirstshort-circuiting terminal operation。所以它继续下一个元素,直到一个匹配过滤器。如果没有元素匹配,则返回一个空的可选项,因此调用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);
        

        请注意,Supplier 仅在您对其调用 get 时进行评估。这样你就会得到你的惰性评估行为。您的供应商 lambda 表达式中的方法参数必须是最终的或有效的最终。

        【讨论】:

        • 如果每个方法都采用不同的参数,这会是什么样子?
        • 这是一个例子,说明使用最新的“工具”有时会造成障碍。在 C++ 中,他们最后教授了运算符重载,每个人都在不需要的地方实现它们,从而破坏了语言。这会创建大量的中间对象来处理流处理,防止添加调试语句,并且并不比简单的 for 循环更容易阅读。别误会,我喜欢 Lambda,但如果我想用 Lambda 编写所有东西,我会使用 Lisp。
        • @EdwinBuck 为什么会有障碍?这是 Stream 用于其擅长的领域的一个干净示例。
        • @lorenzocastillo “如果每个方法都采用不同的参数,这会是什么样子?”在我看来,你应该澄清你想要在问题中得到这个答案。请对其进行编辑,以使答案有效,并且您的两个问题都能找到答案。
        • @AndriyKryvtsun 我们想要惰性评估,供应商确保评估仅在我们调用get 时进行。如果你写Stream.of(first(), second()),那么这两个方法都将在Stream被构造之前执行。
        【解决方案6】:

        如果您使用的是 java 8,则可以将这些函数调用转换为 lambda。

        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,请继续将T 替换为String

        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 是保留关键字,因此不能将其用作方法名称:)

        编辑:好的,最好的方法是返回 Optional,然后你不需要单独传递默认供应商:

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

        【讨论】:

          【解决方案7】:

          它不可读,因为您正在处理一堆单独的函数,这些函数彼此之间没有任何形式的联系。当您尝试将它们放在一起时,显然缺乏方向。

          不如试试

           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 等工具集成。

          【讨论】:

          • ValueProvider 的另一个合适的名称是 Supplier。或者您可以使用 JRE 中已经存在的那个。 Supplier&lt;String&gt;
          • @slim 可能,但在不了解实际问题域的情况下,所有名称都将是次优的。
          【解决方案8】:

          您可以通过反射来完成此操作:

          public Object getFirstNonNull(Object target, Method... methods) {
              Object value = null;
              for (Method m : methods) {
                  if ( (value = m.invoke(target)) != null) {
                      break;
                  }
              }
              return value;
          }
          

          【讨论】:

          • 反思应该是最后的手段。此代码不是类型安全的。
          猜你喜欢
          • 2019-12-07
          • 2017-05-15
          • 1970-01-01
          • 1970-01-01
          • 2018-05-20
          • 2017-08-14
          • 1970-01-01
          • 1970-01-01
          • 2015-09-13
          相关资源
          最近更新 更多