É uma boa prática usar a reflexão se reduzir bastante a quantidade de código padrão?
Basicamente, há uma troca entre desempenho e talvez legibilidade de um lado e abstração / automação / redução do código padrão no outro lado.
Edit: Aqui está um exemplo de um uso recomendado de reflexão .
Para dar um exemplo, suponha que exista uma classe abstrata Base
que possua 10 campos e 3 subclasses SubclassA
, SubclassB
e SubclassC
cada uma com 10 campos diferentes; todos são feijões simples. O problema é que você obtém duas Base
referências de tipo e deseja ver se os objetos correspondentes são do mesmo (sub) tipo e são iguais.
Como soluções, existe a solução bruta na qual você primeiro verifica se os tipos são iguais e depois verifica todos os campos ou pode usar a reflexão e ver dinamicamente se eles são do mesmo tipo e iteram em todos os métodos que começam com "get" (convenção sobre a configuração), chame-os nos objetos e chame iguais nos resultados.
boolean compare(Base base1, Base, base2) {
if (base1 instanceof SubclassA && base2 instanceof SubclassA) {
SubclassA subclassA1 = (SubclassA) base1;
SubclassA subclassA2 = (SubclassA) base2;
compare(subclassA1, subclassA2);
} else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
//the same
}
//boilerplate
}
boolean compare(SubclassA subA1, SubclassA subA2) {
if (!subA1.getField1().equals(subA2.getField1)) {
return false;
}
if (!subA1.getField2().equals(subA2.getField2)) {
return false;
}
//boilerplate
}
boolean compare(SubclassB subB1, SubclassB subB2) {
//boilerplate
}
//boilerplate
//alternative with reflection
boolean compare(Base base1, Base base2) {
if (!base1.getClass().isAssignableFrom(base2.getClass())) {
System.out.println("not same");
System.exit(1);
}
Method[] methods = base1.getClass().getMethods();
boolean isOk = true;
for (Method method : methods) {
final String methodName = method.getName();
if (methodName.startsWith("get")) {
Object object1 = method.invoke(base1);
Object object2 = method.invoke(base2);
if(object1 == null || object2 == null) {
continue;
}
if (!object1.equals(object2)) {
System.out.println("not equals because " + object1 + " not equal with " + object2);
isOk = false;
}
}
}
if (isOk) {
System.out.println("is OK");
}
}
fonte
Respostas:
Reflexão foi criado para uma finalidade específica, para descobrir a funcionalidade de uma classe que era desconhecido em tempo de compilação, semelhante ao que o
dlopen
edlsym
funções fazer em C. Qualquer utilização fora do que deve ser fortemente examinado.Já lhe ocorreu que os próprios designers de Java encontraram esse problema? É por isso que praticamente toda classe tem um
equals
método. Classes diferentes têm definições diferentes de igualdade. Em algumas circunstâncias, um objeto derivado pode ser igual a um objeto base. Em algumas circunstâncias, a igualdade pode ser determinada com base em campos privados sem getters. Você não sabe.É por isso que todo objeto que deseja igualdade personalizada deve implementar um
equals
método. Eventualmente, você desejará colocar os objetos em um conjunto ou usá-los como um índice de hash, e precisará implementá-lo deequals
qualquer maneira. Outras linguagens fazem isso de maneira diferente, mas o Java usaequals
. Você deve seguir as convenções do seu idioma.Além disso, o código "clichê", se colocado na classe correta, é muito difícil de estragar. A reflexão adiciona complexidade adicional, o que significa chances adicionais de erros. No seu método, por exemplo, dois objetos são considerados iguais se um retornar
null
para um determinado campo e o outro não. E se um de seus getters retornar um de seus objetos, sem um apropriadoequals
? Seuif (!object1.equals(object2))
irá falhar. Também tornando-o propenso a erros é o fato de que a reflexão raramente é usada, para que os programadores não estejam tão familiarizados com suas dicas.fonte
O uso excessivo da reflexão provavelmente depende do idioma usado. Aqui você está usando Java. Nesse caso, a reflexão deve ser usada com cuidado, porque geralmente é apenas uma solução alternativa para um design inadequado.
Portanto, você está comparando classes diferentes, este é um problema perfeito para a substituição de métodos . Observe que as instâncias de duas classes diferentes nunca devem ser consideradas iguais. Você pode comparar a igualdade apenas se tiver instâncias da mesma classe. Consulte /programming/27581/overriding-equals-and-hashcode-in-java para obter um exemplo de como implementar a comparação de igualdade corretamente.
fonte
compare()
método, assumindo que qualquer método que comece com "get" é um getter e é seguro e apropriado chamar como parte de uma operação de comparação. É uma violação da definição de interface pretendida do objeto e, embora possa ser conveniente, quase sempre está errado.get
é um getter e não um método personalizado que retorna algo?Eu acho que você tem dois problemas aqui.
Código Estático vs Dinâmico
Esta é uma pergunta perene e a resposta é muito opinativa.
Por um lado, seu compilador é muito bom em capturar todo tipo de código incorreto. Isso é feito através de várias formas de análise, sendo a análise de tipo comum. Ele sabe que você não pode usar um
Banana
objeto no código que está esperando aCog
. Ele diz isso através de um erro de compilação.Agora, isso só pode ser feito quando é possível inferir o tipo aceito e o tipo fornecido do contexto. Quanto pode ser inferido, e quão geral é essa inferência, depende em grande parte do idioma que está sendo usado. Java pode inferir informações de tipo por meio de mecanismos como Herança, Interfaces e Genéricos. A milhagem varia, alguns outros idiomas fornecem menos mecanismos e outros fornecem mais. Ainda se resume ao que o compilador pode saber como verdadeiro.
Por outro lado, seu compilador não pode prever a forma do código externo e, às vezes, um algoritmo geral pode ser expresso em muitos tipos que não podem ser expressos facilmente usando o sistema de tipos da linguagem. Nesses casos, o compilador nem sempre pode saber o resultado com antecedência e pode nem ser capaz de saber qual pergunta fazer. Reflexão, interfaces e a classe Object são a maneira do Java lidar com esses problemas. Você precisará fornecer as verificações e o manuseio corretos, mas não é prejudicial ter esse tipo de código.
A possibilidade de tornar seu código muito específico ou muito geral se resume ao problema que você está tentando solucionar. Se você puder expressá-lo facilmente usando o sistema de tipos, faça-o. Deixe o compilador tocar seus pontos fortes e ajudá-lo. Se o sistema de tipos não puder conhecer antecipadamente (código estrangeiro), ou se o sistema de tipos não for adequado para uma implementação geral do seu algoritmo, a reflexão (e outros meios dinâmicos) é a ferramenta certa a ser usada.
Lembre-se de que sair do sistema de tipos do seu idioma é surpreendente. Imagine ir até o seu amigo e começar uma conversa em inglês. De repente, solte algumas palavras do espanhol, francês e cantonês que expressam seus pensamentos com precisão. O contexto dirá muito a seu amigo, mas ele também pode não saber lidar com essas palavras, levando a todo tipo de mal-entendido. Lidar com esses mal-entendidos é melhor do que explicar essas idéias em inglês usando mais palavras?
Igualdade personalizada
Embora eu entenda que o Java depende muito do
equals
método para comparar geralmente dois objetos, nem sempre é adequado em um determinado contexto.Existe outra maneira, e também é um padrão Java. É chamado de Comparador .
A maneira como você implementa o seu comparador dependerá do que você está comparando e como.
equals
implementação específica .fonte
Eu prefiro evitar a programação reflexiva, tanto quanto possível, porque
Também tem muito menos desempenho do que chamadas simples de método; costumava ser mais lento por uma ordem de magnitude ou mais.
Código de verificação estaticamente
Qualquer código reflexivo procura classes e métodos usando strings; no exemplo original, ele está procurando por qualquer método que comece com "get"; ele retornará getters, mas também outros métodos, como "gettysburgAddress ()". As regras podem ser reforçadas no código, mas a questão é que é uma verificação de tempo de execução ; um IDE e o compilador não podem ajudar. Em geral, não gosto de códigos "tipificados" ou "obsessivos primitivos".
Mais difícil de raciocinar
O código reflexivo é mais detalhado do que as chamadas simples de método. Mais código = mais bugs, ou pelo menos mais potencial para bugs, mais código para ler, testar etc. Menos é mais.
Mais difícil de refatorar
Porque o código é baseado em strings / dinamicamente; assim que a reflexão estiver em cena, você não poderá refatorar o código com 100% de confiança usando as ferramentas de refatoração de um IDE, pois o IDE não poderá captar os usos reflexivos.
Basicamente, evite a reflexão no código geral, se possível; procure um design aprimorado.
fonte
O uso excessivo de qualquer coisa por definição é ruim, certo? Então, vamos nos livrar do (over) por enquanto.
Você chamaria o uso interno incrivelmente pesado da reflexão da primavera de um mau hábito?
A primavera domestica a reflexão usando anotações - o mesmo ocorre com o Hibernate (e provavelmente dezenas / centenas de outras ferramentas).
Siga esses padrões se você o usar em seu próprio código. Use anotações para garantir que o IDE do usuário ainda possa ajudá-lo (mesmo se você for o único "Usuário" do seu código, o uso descuidado da reflexão provavelmente voltará a morder sua bunda eventualmente).
No entanto, sem considerar como seu código será usado pelos desenvolvedores, mesmo o uso mais simples da reflexão provavelmente será usado em excesso.
fonte
Eu acho que a maioria dessas respostas não entendem o ponto.
Sim, você provavelmente deve escrever
equals()
ehashcode()
, como observado por @KarlBielefeldt.Mas, para uma classe com muitos campos, isso pode ser um tédio.
Então depende .
Outra possibilidade: se você realmente tem tantos campos que escrever um igual é entediante, considere
Map.equals()
para sua comparação. (ouMap.hashcode()
)por exemplo ( nota : ignorar as verificações nulas, provavelmente deve usar Enums em vez de chaves String, muito não mostrado ...)
fonte