爪哇“?”用于检查 null 的运算符 - 它是啥? (不是三元!)

Posted

技术标签:

【中文标题】爪哇“?”用于检查 null 的运算符 - 它是啥? (不是三元!)【英文标题】:Java "?" Operator for checking null - What is it? (Not Ternary!)爪哇“?”用于检查 null 的运算符 - 它是什么? (不是三元!) 【发布时间】:2011-05-22 08:48:47 【问题描述】:

我正在阅读与 slashdot 故事相关的文章,偶然发现了这个小花絮:

获取最新版本的 Java,其中 尝试进行空指针检查 通过提供速记语法更容易 用于无休止的指针测试。只是 为每个方法添加一个问号 调用自动包含一个 测试空指针,替换 if-then 语句的老鼠巢,例如 作为:

    public String getPostcode(Person person) 
      String ans= null;
      if (person != null) 
        Name nm= person.getName();
        if (nm!= null) 
          ans= nm.getPostcode();
        
      
      return ans
     

有了这个:

public String getFirstName(Person person) 
      return person?.getName()?.getGivenName();
     

我搜索了互联网(好吧,我花了至少 15 分钟在谷歌上搜索“java 问号”的变体)但一无所获。所以,我的问题是:有这方面的官方文件吗?我发现 C# 有一个类似的运算符(“??”运算符),但我想获取我正在使用的语言的文档。或者,这只是我使用的三元运算符从未见过。

谢谢!

编辑:文章链接:http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292

【问题讨论】:

