Java 空检查链与捕获NullPointerException

Java 空检查链与捕获NullPointerException,java,exception,nullpointerexception,null,custom-error-handling,Java,Exception,Nullpointerexception,Null,Custom Error Handling,web服务返回一个巨大的XML,我需要访问它的深层嵌套字段。例如: return wsObject.getFoo().getBar().getBaz().getInt() public static class MyFoo { private int barBazInt; public MyFoo(Foo foo) { this.barBazInt = parseBarBazInt(); } public int getBarBazInt(

web服务返回一个巨大的XML,我需要访问它的深层嵌套字段。例如:

return wsObject.getFoo().getBar().getBaz().getInt()
public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}
问题是
getFoo()
getBar()
getBaz()
都可能返回
null

但是,如果我在所有情况下都检查
null
,代码就会变得非常冗长且难以阅读。此外,我可能会错过一些字段的检查

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
可以写吗

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

或者这会被视为反模式吗?

NullPointerException是一个运行时异常,因此一般不建议捕获它,而是避免它

无论您想在哪里调用该方法,都必须捕获异常(否则它将向上传播)。然而,如果在您的例子中,您可以继续使用值为-1的结果,并且您确信它不会传播,因为您没有使用任何可能为null的“片段”,那么我认为捕获它是正确的

编辑:


我同意@xenteros后面的说法,最好启动您自己的异常,而不是返回-1。例如,您可以将其称为
InvalidXMLException

创建您自己的异常是值得考虑的。我们称之为MyOperationFailedException。您可以抛出它,而不是返回一个值。结果将是相同的-您将退出该函数,但不会返回硬编码值-1,这是Java反模式。在Java中,我们使用异常

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}
编辑:

根据评论中的讨论,让我补充一下我以前的想法。在这段代码中有两种可能。一个是您接受null,另一个是,这是一个错误


如果这是一个错误并且发生了,那么当断点不够时,您可以使用其他结构来调试代码


如果可以接受,您就不关心这个空值出现在哪里。如果你这样做了,你肯定不应该把这些请求链起来。

如果效率是个问题,那么就应该考虑“捕获”选项。 如果“catch”无法使用,因为它会传播(如“SCouto”所述),那么使用局部变量避免多次调用方法
getFoo()
getBar()
getBaz()
,,我建议考虑。您可以为每个异常构建包含详细消息的链,如

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");
我建议您不要使用特殊的返回值,如
-1
。这不是Java风格。Java设计了异常机制来避免这种来自C语言的老式方法


抛出
NullPointerException
也不是最好的选择。您可以提供自己的异常(选中它以保证用户可以处理它,或者不选中它以更简单的方式处理它),或者使用您正在使用的XML解析器中的特定异常。

给出的答案似乎与其他所有异常都不同

我建议您在
if
s中检查
NULL

原因:

我们不应该给我们的程序留下任何崩溃的机会。 空指针由系统生成系统的行为 无法预测生成的异常。你不应该离开家 当您已经有了一种处理方法时,程序就掌握在系统手中 这是你自己的。并将异常处理机制用于额外的安全性

为了使代码易于阅读,请尝试以下检查条件:

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();
编辑:

这里需要存储这些值
wsObject.getFoo()
, 中的wsObject.getFoo().getBar(),
wsObject.getFoo().getBar().getBaz()
一些变量。我不这样做,因为我不知道回报 功能的类型


如有任何建议,我们将不胜感激

我的答案与@janki的答案几乎相同,但我想稍微修改一下代码片段,如下所示:

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

如果对象有可能为空,您也可以为
wsObject
添加空检查。

不要捕获
NullPointerException
。你不知道它是从哪里来的(我知道这在你的情况下是不可能的,但可能是别的什么东西扔的),而且速度很慢。 您希望访问指定的字段,为此,每个其他字段都必须不为null。这是检查每个字段的完美理由。我可能会在一个if中检查它,然后创建一个可读性方法。正如其他人指出的那样,返回-1是一个非常古老的学校,但我不知道你是否有理由这样做(例如,与另一个系统交谈)


编辑:如果它违反了德米特定律,这是有争议的,因为WsObject可能只是一个数据结构(检查)。

捕获
空点异常
是一件非常有问题的事情,因为它们几乎可以在任何地方发生。很容易从一个bug那里得到一个,意外地捕捉到它,然后像一切正常一样继续,从而隐藏了一个真正的问题这是一个非常棘手的问题,因此最好完全避免。(例如,考虑自动取消对空
整数的装箱)

我建议你改用这门课。当您想要处理存在或不存在的值时,这通常是最好的方法

使用它,您可以编写如下代码:

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}
public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

为什么不是可选的? 我能想到的不使用
Optional
的唯一原因是,这是否在代码的真正性能关键部分,以及垃圾收集开销是否是一个问题。这是因为每次执行代码时都会分配一些
可选的
对象,VM可能无法优化这些对象。在这种情况下,您的原始if测试可能会更好。

正如评论中所指出的

以下声明违反了

您需要的是
int
an
wsObject.getFoo().getBar().getBaz().getInt()
Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();
public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}
wsObject.getFoo().getBar().getBaz().getInt();
void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}
static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}
  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}
package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}
package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}
Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();
final String name =  ENGINE_NAME.get(firstCar);
private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}
public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}
return wsObject.getFooBarBazInt();
class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}