Cadeia de verificação nula vs captura NullPointerException

118

Um serviço da web retorna um XML enorme e preciso acessar campos profundamente aninhados dele. Por exemplo:

return wsObject.getFoo().getBar().getBaz().getInt()

O problema é que getFoo(), getBar(), getBaz()tudo pode retorno null.

No entanto, se eu verificar nullem todos os casos, o código se torna muito prolixo e difícil de ler. Além disso, posso perder as verificações de alguns dos campos.

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

É aceitável escrever

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

ou isso seria considerado um antipadrão?

David Frank
fonte
29
Eu não me importaria tanto com as nullverificações, pois wsObject.getFoo().getBar().getBaz().getInt()já é um cheiro de código. Leia o que é a "Lei de Demeter" e prefira refatorar seu código de acordo. Então, o problema com os nullcheques também desaparecerá. E pense em usar Optional.
Tom
9
Que tal usar o XPath e deixá-lo para sua avaliação?
Joop Eggen
15
Esse código provavelmente é gerado por wsdl2java, que não respeita a Lei de Demeter.
Adrian Cox

Respostas:

143

Pegar NullPointerExceptioné uma coisa realmente problemática, pois pode acontecer em quase qualquer lugar. É muito fácil pegar um de um bug, pegá-lo sem querer e continuar como se tudo estivesse normal, escondendo assim um problema real. É tão complicado lidar com isso, então é melhor evitar completamente. (Por exemplo, pense no desempacotamento automático de um nulo Integer.)

Eu sugiro que você use a Optionalclasse em vez disso. Muitas vezes, essa é a melhor abordagem quando você deseja trabalhar com valores que estão presentes ou ausentes.

Usando isso, você poderia escrever seu código assim:

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

Por que opcional?

Usar Optionals em vez de nullpara valores que podem estar ausentes torna esse fato muito visível e claro para os leitores, e o sistema de tipos garantirá que você não o esqueça acidentalmente.

Você também obtém acesso a métodos para trabalhar com esses valores de forma mais conveniente, como mape orElse.


Ausência é válida ou erro?

Mas também pense se é um resultado válido para os métodos intermediários retornarem nulo ou se isso é um sinal de erro. Se for sempre um erro, provavelmente é melhor lançar uma exceção do que retornar um valor especial, ou para os próprios métodos intermediários lançar uma exceção.


Talvez mais opcionais?

Se, por outro lado, os valores ausentes dos métodos intermediários forem válidos, talvez você também possa alternar para Optionals para eles?

Então você pode usá-los assim:

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

Por que não opcional?

A única razão que posso pensar para não usar Optionalé se isso é realmente uma parte crítica de desempenho do código e se a sobrecarga de coleta de lixo acabar sendo um problema. Isso ocorre porque alguns Optionalobjetos são alocados cada vez que o código é executado, e a VM pode não ser capaz de otimizá-los. Nesse caso, seus testes de if originais podem ser melhores.

Lii
fonte
Muito boa resposta. Deve ser mencionado que, se houver outras condições de falha possíveis e você precisar distingui-las, poderá usar em Tryvez de Optional. Embora não existe Tryna API do Java, há muitas libs proporcionando um, por exemplo javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org ou github.com/jasongoodwin/better-java-monads
Landei
8
FClass::getBaretc seria mais curto.
Boris the Spider
1
@BoristheSpider: Talvez um pouco. Mas geralmente prefiro lambdas a métodos refs porque geralmente os nomes das classes são muito mais longos e acho lambdas um pouco mais fáceis de ler.
Lii
6
@Lii é justo, mas observe que uma referência de método pode ser um pouco mais rápida, pois um lambda pode exigir construções de tempo de compilação mais complexas. O lambda exigirá a geração de um staticmétodo, que incorrerá em uma penalidade muito pequena.
Boris the Spider
1
@Lii Na verdade, acho que as referências de método são mais claras e descritivas, mesmo quando são um pouco mais longas.
shmosel
14

Eu sugiro considerar Objects.requireNonNull(T obj, String message). Você pode construir cadeias com uma mensagem detalhada para cada exceção, como

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

Eu sugiro que você não use valores de retorno especiais, como -1. Esse não é um estilo Java. Java projetou o mecanismo de exceções para evitar essa maneira antiquada que veio da linguagem C.

Jogar NullPointerExceptiontambém não é a melhor opção. Você poderia fornecer sua própria exceção (tornando-se verificado a garantia de que ele será tratado por um utilizador ou desmarcada para processá-lo em uma maneira mais fácil) ou usar uma exceção específica do analisador XML que você está usando.