能否给我们至少一个文章的链接? 那sn-p的来源呢? 文章有误。 infoworld.com/print/145292 我相信已经提交了 Project Coin 提交。但它没有被选中(由于文章中提到的原因 - 如果你想做那种事情,请使用 C# 或其他东西),并且肯定不在 Java 语言的当前版本中。 这和C#不一样??操作员: ??合并空值,即A ?? B == (A != null) ? A : B。如果对象引用不为空,即A?.B == (A != null) ? A.B : null,这似乎会评估对象的属性。 @Erty:作为@NotNull 注释的大用户,我编写的代码中基本上无处不在,我不太清楚NPE 是什么(除非使用糟糕的设计API) .然而,我发现这种“快捷符号”既可爱又有趣。当然,这篇文章是正确的:毕竟,它并没有消除问题的根源:由于快速和松散的编程导致空值的泛滥。 'null' 不存在在 OOA/OOD 级别。这是另一个几乎可以解决的 Java 特有的废话。对我来说,@NotNull 无处不在。 【参考方案1】:

最初的想法来自groovy。它是作为 Project Coin 的一部分为 Java 7 提出的:https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC(Elvis 和其他 Null-Safe Operators),但尚未被接受。

建议使用相关的 Elvis 运算符 ?: 使 x ?: y 成为 x != null ? x : y 的简写,在 x 是复杂表达式时特别有用。

【讨论】:

在java中(没有自动强制为null)x!=null ? x : y的简写 @Michael Borgwardt:说得好,我在考虑 groovy 语义。 ? 是猫王的签名 quiff; : 像往常一样只代表一双眼睛。也许?:-o 更令人回味…… ?:0 应该是“非空,非 0”运算符。做到这一点。 将提案称为猫王运营商实际上是错误的。 mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html 的解释“Null-safe dereferencing”是一个更好的术语。【参考方案2】:

这种语法在 Java 中不存在,也不会包含在我所知道的任何即将发布的版本中。

【讨论】:

为什么投反对票?据我所知,这个答案是 100% 正确的......如果你知道不同的东西,请说出来。 @Webinator:它不会出现在 Java 7 或 8 中,目前也没有其他“即将推出的版本”。我也发现它不太可能进入,因为它鼓励了相当糟糕的做法。我也不认为“还”是必要的,因为“Java 中不存在”与“Java 中永远不会存在”不同。 @Webinator:几位发帖人评论说提案已提交但被拒绝。因此,答案是 100% 准确的。赞成以抵消反对票。 @ColinD 不好的做法是当你放弃这个丑陋的代码并决定使用Optionalmap的东西时。如果值可以为空,我们并没有隐藏问题,这意味着它有时应该为空,你必须处理它。有时默认值是完全合理的,这不是一个坏习惯。 Java 只是拒绝有点现代。只需终止此语言即可。【参考方案3】:

解决缺少“?”的一种方法使用 Java 8 而没有 try-catch 开销的运算符(如前所述,它还可以隐藏源自其他地方的 NullPointerException)是创建一个类来以 Java-8-Stream 样式“管道”方法。

public class Pipe<T> 
    private T object;

    private Pipe(T t) 
        object = t;
    

    public static<T> Pipe<T> of(T t) 
        return new Pipe<>(t);
    

    public <S> Pipe<S> after(Function<? super T, ? extends S> plumber) 
        return new Pipe<>(object == null ? null : plumber.apply(object));
    

    public T get() 
        return object;
    

    public T orElse(T other) 
        return object == null ? other : object;
    

那么,给定的例子将变成:

public String getFirstName(Person person) 
    return Pipe.of(person).after(Person::getName).after(Name::getGivenName).get();


[编辑]

经过进一步思考,我发现实际上只有使用标准 Java 8 类才能实现相同的目标:

public String getFirstName(Person person) 
    return Optional.ofNullable(person).map(Person::getName).map(Name::getGivenName).orElse(null);

在这种情况下,甚至可以选择默认值(如"&lt;no first name&gt;")而不是null,方法是将其作为orElse 的参数传递。

【讨论】:

我更喜欢您的第一个解决方案。您将如何改进您的 Pipe 类以适应 orElse 功能,以便我可以在 orElse 方法中传递任意非空对象? @ThanosFisherman 我已将orElse 方法添加到Pipe 类。 这是过度使用......并且不鼓励。【参考方案4】:

在 Java 7 中有一个提议,但被拒绝了:

http://tech.puredanger.com/java7/#null

【讨论】:

【参考方案5】:

Java 没有 exact 语法,但从 JDK-8 开始,我们有 Optional API 以及各种可供我们使用的方法。所以,C#版本用null conditional operator:

return person?.getName()?.getGivenName(); 

在Java中可以用Optional API写成如下:

 return Optional.ofNullable(person)
                .map(e -> e.getName())
                .map(e -> e.getGivenName())
                .orElse(null);

如果 persongetNamegetGivenName 中的任何一个为 null,则返回 null。

【讨论】:

【参考方案6】:

参见:https://blogs.oracle.com/darcy/project-coin:-the-final-five-or-so(特别是“Elvis 和其他 null 安全运算符”)。

结果是 Java 7 考虑了此功能,但未包括在内。

【讨论】:

【参考方案7】:

实际上是Groovy's safe-dereference operator。你不能在纯 Java 中使用它(很遗憾),所以那篇文章完全是错误的(或者更可能有点误导,如果它声称 Groovy 是“Java 的最新版本”)。

【讨论】:

所以文章是错误的 - 本机 java 中不存在语法。嗯。 但是链接坏了【参考方案8】:

可以定义 util 方法,使用 Java 8 lambda 几乎可以很好地解决这个问题。

这是H-MANs solution 的变体,但它使用带有多个参数的重载方法来处理多个步骤,而不是捕获NullPointerException

即使我认为这个解决方案有点酷,我认为我更喜欢Helder Pereira's seconds one,因为它不需要任何 util 方法。

void example() 
    Entry entry = new Entry();
    // This is the same as H-MANs solution 
    Person person = getNullsafe(entry, e -> e.getPerson());    
    // Get object in several steps
    String givenName = getNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.getGivenName());
    // Call void methods
    doNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.nameIt());        


/** Return result of call to f1 with o1 if it is non-null, otherwise return null. */
public static <R, T1> R getNullsafe(T1 o1, Function<T1, R> f1) 
    if (o1 != null) return f1.apply(o1);
    return null; 


