Pergunta

Como posso descobrir o que fez com que equals() retornasse falso?

Não estou perguntando sobre uma abordagem segura e sempre correta, mas sobre algo que ajude no processo de desenvolvimento.Atualmente eu tenho que entrar nas chamadas equals() (geralmente uma árvore delas) até que uma delas seja falsa, então entrar nela, até enjoar.

Pensei em usar o gráfico de objetos, enviá-lo para xml e comparar os dois objetos.No entanto, XMLEncoder requer construtores padrão, jibx requer pré-compilação, x-stream e API simples não são usados ​​em meu projeto.Não me importo de copiar uma única classe, ou mesmo um pacote, para minha área de teste e usá-la lá, mas importar um jar inteiro para isso simplesmente não vai acontecer.

Também pensei em construir um atravessador de gráfico de objetos, e ainda posso fazê-lo, mas odiaria começar a lidar com casos especiais (coleções ordenadas, coleções não ordenadas, mapas...)

Alguma ideia de como fazer isso?

Editar:Eu sei que adicionar potes é a maneira normal de fazer as coisas.Eu sei que os potes são unidades reutilizáveis.Porém, a burocracia necessária (no meu projeto) para isso não justifica os resultados - eu continuaria depurando e intervindo.

Foi útil?

Solução

Presumivelmente, não é uma comparação gráfica completa ...a menos que seus iguais incluam todas as propriedades de cada classe ...(você poderia tentar == :))

Tentar matchers de hamcrest - você pode compor cada matcher em um matcher "todos":

Matcher<MyClass> matcher = CoreMatchers.allOf(
  HasPropertyWithValue.hasProperty("myField1", getMyField1()),
  HasPropertyWithValue.hasProperty("myField2", getMyField2()));
if (!matcher.matches(obj)){
  System.out.println(matcher.describeFailure(obj));
  return false;
}
return true;

Ele dirá coisas como:'esperava que myField1 tivesse um valor de "valor", mas era "um valor diferente"'

Claro que você pode incorporar as fábricas estáticas.Isso é um pouco mais pesado do que usar apache-commonsEqualsBuilder, mas fornece uma descrição precisa do que exatamente falhou.

Você pode criar seu próprio matcher especializado para criar rapidamente essas expressões.Seria sensato copiar apache-commonsEqualsBuilder aqui.

A propósito, o jar básico do hamcrest tem 32K (incluindo a fonte!), Dando a você a opção de revisar o código e dizer aos seus chefes "Vou defender isso como meu próprio código" (que presumo ser o seu problema de importação).

Outras dicas

Você poderia usar aspectos para corrigir "igual" nas classes em seu gráfico de objeto e fazê-los registrar o estado do objeto em um arquivo quando retornarem falso.Para registrar o estado do objeto, você poderia usar algo como beanutils para inspecionar o objeto e despejá-lo.Esta é uma solução baseada em jar que pode ser facilmente usada em seu espaço de trabalho

Se a hierarquia dos objetos armazenados em sua árvore for simples o suficiente, você poderá colocar pontos de interrupção condicionais na implementação "igual" de suas classes, que só é acionada quando "igual" retorna falso, o que limitaria o número de vezes que você precisa entrar ...você pode usar isso sempre que tiver acesso ao depurador.Eclipse lida com isso muito bem.

Parece que você quer java-diff, ou algo parecido.

Ok, esta é uma maneira completamente bizarra de ver isso, mas que tal introduzir um novo método estático:

public static boolean breakableEquals(Object o1, Object o2)
{
    if (o1 == o2)
    {
        return true;
    }
    if (o1 == null || o2 == null)
    {
        return false;
    }
    // Don't condense this code!
    if (o1.equals(o2))
    {
        return true;
    }
    else
    {
        return false;
    }
}

Eu sei, a última parte parece louca...mas a diferença é que você pode colocar um ponto de interrupção no "retorno falso".Se você então usar breakableEquals em todas as suas comparações profundas de igualdade, você pode quebrar assim que atingir o primeiro "return false".

Isso não ajuda muito se você estiver comparando muitos valores primitivos, é certo...mas pode ser útil.Não posso dizer que já usei isso, mas não vejo por que não funcionaria.Será um pouco menos eficiente, é claro - portanto, se você estiver lidando com código de alto desempenho, poderá querer alterá-lo posteriormente.

Outra opção seria usar algo como:

boolean result = // comparison;
return result;

Supondo que seu IDE os suporte, você pode colocar um ponto de interrupção condicional na instrução return e definir a condição como "!result".

Mais uma opção:

public static boolean noOp(boolean result)
{
    return result;
}

Você pode então usar isso em comparações:

return Helpers.noOp(x.id == y.id) &&
       Helpers.noOp(x.age == y.age);

Espero que quando você não estiver depurando, isso seja otimizado pelo JIT - mas, novamente, você pode usar um ponto de interrupção condicional em noOp.Isso torna o código mais feio, infelizmente.

Resumidamente:não há soluções particularmente atraentes aqui, mas apenas algumas ideias que poder ajudar em determinadas situações.

Não me importo de copiar uma única classe, ou mesmo um pacote, na minha área de teste e usá -la lá, mas importar um frasco inteiro para isso simplesmente não vai acontecer.