Andrew Tobilko
fonte
1
Objects.requireNonNulleventualmente joga NullPointerException. Portanto, isso não torna a situação diferente dereturn wsObject.getFoo().getBar().getBaz().getInt()
Arka Ghosh
1
@ArkaGhosh, também evita muitos ifs como OP mostrou
Andrew Tobilko
4
Esta é a única solução sensata. Todos os outros aconselham o uso de exceções para controle de fluxo, que é um cheiro de código. Em uma nota lateral: eu considero o encadeamento de métodos feito pelo OP um cheiro também. Se ele trabalhasse com três variáveis ​​locais e os if's correspondentes, a situação seria muito mais clara. Além disso, acho que o problema é mais profundo do que apenas trabalhar em torno de um NPE: OP deve se perguntar por que os getters podem retornar null. O que significa nulo? Talvez algum objeto nulo fosse melhor? Ou travar em um getter com uma exceção significativa? Basicamente, tudo é melhor do que exceções para controle de fluxo.
Marius K.
1
O conselho incondicional de usar exceções para sinalizar a ausência de um valor de retorno válido não é muito bom. As exceções são úteis quando um método falha de uma maneira que é difícil para o chamador se recuperar e que é melhor tratada em uma instrução try-catch em alguma outra parte do programa. Para simplesmente sinalizar a ausência de um valor de retorno, é melhor usar a Optionalclasse, ou talvez retornar um anulávelInteger
Lii
6

Assumindo que a estrutura de classes está realmente fora de nosso controle, como parece ser o caso, acho que pegar o NPE como sugerido na pergunta é de fato uma solução razoável, a menos que o desempenho seja uma preocupação importante. Uma pequena melhoria pode ser envolver a lógica de jogar / pegar para evitar confusão:

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

Agora você pode simplesmente fazer:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
shmosel
fonte
return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");não fornece um erro no tempo de compilação que pode ser problemático.
Philippe Gioseffi
5

Como já apontado por Tom no comentário,

A seguinte declaração desobedece a Lei de Deméter ,

wsObject.getFoo().getBar().getBaz().getInt()

O que você quer é inte pode obtê-lo Foo. A lei de Deméter diz: nunca fale com estranhos . Para o seu caso, você pode ocultar a implementação real sob o capô de Fooe Bar.

Agora, você pode criar método Foopara buscar inta partir Baz. Em última análise, Footeremos Bare Barpodemos acessar Intsem expor Bazdiretamente a Foo. Portanto, as verificações de nulos provavelmente são divididas em classes diferentes e apenas os atributos necessários serão compartilhados entre as classes.

CoderCroc
fonte
4
É discutível se ele desobedece a Lei de Demeter, uma vez que o WsObject provavelmente é apenas uma estrutura de dados. Veja aqui: stackoverflow.com/a/26021695/1528880
DerM de
2
@DerM Sim, isso é possível, mas como OP já tem algo que analisa seu arquivo XML, ele também pode pensar em criar classes de modelo adequadas para as tags necessárias, para que a biblioteca de análise possa mapeá-las. Então, essas classes de modelo contêm a lógica para uma nullverificação de suas próprias subtags.
Tom
4

Minha resposta vai quase na mesma linha que @janki, mas eu gostaria de modificar o snippet de código ligeiramente como abaixo:

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;

Você também pode adicionar uma verificação de nulo para wsObject, se houver alguma chance desse objeto ser nulo.

Arka Ghosh
fonte
4

Você diz que alguns métodos "podem retornar null", mas não diz em que circunstâncias eles retornam null. Você diz que pega o, NullPointerExceptionmas não diz por que o pega. Essa falta de informação sugere que você não tem um entendimento claro do que servem as exceções e por que são superiores à alternativa.

Considere um método de classe destinado a realizar uma ação, mas o método não pode garantir que executará a ação, devido a circunstâncias além de seu controle (o que é de fato o caso para todos os métodos em Java ). Chamamos esse método e ele retorna. O código que chama esse método precisa saber se foi bem-sucedido. Como pode saber? Como pode ser estruturado para lidar com as duas possibilidades, de sucesso ou fracasso?