public static <R, T0, T1> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, R> f2) 
    return getNullsafe(getNullsafe(o0, f1), f2);


public static <R, T0, T1, T2> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Function<T2, R> f3) 
    return getNullsafe(getNullsafe(o0, f1, f2), f3);



/** Call consumer f1 with o1 if it is non-null, otherwise do nothing. */
public static <T1> void doNullsafe(T1 o1, Consumer<T1> f1) 
    if (o1 != null) f1.accept(o1);


public static <T0, T1> void doNullsafe(T0 o0, Function<T0, T1> f1, Consumer<T1> f2) 
    doNullsafe(getNullsafe(o0, f1), f2);


public static <T0, T1, T2> void doNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Consumer<T2> f3) 
    doNullsafe(getNullsafe(o0, f1, f2), f3);



class Entry 
    Person getPerson()  return null; 


class Person 
    Name getName()  return null; 


class Name 
    void nameIt() 
    String getGivenName()  return null; 

【讨论】:

【参考方案9】:

我不确定这是否可行;例如,如果人员引用为空,运行时将用什么替换它?一个新人?这将要求 Person 具有您在这种情况下所期望的一些默认初始化。您可以避免空引用异常,但如果您没有计划这些类型的设置,您仍然会遇到不可预知的行为。

?? C# 中的运算符最好称为“coalesce”运算符;您可以链接多个表达式,它将返回第一个不为空的表达式。不幸的是,Java 没有它。我认为你能做的最好的事情是使用三元运算符来执行空检查,并在链中的任何成员为空时评估整个表达式的替代方案:

return person == null ? "" 
    : person.getName() == null ? "" 
        : person.getName().getGivenName();

你也可以使用 try-catch:

try

   return person.getName().getGivenName();

catch(NullReferenceException)

   return "";

【讨论】:

"运行时会用什么替换它?" ...阅读问题可能会有所帮助:-P它将用null替换它。一般来说,这个想法似乎是 person?.getName 如果 person 为 null 则评估为 null ,否则为 person.getName 。所以这很像在所有示例中用 null 替换 ""。 getName()getGivenName() 中也可能引发 NullReferenceException,如果您只是为所有事件返回一个空字符串,您将不知道。 在 Java 中是 NullPointerException。 在 C# 中,person?.getName()?.getGivenName() ?? "" 等同于您的第一个示例,但如果 getGivenName() 返回 null,它仍然会给出 ""【参考方案10】:

由于很多答案提到Java语言没有这个功能。

compiler plugin 在少数情况下是可能的,几乎没有限制

在你提到的示例代码中可以写成

public String getFirstName(Person person) 
  @NullSafe
  String retVal = person.getName().getGivenName();
  return retVal;
 

PS:我是插件作者

【讨论】:

【参考方案11】:

你有它,Java 8 中的 null 安全调用:

public void someMethod() 
    String userName = nullIfAbsent(new Order(), t -> t.getAccount().getUser()
        .getName());


static <T, R> R nullIfAbsent(T t, Function<T, R> funct) 
    try 
        return funct.apply(t);
     catch (NullPointerException e) 
        return null;
    

【讨论】:

我得试试这个。 SI 严重怀疑这整个“可选”业务。看起来像一个讨厌的黑客。 Elvis Operator 提案的全部目的是在一行中完成此操作。这种方法并不比上面的 "if(!=null) 方法好。事实上,我认为它更糟,因为它不是一个直截了当的方法。此外,您应该避免由于开销而引发和捕获错误。 正如@Darron 在另一个答案中所说,这里同样适用:“这种风格的问题是 NullPointerException 可能不是来自您期望的地方。因此它可能隐藏一个真正的错误。”【参考方案12】:

如果有人正在寻找旧 java 版本的替代品,你可以试试我写的这个:

