使用Java7避免嵌套数据中出现空值的更好方法

使用Java7避免嵌套数据中出现空值的更好方法,java,json,nullpointerexception,null,retrofit,Java,Json,Nullpointerexception,Null,Retrofit,我必须分析一个巨大的数据流,其中经常包含不完整的数据。目前,代码在多个级别上充斥着空检查,因为任何级别上都可能有不完整的数据 例如,我可能需要检索: Model.getDestination().getDevice().getName() 我尝试创建一个方法,尝试将空检查减少为一个方法,从而输入: IsValid(Model.getDestination()、Model.getDestination().getDevice()、Model.getDestination().getDevice()

我必须分析一个巨大的数据流,其中经常包含不完整的数据。目前,代码在多个级别上充斥着空检查,因为任何级别上都可能有不完整的数据

例如,我可能需要检索:
Model.getDestination().getDevice().getName()

我尝试创建一个方法,尝试将空检查减少为一个方法,从而输入:

IsValid(Model.getDestination()、Model.getDestination().getDevice()、Model.getDestination().getDevice().getName())

此方法失败,因为它在发送所有参数之前对其进行求值,而不是像这样一次检查每个参数
Model.getDestination()!=null&&Model.getDestination().getDevice()!=空和等

但是有没有一种方法可以让我传入
Model.getDestination().getDevice().getName()
,然后在每个级别上进行检查,而不必在通过之前对其进行评估或拆分

我真正希望它做的是,如果存在null/nullexception,它应该悄悄地返回“”,并继续处理传入的数据


我知道在Java8中有很多方法可以优雅地做到这一点,但我还是坚持使用Java7

您可以使用
try catch
。因为在您的案例中不需要处理,比如

try{
    if(IsValid(Model.getDestination(), Model.getDestination().getDevice(), Model.getDestination().getDevice().getName())){

}catch(Exception e){
    //do nothing
}
或者,您可以通过只传递
模型
对象来改进
isValid
方法

boolean isValid(Model model){
    return (model != null && model.getDestination() != null && model.getDestination().getDevice() != null && model.getDestination().getDevice().getName() != null)
}
选项1-if语句 您已经在问题中提供了它。我认为像下面这样使用am
if语句是完全可以接受的:

Model.getDestination() != null && Model.getDestination().getDevice() != null && etc

选项2-javax验证和检查结果-发送前 您可以使用
javax验证

见:

您可以使用
@NotNull
注释所需的字段

然后可以使用编程验证

您可以检查验证结果以查看是否存在问题

示例:

所以在你的课堂上你会做:

@NotNull
Public String Destination;
您可以将对象馈送到验证程序:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

Set<ConstraintViolation<Model>> violations = validator.validate(Model);

for (ConstraintViolation<User> violation : violations) {
    log.error(violation.getMessage()); 
}

选项4-仅使用try/catch
由于异常的缓慢性,每个人都讨厌这一个。

所以您希望简化
模型。getDestination().getDevice().getName()
。首先,我想列出一些不应该做的事情:不要使用异常。不要编写
IsValid
方法,因为它根本不起作用,因为所有函数(或方法)在Java中都是严格的:这意味着每次调用函数时,所有参数都会在传递给函数之前进行求值

在Swift中,我只需编写
let name=Model.getDestination()?.getDevice()?.getName()??“”
。在Haskell中,它就像
name>=getDevice>=getName)只是“
(假设可能是monad)。这与Java代码的语义不同:

if(Model.getDestination() && Model.getDestination().getDevice() && Model.getDestination().getDevice().getName() {
    String name = Model.getDestination().getDevice().getName();
    System.out.println("We got a name: "+name);
}
因为这个代码段调用了
getDestination()
4次,
getDevice()
3次,
getName()
2次。这不仅仅意味着性能:1)它引入了竞争条件。2) 如果其中任何一种方法都有副作用,您不希望多次调用它们。3) 这使得调试变得更加困难

唯一正确的方法是这样:

Destination dest = Model.getDestination();
Device device = null;
String name = null;
if(dest != null) {
    device = dest.getDevice();
    if(device != null) {
        name = device.getName();
    }
}
if(name == null) {
    name = "";
}
 Model model = new Model(new Destination(null));

 Destination destination = model.getDestination().getValue(); // destination is not null
 Device device = model.getDestination().getDevice().getValue(); // device will be null, no NPE
 String name = destination.getDevice().getName().getValue(); // name will be null, no NPE

 NavDevice navDevice = model.getDestination().getDevice(); // returns an ever non-null NavDevice, not a Device
 String name = navDevice.getValue().getName(); // cause an NPE by circumventing the navigation structure
此代码将name设置为
Model.getDestination().getDevice().getName()
,或者如果这些方法调用中的任何一个返回null,则将name设置为“”。我认为正确性比可读性更重要,特别是对于生产应用程序(甚至对于示例代码IMHO)。上述Swift或Haskell代码相当于该Java代码

如果你有一个生产应用程序,我想你已经在做类似的事情了,因为所有与之根本不同的东西都很容易出错

每个更好的解决方案都必须提供相同的语义,并且不能多次调用任何方法(getDestination、getDevice、getName)。

也就是说,我不认为用Java7可以简化代码太多

当然,您可以做的是缩短调用链:例如,如果您经常需要此功能,您可以在
目的地
上创建一个方法
getDeviceName()
。这是否使代码更具可读性取决于具体情况

强制您在这个低级别上编码也有好处:您可以执行公共子表达式消除,并且您将看到它的优点,因为它将使代码更短。例如,如果您有:

String name1 = Model.getDevice().getConnection().getContext().getName();
String name2 = Model.getDevice().getConnection().getContext().getLabel();
您可以将它们简化为

Context ctx = Model.getDevice().getConnection().getContext();
String name1 = ctx.getName();
String name2 = ctx.getLabel();
第二个代码段有3行,而第一个代码段只有两行。但如果展开这两个代码段以包含空检查,您将看到第二个版本实际上要短得多。(我现在不做是因为我懒惰。)

因此(关于可选链接),Java7将使性能感知编码器的代码看起来更好,而更多的高级语言则会鼓励编写慢代码。(当然,您也可以在更高级的语言中消除公共子表达式(您可能应该这样做),但根据我的经验,大多数开发人员更不愿意使用高级语言。而在汇编程序中,一切都是优化的,因为更好的性能通常意味着您必须编写更少的代码,并且您编写的代码更容易理解。)


一句完美的话,我们都会使用内置可选链接的语言,并且我们都会负责任地使用它,而不会造成性能问题和竞争条件。

我也遇到了一个类似的问题,即深度嵌套的结构,如果我有机会引入额外的结构来导航底层数据,我想,我已经做到了

这就是C#,同时它有一个save导航/Elvis操作符,我们将用Java徒劳地等待它(建议用于Java7,但顺便说一句,Groovy有它)。看起来也有争论,即使你
 Model model = new Model(new Destination(null));

 Destination destination = model.getDestination().getValue(); // destination is not null
 Device device = model.getDestination().getDevice().getValue(); // device will be null, no NPE
 String name = destination.getDevice().getName().getValue(); // name will be null, no NPE

 NavDevice navDevice = model.getDestination().getDevice(); // returns an ever non-null NavDevice, not a Device
 String name = navDevice.getValue().getName(); // cause an NPE by circumventing the navigation structure
    class Destination {

        private final Device device;

        public Destination(Device device) {
            this.device = device;
        }

        public Device getDevice() {
            return device;
        }
    }

    class Device {

        private final String name;

        private Device(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
    class Model {

        private final Destination destination;

        private Model(Destination destination) {
            this.destination = destination;
        }

        public NavDestination getDestination() {
            return new NavDestination(destination);
        }
    }

    class NavDestination {

        private final Destination value;

        private NavDestination(Destination value) {
            this.value = value;
        }

        public Destination getValue() {
            return value;
        }

        public NavDevice getDevice() {
            return new NavDevice(value == null ? null : value.getDevice());
        }

    }

    class NavDevice {

        private final Device value;

        private NavDevice(Device value) {
            this.value = value;
        }

        public Device getValue() {
            return value;
        }

        public NavName getName() {
            return new NavName(value == null ? null : value.getName());
        }
    }

    class NavName {

        private final String value;

        private NavName(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }

    }