Usando exceções, podemos escrever métodos que têm sucesso como uma pós-condição . Se o método retornar, foi bem-sucedido. Se ele lançar uma exceção, ele falhou. Esta é uma grande vitória para maior clareza. Podemos escrever código que processa claramente o caso normal de sucesso e mover todo o código de tratamento de erros para catchcláusulas. Freqüentemente, acontece que os detalhes de como ou por que um método não teve êxito não são importantes para o chamador, portanto, a mesma catchcláusula pode ser usada para lidar com vários tipos de falha. E muitas vezes acontece que um método não precisa capturar exceções em tudo , mas pode apenas permitir que eles se propaguem para o seu interlocutor. As exceções devido a bugs do programa estão nessa última classe; poucos métodos podem reagir apropriadamente quando há um bug.

Então, esses métodos que retornam null.

  • Um nullvalor indica um bug em seu código? Em caso afirmativo, você não deve capturar a exceção. E seu código não deve tentar adivinhar a si mesmo. Basta escrever o que é claro e conciso, pressupondo que funcionará. Uma cadeia de chamadas de método é clara e concisa? Depois é só usá-los.
  • Um nullvalor indica uma entrada inválida para o seu programa? Em caso afirmativo, a não NullPointerExceptioné uma exceção apropriada para lançar, porque convencionalmente é reservado para indicar bugs. Você provavelmente deseja lançar uma exceção personalizada derivada de IllegalArgumentException(se quiser uma exceção não verificada ) ou IOException(se quiser uma exceção marcada). É necessário que seu programa forneça mensagens de erro de sintaxe detalhadas quando houver entrada inválida? Nesse caso, verificar cada método para um nullvalor de retorno e, em seguida, lançar uma exceção de diagnóstico apropriada é a única coisa que você pode fazer. Se o seu programa não precisar fornecer diagnósticos detalhados, encadear as chamadas de método, capturar qualquer uma NullPointerExceptione lançar sua exceção personalizada é mais claro e conciso.

Uma das respostas afirma que as chamadas de método encadeadas violam a Lei de Demeter e, portanto, são ruins. Essa afirmação está errada.

  • Quando se trata de design de programas, não existem regras absolutas sobre o que é bom e o que é ruim. Existem apenas heurísticas: regras que estão corretas na maior parte do tempo (quase sempre). Parte da habilidade de programação é saber quando é normal quebrar esse tipo de regra. Portanto, uma afirmação concisa de que "isso é contra a regra X " não é realmente uma resposta. Esta é uma das situações em que a regra deve ser quebrada?
  • A Lei de Demeter é realmente uma regra sobre API ou design de interface de classe. Ao projetar classes, é útil ter uma hierarquia de abstrações. Você tem classes de baixo nível que usam os primitivos de linguagem para realizar operações diretamente e representar objetos em uma abstração que é de nível mais alto que os primitivos de linguagem. Você tem classes de nível médio que delegam às classes de nível inferior e implementam operações e representações em um nível mais alto do que as classes de nível inferior. Você tem classes de alto nível que delegam às classes de nível médio e implementam operações e abstrações de nível ainda mais alto. (Eu falei sobre apenas três níveis de abstração aqui, mas mais são possíveis). Isso permite que seu código se expresse em termos de abstrações apropriadas em cada nível, ocultando assim a complexidade. A justificativa para a Lei de Deméteré que se você tem uma cadeia de chamadas de método, isso sugere que você tem uma classe de alto nível alcançando através de uma classe de nível médio para lidar diretamente com detalhes de baixo nível e, portanto, que sua classe de nível médio não forneceu uma operação abstrata de nível médio que a classe de alto nível precisa. Mas parece que essa não é a situação que você tem aqui: você não projetou as classes na cadeia de chamadas de método, elas são o resultado de algum código de serialização XML gerado automaticamente (certo?) E a cadeia de chamadas não é decrescente por meio de uma hierarquia de abstração, porque o XML desserializado está todo no mesmo nível da hierarquia de abstração (certo?)?
Raedwald
fonte
3

Para melhorar a legibilidade, você pode querer usar várias variáveis, como

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();
JimmyB
fonte
Isso é muito menos legível na minha opinião. Ele confunde o método com um monte de lógica de verificação de nulos que é completamente irrelevante da lógica real do método.
Desenvolvedor
2

Não pegue NullPointerException. Você não sabe de onde vem (eu sei que não é provável no seu caso, mas talvez outra coisa o tenha jogado) e é lento. Você deseja acessar o campo especificado e, para isso, todos os outros campos não devem ser nulos. Esta é uma razão válida perfeita para verificar todos os campos. Eu provavelmente verificaria em um if e, em seguida, criaria um método para facilitar a leitura. Como outros apontaram, já retornando -1 é muito oldschool mas não sei se você tem um motivo para isso ou não (por exemplo, falando com outro sistema).

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