/**
 * Strong typed Lambda to return NULL or DEFAULT VALUES instead of runtime errors. 
 * if you override the defaultValue method, if the execution result was null it will be used in place
 * 
 * 
 * Sample:
 * 
 * It won't throw a NullPointerException but null.
 * <pre>
 * @code
 *  new RuntimeExceptionHandlerLambda<String> () 
 *      @Override
 *      public String evaluate() 
 *          String x = null;
 *          return x.trim();
 *        
 *  .get();
 * 
 * <pre>
 * 
 * 
 * @author Robson_Farias
 *
 */

public abstract class RuntimeExceptionHandlerLambda<T> 

    private T result;

    private RuntimeException exception;

    public abstract T evaluate();

    public RuntimeException getException() 
        return exception;
    

    public boolean hasException() 
        return exception != null;
    

    public T defaultValue() 
        return result;
    

    public T get() 
        try 
            result = evaluate();
         catch (RuntimeException runtimeException) 
            exception = runtimeException;
        
        return result == null ? defaultValue() : result;
    


【讨论】:

【参考方案13】:

您可以测试您提供的代码,它会给出语法错误。因此,Java 不支持它。 Groovy 确实支持它,它是为 Java 7 提出的(但从未被包含在内)。

但是,您可以使用 Java 8 中提供的 Optional。这可能会帮助您实现类似的目标。 https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Example Code for Optional

【讨论】:

【参考方案14】:

由于除非您安装的操作系统 >= 24,否则 android 不支持 Lambda 函数,因此我们需要使用反射。

// Example using doIt function with sample classes
public void Test() 
    testEntry(new Entry(null));
    testEntry(new Entry(new Person(new Name("Bob"))));


static void testEntry(Entry entry) 
    doIt(doIt(doIt(entry,  "getPerson"), "getName"), "getName");


// Helper to safely execute function 
public static <T,R> R doIt(T obj, String methodName) 
    try 
       if (obj != null) 
           return (R)obj.getClass().getDeclaredMethod(methodName).invoke(obj);
     catch (Exception ignore) 
    
    return null;

// Sample test classes
    static class Entry 
        Person person;
        Entry(Person person)  this.person = person; 
        Person getPerson()  return person; 
    

    static class Person 
        Name name;
        Person(Name name)  this.name = name; 
        Name getName()  return name; 
    

    static class Name 
        String name;
        Name(String name)  this.name = name; 
        String getName() 
            System.out.print(" Name:" + name + " ");
            return name;
        
    

【讨论】:

【参考方案15】:

如果这不是你的性能问题,你可以写

public String getFirstName(Person person) 
  try 
     return person.getName().getGivenName();
   catch (NullPointerException ignored) 
     return null;
  
 

【讨论】:

这种风格的问题是 NullPointerException 可能不是来自你期望的地方。因此它可能隐藏了一个真正的错误。 @Darron,您能否举一个您编写的可能引发 NPE 的 getter 示例以及您希望如何以不同方式处理它? 您将它运行到一个单独的变量中,并像往常一样使用 if 测试它。这个运算符背后的想法是消除这种丑陋。 Darron 是对的,您的解决方案可能会隐藏并丢弃您想要抛出的异常。比如getName()在内部抛出了一个你不想丢弃的异常。 没有任何异常,它必须是 NullPointerException。您正在尝试保护自己免受尚未开始解释它在实际应用程序中如何发生的情况的影响。 在真正的应用程序中Person 可能是访问数据库或一些非堆内存的代理,并且可能在某处存在错误......不太现实,但是,彼得,我敢打赌你从来没有写过像上面这样的一段代码。

以上是关于爪哇“?”用于检查 null 的运算符 - 它是啥? (不是三元!)的主要内容,如果未能解决你的问题,请参考以下文章

检查空值的正确方法是啥?

Flutter Bloc Test:空值检查运算符用于空值(空安全)

== 检查布尔值是不是完全相等? - 爪哇

??!??! 是啥意思?运算符在 C 中做啥?

java 四个点是啥特殊运算符?

c#中 new 这个关键字是啥意思