Hum...o que?Adicionar um jar ao seu caminho de classe é, no mínimo, mais fácil e menos perturbador para o projeto do que copiar classes ou pacotes inteiros como código-fonte.

Quanto ao seu problema específico, você tem muitas classes diferentes que usam muitas propriedades diferentes para determinar a igualdade ou apenas possui um gráfico de objetos profundamente aninhados essencialmente das mesmas classes?Neste último caso, seria muito fácil apenas estruturar os métodos equals() para que você possa colocar pontos de interrupção nas instruções "return false".No primeiro caso, isso pode ser muito trabalhoso, suponho.Mas então, uma comparação baseada em XML também pode não funcionar, uma vez que mostrará diferenças entre objetos semanticamente iguais (por exemplo,Conjuntos e Mapas).

Dado que seu projeto não pode adicionar um jar, parece muito além de uma resposta SO fornecer uma implementação completa de uma solução que outros projetos exigem uma quantidade substancial de código para realizar (e incluir em um Jar para você).

Que tal uma solução sem código - pontos de interrupção condicionais no depurador?Você pode adicionar pontos de interrupção que disparam apenas se o método retornar falso e colocá-los em todas as classes relevantes.Sem pisar.

Eu sei que adicionar potes é a maneira normal de fazer as coisas.Eu sei que os potes são unidades reutilizáveis.Porém, a burocracia necessária (no meu projeto) para isso não justifica os resultados - eu continuaria depurando e intervindo.

Uma maneira de contornar isso é incluir uma biblioteca como Spring (que puxa muitos outros jars). Eu vi um projeto Spring que na verdade não usava nenhum jar Spring apenas para que pudessem usar qualquer jar que vem junto com ele.

commons-jxpath pode ser útil para examinar rapidamente a árvore de objetos.Não entendo completamente a questão da inclusão de jars, mas você pode simplesmente usá-los em seu próprio projeto em qualquer IDE que usar, que presumivelmente permite usar expressões durante a depuração.

Talvez isto artigo sobre rastreamento de método irá ajudá-lo.

Como funciona

Um carregador de classes personalizado lê o arquivo de classe e instrumenta cada método com código de rastreamento.O carregador de classes também adiciona um campo estático a cada classe.Este campo possui dois estados, 'ligado' e 'desligado'.O código de rastreamento verifica esse campo antes da impressão.As opções de linha de comando acessam e modificam esse campo estático para controlar a saída de rastreamento.

O exemplo de saída mostrado parece promissor para o seu problema, pois mostra o valor de retorno de alguns métodos (como isApplet):

isApplet=falso

Deve ser fácil identificar a classe exata que começou a retornar falso em iguais.Aqui está o exemplo completo de saída da página:

% java -jar /home/mike/java/trace.jar -classpath "/home/mike/jdk1.3/demo/jfc/SwingSet2/SwingSet2.jar" -exclude CodeViewer SwingSet2
|SwingSet2.()
|SwingSet2.
|SwingSet2.main([Ljava.lang.String;@1dd95c)
||isApplet(SwingSet2@3d12a6)
||isApplet=falso
||SwingSet2.createFrame(apple.awt.CGraphicsConfig@93537d)
||SwingSet2.createFrame=javax.swing.JFrame@cb01e3
||criarSplashScreen(SwingSet2@3d12a6)
|||createImageIcon(SwingSet2@3d12a6, "Splash.jpg", "Splash.accessible_description")
|||createImageIcon=javax.swing.ImageIcon@393e97
|||isApplet(SwingSet2@3d12a6)
|||isApplet=falso
|||getFrame(SwingSet2@3d12a6)
|||getFrame=javax.swing.JFrame@cb01e3
|||getFrame(SwingSet2@3d12a6)
|||getFrame=javax.swing.JFrame@cb01e3
||criarSplashScreen
.run(SwingSet2$1@fba2af)
..showSplashScreen(SwingSet2@3d12a6)
...isApplet(SwingSet2@3d12a6)
...isApplet=falso
..showSplashScreen
.correr
||inicializarDemo(SwingSet2@3d12a6)
|||criarMenus(SwingSet2@3d12a6)
||||getString(SwingSet2@3d12a6, "MenuBar.accessible_description")
|||||getResourceBundle(SwingSet2@3d12a6)
|||||getResourceBundle=java.util.PropertyResourceBundle@6989e
||||getString="Barra de menu de demonstração do Swing"

XMLEncoder codifica apenas propriedades de bean, enquanto equals pode, obviamente, funcionar em não-beans e em quaisquer campos internos.

Parte do problema é que você não sabe o que igual está realmente olhando.Um objeto pode ter muitos campos diferentes e ainda afirmar que é igual a algum outro objeto, pode até ser de um tipo diferente.(por exemplo, uma classe de URL personalizada pode retornar true para uma string igual ao seu formato externo).

Então eu não acho que seja possível sem instrumentação de bytecode onde você pode realmente modificar a função classes equals() para ver quais campos ela acessa.Mesmo assim, ainda seria extremamente difícil saber 'verdadeiramente' por que uma função retornou falsa.Mas espero que seja uma simples questão de comparar os campos que são realmente acessados ​​em equals()

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top