Edit: É discutível se ele desobedece a Lei de Demeter uma vez que o WsObject é provavelmente apenas uma estrutura de dados (verifique https://stackoverflow.com/a/26021695/1528880 ).

DerM
fonte
2

Se você não quiser refatorar o código e puder usar o Java 8, é possível usar referências de método.

Uma demonstração simples primeiro (desculpem as classes internas estáticas)

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

Resultado

241
4

nulo
2

A interface Getteré apenas uma interface funcional, você pode usar qualquer equivalente.
GetterResultclasse, acessadores retirados para maior clareza, contêm o resultado da cadeia getter, se houver, ou o índice do último getter chamado.

O método getterChainé um pedaço de código simples e padronizado, que pode ser gerado automaticamente (ou manualmente quando necessário).
Estruturei o código de forma que o bloco de repetição seja evidente.


Essa não é uma solução perfeita, pois você ainda precisa definir uma sobrecarga getterChainpor número de getters.

Em vez disso, eu refatoraria o código, mas se não puder e você se descobrir usando longas cadeias de getter, muitas vezes, você pode considerar construir uma classe com sobrecargas que levam de 2 a, digamos, 10 getters.

Margaret Bloom
fonte
2

Como outros já disseram, respeitar a Lei de Deméter é definitivamente parte da solução. Outra parte, sempre que possível, é alterar esses métodos encadeados para que não possam retornar null. Você pode evitar o retorno nullretornando um vazio String, um vazio Collectionou algum outro objeto fictício que significa ou faz o que quer que o chamador faça null.

Kevin Krumwiede
fonte
2

Eu gostaria de adicionar uma resposta que enfoque o significado do erro . A exceção nula em si não fornece nenhum erro completo de significado. Portanto, eu aconselho a evitar lidar com eles diretamente.

Existem milhares de casos em que seu código pode dar errado: não é possível conectar ao banco de dados, Exceção de E / S, Erro de rede ... Se você lidar com eles um por um (como a verificação de nulo aqui), seria muito complicado.

No código:

wsObject.getFoo().getBar().getBaz().getInt();

Mesmo quando você sabe qual campo é nulo, não tem ideia do que está errado. Talvez Bar seja nulo, mas é esperado? Ou é um erro de dados? Pense nas pessoas que leem seu código

Como na resposta do xenteros, eu proporia o uso de exceção desmarcada personalizada . Por exemplo, nesta situação: Foo pode ser nulo (dados válidos), mas Bar e Baz nunca devem ser nulos (dados inválidos)

O código pode ser reescrito:

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"
    }
}
Hoàng Long
fonte
Exceções não verificadas indicam que o programador está fazendo mau uso da API. Problemas externos como "não é possível conectar ao banco de dados, Exceção de E / S, Erro de rede" devem ser indicados por exceções marcadas.
Kevin Krumwiede
Realmente depende da necessidade de quem liga. A exceção verificada ajuda porque força você a processar o erro. Porém, em outros casos, não é necessário e pode poluir o código. Por exemplo, você tem uma IOException em sua camada de dados, você vai jogá-la na camada de apresentação? Isso significa que você tem que capturar a exceção e lançar novamente a cada chamador. Prefiro envolver a IOException por uma BusinessException personalizada, com uma mensagem relevante, e deixá-la aparecer no stacktrace até que um filtro global a capture e exiba a mensagem ao usuário.
Hoàng Long
Os chamadores não precisam capturar e relançar as exceções verificadas, apenas declare que foram lançadas.
Kevin Krumwiede
@KevinKrumwiede: você está correto, só precisamos declarar a exceção a ser lançada. Ainda precisamos declarar. Edit: Dando uma segunda olhada nele, há muitos debates sobre os usos de exceção verificados e não verificados (por exemplo: programmers.stackexchange.com/questions/121328/… ).
Hoàng Long
2

NullPointerException é uma exceção de tempo de execução, de modo geral, não é recomendado capturá-la, mas evitá-la.

Você terá que capturar a exceção sempre que quiser chamar o método (ou ele se propagará pela pilha). No entanto, se no seu caso você puder continuar trabalhando com aquele resultado com valor -1 e tiver certeza de que ele não se propagará porque você não está usando nenhuma das "peças" que podem ser nulas, então me parece correto apanha isto

Editar:

Eu concordo com a resposta posterior de @xenteros, será melhor iniciar sua própria exceção em vez de retornar -1, você pode chamá-la, InvalidXMLExceptionpor exemplo.

SCouto
fonte
3
O que você quer dizer com "não importa se você pegá-lo, ele pode se propagar para outras partes do código"?
Hulk de
Se o nulo estiver nesta frase wsObject.getFoo () E nas partes posteriores do código, você executa novamente essa consulta ou usa wsObject.getFoo (). GetBar () (por exemplo), ele gerará novamente um NullPointerException.
SCouto de
Essa é uma expressão incomum para "Você terá que capturar a exceção sempre que quiser chamar o método (ou ele se propagará pela pilha)." se eu entendi corretamente. Eu concordo com isso (e isso pode ser um problema), apenas acho o texto confuso.
Hulk
Vou consertar, desculpe, o inglês não é meu primeiro idioma, então isso pode acontecer às vezes :) Obrigado
SCouto
2

Tenho acompanhado este post desde ontem.

Estive comentando / votando os comentários que dizem que pegar NPE é ruim. É aqui que tenho feito isso.

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

Resultado

3.216

0,002

Eu vejo um vencedor claro aqui. Ter if checks é muito menos caro do que pegar uma exceção. Eu vi essa maneira de fazer o Java-8. Considerando que 70% dos aplicativos atuais ainda são executados em Java-7, estou adicionando esta resposta.

Conclusão Para qualquer aplicação de missão crítica, o manuseio do NPE é caro.

Novo usuário
fonte
Três segundos extras em um milhão de solicitações no pior caso são mensuráveis, mas raramente seria um obstáculo, mesmo em "aplicativos de missão crítica". Existem sistemas em que adicionar 3,2 microssegundos a uma solicitação é um grande negócio e, se você tiver esse sistema, pense cuidadosamente sobre as exceções. Mas chamar um serviço da web e desserializar sua saída, de acordo com a pergunta original, provavelmente leva muito mais tempo do que isso, e se preocupar com o desempenho do tratamento de exceções não vem ao caso.
Jeroen Mostert
@JeroenMostert: 3 segundos por cheque / milhão. Portanto, o número de verificações aumentará o custo
Novo usuário
Verdade. Mesmo assim, eu ainda consideraria um caso de "perfil primeiro", no entanto. Você precisaria de mais de 300 cheques em uma solicitação antes que ela demorasse um milissegundo a mais. As considerações de design pesariam em minha alma muito antes disso.
Jeroen Mostert
@JeroenMostert: :) Concordo! Eu gostaria de deixar o resultado para o programador e deixá-los fazer uma ligação!
Novo usuário
1

Se a eficiência for um problema, a opção 'catch' deve ser considerada. Se 'pegar' não pode ser usado porque iria propagar (como mencionado por 'SCouto'), em seguida, usar variáveis locais para evitar várias chamadas para métodos getFoo(), getBar()e getBaz().

JEP
fonte
1

Vale a pena considerar a criação de sua própria exceção. Vamos chamá-lo de MyOperationFailedException. Você pode jogá-lo em vez de retornar um valor. O resultado será o mesmo - você sairá da função, mas não retornará o valor -1 embutido em código, que é o antipadrão Java. Em Java, usamos exceções.

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

EDITAR:

De acordo com a discussão nos comentários, deixe-me acrescentar algo aos meus pensamentos anteriores. Neste código, existem duas possibilidades. Uma é que você aceita nulo e a outra é que é um erro.

Se for um erro e ocorrer, você pode depurar seu código usando outras estruturas para fins de depuração quando os pontos de interrupção não são suficientes.

Se for aceitável, você não se importa onde esse nulo apareceu. Se você fizer isso, definitivamente não deve encadear essas solicitações.

xenteros
fonte
2
Você não acha que é uma má ideia suprimir a exceção? Em tempo real, se perdermos o traço de uma exceção, é uma verdadeira dor no fundo descobrir o que diabos está acontecendo! Eu sempre sugeriria não usar o encadeamento. O segundo problema que vejo é: este código não pode conceder no momento, qual dos resultados foi nulo.
Novo usuário em
Não, sua exceção pode ter uma mensagem que certamente apontaria o lugar onde foi lançada. Eu concordo que o encadeamento não é a melhor solução :)
xenteros
3
Não, diria apenas sobre o número da linha. Portanto, qualquer uma das chamadas na cadeia pode levar a uma exceção.
Novo usuário de
"Se for um erro e ocorrer, você pode depurar seu código" - não em produção. Prefiro saber O QUE falhou quando tudo o que tenho é um registro do que tentar adivinhar o que aconteceu para causar a falha. Com esse aviso (e esse código), tudo o que você realmente sabe é que uma das 4 coisas era nula, mas não qual ou por quê.
VLAZ de
1

