Java“?” 用于检查null的运算符-这是什么?(不是三元!)


86

我正在阅读一篇与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


3
我们至少可以链接到该文章吗?
Karl Knechtel 2010年

和摘要的来源?
卡其克2010年

5
这篇文章是错误的。infoworld.com/print/145292我相信已为此提交了项目硬币。但是没有选择它(出于本文中提到的原因-如果您想做这种事情,请使用C#或其他方法),并且肯定不在当前Java语言版本中。
Tom Hawtin-大头钉

4
这与C#不同?接线员:合并null,即A ?? B == (A != null) ? A : B。如果对象引用不为null(即),则这似乎是在评估对象的属性A?.B == (A != null) ? A.B : null
Rup 2010年

1
@Erty:作为@NotNull批注的庞大用户,我编写的代码中几乎到处都有@NotNull批注,我现在不太清楚NPE是什么(除非使用糟糕的设计API时)。但是,我发现这种“快捷方式”既可爱又有趣。当然,本文指出以下内容是正确的:毕竟,它并没有消除问题的根源:由于快速而宽松的编程而导致的空值激增。在OOA / OOD级别上不存在“ null”。这是另一种Java特有的废话,可以解决。对我来说,到处都是@NotNull
语法T3rr0r,2010年

Answers:


78

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

提出了相关的Elvis运算符?:的x ?: y简称x != null ? x : y,特别适用于x是复杂表达式的情况。


3
在Java中(没有自动强制转换为null的简写形式)x!=null ? x : y
Michael Borgwardt 2010年

@Michael Borgwardt:好点,我想到的是古怪的语义。
ataylor 2010年

50
?是猫王(Elvis Presley)的签名quiff;在:刚刚代表一双眼睛像往常一样的。也许?:-o更令人回味...
Andrzej Doyle 2010年

4
?:0应为“不为null,也不为0”的运算符。做到这一点。
azz

3
将提案称为猫王运营商实际上是错误的。mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html上的解释 “ Null-safe dereferencing”是一个更好的术语。
JustinKSU 2014年

62

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


9
为什么要投票?据我所知,这个答案是100%正确的...如果您知道不同的地方,请这样说。
ColinD 2010年

6
@Webinator:它将不再是Java 7或8,并且目前没有其他“即将发布的版本”。我还发现它不太可能加入其中,因为它鼓励了相当糟糕的做法。我也认为“还没有必要”,因为“在Java中不存在”与“在Java中将不存在”不同。
ColinD 2010年

9
@Webinator:几个发布者评论说,提案已提交但被拒绝。因此,答案是100%准确的。投票反对反对票。
JeremyP,2010年

2
@ColinD的坏习惯是当您放弃这个丑陋的代码并决定使用Optionalmap填充时。如果值可为空,那么我们并没有隐藏问题,这意味着有时它应该为空,而您必须处理该问题。有时默认值是完全合理的,这不是一个坏习惯。
M.kazem Akhgary

2
Java只是拒绝变得有些现代。只需终止此语言即可。
Plagon


19

解决缺少“?”的一种方法 使用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);
}

在这种情况下,甚至可以选择默认值(例如"<no first name>"),而不是null通过将其作为参数传递orElse


我更喜欢您的第一个解决方案。您将如何改进您的Pipe类以适应orElse功能,以便我可以在orElse方法内部传递任意非空对象?
ThanosFisherman

@ThanosFisherman我已经将该orElse方法添加到Pipe类中。
Helder Pereira



6

Java没有确切的语法,但是从JDK-8开始,我们拥有Optional API,可以使用各种方法。因此,使用空条件运算符的C#版本:

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

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

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

如果有的话persongetNamegetGivenName为null,则返回null。


2

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

这是H-MAN解决方案的一种变体,但它使用带有多个参数的重载方法来处理多个步骤,而不是catch NullPointerException

即使我认为该解决方案很酷,但我认为我更喜欢Helder Pereira的秒,因为它不需要任何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; }
}

1

我不确定这是否行得通。如果说人员引用为空,那么运行时将用它替换什么?一个新人?这将要求此人具有您在这种情况下期望的一些默认初始化。您可以避免使用null引用异常,但是如果您不打算针对这些类型的设置进行计划,则仍然会出现无法预测的行为。

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

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

您还可以使用try-catch:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}

1
“运行时将用什么替换它?” ...阅读该问题可能会有所帮助:-P会将其替换为null。通常,这个想法似乎是person..getName如果person为null则为null,否则为person.getName。因此,这很像在所有示例中将“”替换为null。
subsub

1
还可能引发NullReferenceException,getName()否则getGivenName()您将不知道是否只是为所有情况返回一个空字符串。
Jimmy T.

1
在Java中,它是NullPointerException。
Tuupertunut

在C#中,person?.getName()?.getGivenName() ?? ""等效于您的第一个示例,不同之处在于,如果getGivenName()返回null,它仍然会给出""
Austin_Anderson

0

有了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对整个“可选”业务存在严重怀疑。好像是个讨厌的骇客。
ggb667 2014年

3
Elvis操作员提议的全部目的是一站式完成。这种方法并不比上面的“ if(!= null)方法更好。实际上,我认为这很糟糕,因为它不是直接的方法。此外,您还应避免由于开销而
引发

就像@Darron在另一个答案中所说的那样,这里也适用:“这种样式的问题是NullPointerException可能不是您期望的那样。因此,它可能隐藏了一个真正的bug。”
Helder Pereira 2015年

0

如果有人正在寻找旧的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;
    }

}


0

由于除非您安装的OS> = 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;
        }
    }
}

-4

如果这不是您的性能问题,则可以编写

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

8
这种样式的问题在于NullPointerException可能不是您期望的那样。因此,它可能隐藏了一个真正的错误。
达伦(Darron)2010年

2
@Darron,能否举一个您所写的可以将NPE扔掉的吸气剂的例子,以及您如何以不同的方式处理它?
彼得·劳瑞

2
您可以将其运行到一个单独的变量中,然后像往常一样使用if进行测试。该运算符背后的想法是消除这种丑陋。Darron是正确的,您的解决方案可能会隐藏并丢弃您要抛出的异常。例如,如果getName()您在内部抛出了一个您不想扔掉的异常。
Mike Miller 2010年

2
也不例外,它必须是NullPointerException。您试图保护自己免受尚未开始解释的情况,在实际应用程序中可能会发生这种情况。
彼得·劳瑞

1
在真实的应用程序中,Person可能是访问数据库或某些非堆内存的代理,并且某处可能存在错误……不是很现实,但是,彼得,我敢打赌,您从未像上面那样写过一段代码。
maaartinus 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.