Лучший способ избежать нуля во вложенных данных с Java 7 - PullRequest
1 голос
/ 05 февраля 2020

Мне нужно проанализировать огромный поток данных, который часто включает в себя неполные данные. В настоящее время код завален нулевыми проверками на нескольких уровнях, поскольку на любом уровне могут быть неполные данные.

Так, например, мне, возможно, придется получить: Model.getDestination().getDevice().getName()

Я попытался создать метод, чтобы попытаться свести нулевые проверки к одному методу, в котором я ввожу:

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

этот метод завершается ошибкой, поскольку он оценивает все параметры перед отправкой, а не проверяет каждый из них за раз, например Model.getDestination() != null && Model.getDestination().getDevice() != null && etc

, но есть ли способ, который я мог бы передать Model.getDestination().getDevice().getName() и выполнять проверку на каждом уровне без необходимости оценивать его или разбивать на части перед тем, как я его пройду?

Что я действительно хочу сделать, так это, если существует нулевое / нульлексное восприятие, которое должно спокойно возвращаться "" и продолжаю обрабатывать входящие данные

Я знаю, что есть способы сделать это элегантно в Java 8, но я застрял с Java 7

Ответы [ 4 ]

0 голосов
/ 05 февраля 2020

Я боролся с подобной проблемой с глубоко вложенными структурами, и если бы у меня была возможность ввести дополнительные структуры просто для навигации по базовым данным, я думаю, я сделал это.

Это был C#, который в то же время имеет навигацию сохранения / оператор Элвиса, которого мы будем ждать напрасно с Java (предлагается для Java 7, но отбрасывается . Groovy кстати.) Также похоже, что есть аргументы против использования Элвиса , даже если он у вас есть). Также лямбды (и методы расширения) ничего не улучшали. Также любой другой подход был дискредитирован как уродливый в других постах здесь.

Поэтому я предлагаю вторичную структуру исключительно для навигации, каждый элемент с методом getValue () для доступа к исходной структуре (также ярлыки, предложенные @ Майкл с нетерпением жду добавления этого пути). Позволяет вам сохранять навигацию без сохранения, как это:

 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;
        }
    }

И вторичными структурами с целью сохранения навигации.

1016 * Очевидно, что это спорно, так как вы всегда можете получить доступ к исходной структуре непосредственно и запустить в NPE. Но с точки зрения читабельности, возможно, я бы все-таки взял это, особенно для больших структур, где куст if или опций действительно является бельмом на глазу (что важно, если вам нужно сказать, какие бизнес-правила на самом деле были реализованы здесь).

Аргументу памяти / скорости можно противостоять, используя только один объект навигации для каждого типа и перенастраивая их внутренние компоненты для оценки базовых объектов во время навигации.

    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;
        }

    }
0 голосов
/ 05 февраля 2020

Вариант 1 - если заявление

Вы уже предоставили его в своем вопросе. Я думаю, что использование am if statement, как показано ниже, вполне приемлемо:

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

Вариант 2 - проверка javax и проверка результата - перед отправкой

Вы можете использовать javax validation.

См .: https://www.baeldung.com/javax-validation

Вы можете пометить поля, которые вы хотите, с помощью @NotNull.

Затем вы можете использовать programmati c Проверка.

Вы можете проверить результат проверки, чтобы увидеть, есть ли проблема.

Пример:

Итак, в вашем классе вы должны сделать:

@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()); 
}

Вариант 3 - из Nullable и Maps (если у вас есть Java 8)

Я беру это из https://softwareengineering.stackexchange.com/questions/255503/null-checking-whilst-navigating-object-hierarchies. Это очень похоже на ваш вопрос.

import java.util.Optional;

Optional.fromNullable(model)
               .map(Model::getDestination)
               .map(Lounge::getDevice)
               .ifPresent(letter -> .... do what you want ...);

Вариант 4 - Просто используйте try / catch

Каждый ненавидит этот вопрос из-за медлительности исключения.

0 голосов
/ 05 февраля 2020

Итак, вы хотите упростить Model.getDestination().getDevice().getName(). Во-первых, я хочу перечислить несколько вещей, которые не следует делать: не используйте исключения. Не пишите метод IsValid, потому что он просто не работает, потому что все функции (или методы) строги в Java: это означает, что каждый раз, когда вы вызываете функцию, все аргументы оцениваются перед их передачей к функции.

В Swift я бы просто написал let name = Model.getDestination()?.getDevice()?.getName() ?? "". В Haskell это будет похоже на name <- (destination >>= getDevice >>= getName) <|> Just "" (в предположении монады Maybe). И эта семантика отличается от кода 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.getDestination().getDevice().getName(), или если какой-либо из них вызовы метода возвращают ноль, он устанавливает имя в "". Я думаю, что правильность важнее, чем удобочитаемость, особенно для производственных приложений (и даже, например, кода IMHO). Вышеприведенный код Swift или Haskell эквивалентен этому Java коду.

Если у вас есть производственное приложение, я думаю, что нечто подобное - это то, что вы уже делаете, потому что все, что принципиально отличается от этого, подвержено ошибкам.

Каждое лучшее решение должно предоставлять одну и ту же семантику, и оно НЕ ДОЛЖНО вызывать какие-либо методы (getDestination, getDevice, getName) более одного раза.

Тем не менее, я не думаю, что вы можете значительно упростить код с помощью Java 7.

Что вы можете сделать, конечно, сократить цепочки вызовов: например, вы может создать метод getDeviceName() на Destination, если вам часто нужна эта функциональность. Если это сделает код более читабельным, зависит от конкретной ситуации.

У принуждения к кодированию на этом низком уровне также есть свои преимущества: вы можете сделать общее исключение подвыражения, и вы увидите его преимущества, потому что это сделает код короче. Например, если у вас есть:

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 строки, тогда как первый фрагмент имеет только две строки. Но если вы развернете два фрагмента для включения нулевых проверок, вы увидите, что вторая версия на самом деле намного короче. (Я не делаю это сейчас, потому что я ленивый.)

Поэтому (в отношении необязательных цепочек) Java 7 сделает код кодера, учитывающего производительность, лучше, в то время как многие другие языки высокого уровня создают стимулы для создания медленного кода. (Конечно, вы можете также выполнять обычное исключение подвыражений на языках более высокого уровня (и, вероятно, вам следует), но, по моему опыту, большинство разработчиков более неохотно делают это на языках высокого уровня. Тогда как в Ассемблере все оптимизировано, потому что часто повышается производительность означает, что вам нужно писать меньше кода, а код, который вы пишете, легче понять.)

Одним словом, мы все будем использовать языки со встроенной необязательной цепочкой, и мы все будем использовать это ответственно , без проблем с производительностью и условиями гонки.

0 голосов
/ 05 февраля 2020

Вы можете использовать try-catch. Поскольку в вашем случае обработка не требуется, например

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

}catch(Exception e){
    //do nothing
}

В качестве альтернативы вы можете улучшить свой метод isValid, передав только Model object

boolean isValid(Model model){
    return (model != null && model.getDestination() != null && model.getDestination().getDevice() != null && model.getDestination().getDevice().getName() != null)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...