O método que você tem é longo, mas muito legível. Se eu fosse um novo desenvolvedor chegando à sua base de código, poderia ver o que você estava fazendo rapidamente. A maioria das outras respostas (incluindo capturar a exceção) não parece estar tornando as coisas mais legíveis e algumas estão tornando-as menos legíveis na minha opinião.

Considerando que você provavelmente não tem controle sobre a origem gerada e presumindo que você realmente só precisa acessar alguns campos profundamente aninhados aqui e ali, eu recomendaria agrupar cada acesso profundamente aninhado com um método.

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

Se você se pegar escrevendo muitos desses métodos ou se sentir tentado a tornar esses métodos estáticos públicos, eu criaria um modelo de objeto separado, aninhado como você gostaria, com apenas os campos de seu interesse, e converteria da web modelo de objeto de serviços para seu modelo de objeto.

Quando você está se comunicando com um serviço da Web remoto, é muito comum ter um "domínio remoto" e um "domínio de aplicativo" e alternar entre os dois. O domínio remoto é frequentemente limitado pelo protocolo da web (por exemplo, você não pode enviar métodos auxiliares para frente e para trás em um serviço RESTful puro e modelos de objetos profundamente aninhados são comuns para evitar várias chamadas de API) e, portanto, não são ideais para uso direto em seu cliente.

Por exemplo:

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

}
Ritmo
fonte
1
return wsObject.getFooBarBazInt();

aplicando a Lei de Demeter,

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;
    }
}
Khaled.K
fonte
0

Dando uma resposta que parece diferente de todas as outras.

Eu recomendo que você verifique NULLem ifs.

Razão:

Não devemos deixar uma única chance de nosso programa travar. NullPointer é gerado pelo sistema. O comportamento das exceções geradas pelo sistema não pode ser previsto . Você não deve deixar seu programa nas mãos do System quando já possui uma maneira de controlá-lo por conta própria. E coloque o mecanismo de tratamento de exceções para a segurança extra. !!

Para tornar seu código fácil de ler, tente verificar as condições:

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

EDITAR:

Aqui você precisa armazenar estes valores wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz()em algumas variáveis. Não estou fazendo isso porque não sei os tipos de retorno dessas funções.

Todas as sugestões serão apreciadas .. !!

Janki Gadhiya
fonte
Você considerou getFoo () uma operação que consome muito tempo? Você deve armazenar os valores retornados em variáveis, no entanto, é um desperdício de memória. Seu método é perfeito para programação C.
xenteros de
mas às vezes é melhor atrasar 1 milissegundo do que o programa travando @xenteros .. !!
Janki Gadhiya
getFoo () pode obter um valor de um servidor localizado em outro continente. Pode durar a qualquer hora: minutos / horas ...
xenteros
wsObjectestará contendo o valor retornado do Webservice .. !! O serviço já será chamado e wsObjectobterá XMLdados longos como uma resposta do webservice .. !! Portanto, não há nada como o servidor localizado em outro continente porque getFoo()é apenas um elemento que obtém o método getter e não uma chamada de serviço da Web .. !! @xenteros
Janki Gadhiya
1
Bem, pelos nomes dos getters, eu presumiria que eles retornariam objetos Foo, Bar e Baz: P Considere também remover a segurança dupla mencionada de sua resposta. Não acho que forneça nenhum valor real além da poluição do código. Com variáveis ​​locais sãs e verificação de nulos, fizemos mais do que o suficiente para garantir a correção do código. Se uma exceção ocorrer, ela deve ser tratada como tal.
Marius K.
0

Eu escrevi uma classe chamada Snagque permite definir um caminho para navegar por uma árvore de objetos. Aqui está um exemplo de seu uso:

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

O que significa que a instância ENGINE_NAMEefetivamente chamaria Car?.getEngine()?.getName()a instância passada a ela e retornaria nullse alguma referência fosse retornada null:

final String name =  ENGINE_NAME.get(firstCar);

Não está publicado no Maven, mas se alguém achar útil, está aqui (sem garantia, é claro!)

É um pouco básico, mas parece funcionar. Obviamente, está mais obsoleto com as versões mais recentes do Java e outras linguagens JVM que suportam navegação segura ou Optional.

Rico
fonte