返回第一个非空值

Posted

技术标签:

【中文标题】返回第一个非空值【英文标题】: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 【参考方案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 处理每个步骤的方式。 map 是 intermediate operation,filter 也是。另一边的findFirst 是short-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;

【讨论】:

反思应该是最后的手段。此代码不是类型安全的。

以上是关于返回第一个非空值的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中获取第一个非空值?

Mysql函数转换非空值

SQL使用任意顺序查找第一个非空值

返回 MIN 和 MAX 值并忽略空值 - 使用前面的非空值填充空值

如何在 PySpark 中用该列的第一个非空值填充该列的空值

如何忽略 PostgreSQL 窗口函数中的空值?或返回列中的下一个非空值