É um mau hábito usar (mais) a reflexão?

14

É uma boa prática usar a reflexão se reduzir muito a quantidade de código padrão?

Basicamente, há um trade-off entre desempenho e talvez legibilidade de um lado e abstração / automação / redução de código clichê do outro lado.

Editar: Aqui está um exemplo de um uso recomendado de reflexão .

Para dar um exemplo, suponha que exista uma classe abstrata Base que tem 10 campos e tem 3 subclasses SubclassA , SubclassB e SubclassC cada com 10 campos diferentes; eles são todos simples feijões. O problema é que você obtém duas referências Base type e deseja ver se seus objetos correspondentes são do mesmo tipo (sub) e são iguais.

Como soluções, há a solução bruta na qual você primeiro verifica se os tipos são iguais e então verifica todos os campos ou pode usar reflexão e ver dinamicamente se eles são do mesmo tipo e iterar sobre todos os métodos que começam com "get "(convenção sobre configuração), chame-os nos dois objetos e chame equals 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");
        }
}
    
por m3th0dman 01.04.2013 / 11:34
fonte

5 respostas

23

O Reflection foi criado para uma finalidade específica, para descobrir a funcionalidade de uma classe que era desconhecida em tempo de compilação, semelhante ao que as funções dlopen e dlsym fazem em C. Qualquer uso fora disso deveria ser strongmente examinado.

Já lhe ocorreu que os próprios projetistas de Java encontraram esse problema? É por isso que praticamente toda classe tem um método equals . Classes diferentes têm diferentes definições 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 método equals . Eventualmente, você vai querer colocar os objetos em um conjunto, ou usá-los como um índice de hash, então você terá que implementar equals de qualquer maneira. Outras linguagens fazem isso de maneira diferente, mas o Java usa equals . Você deve seguir as convenções de seu idioma.

Além disso, o código "boilerplate", se colocado na classe correta, é muito difícil de estragar. O reflexo adiciona complexidade adicional, significando chances adicionais de erros. Em 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 equals apropriado? Seu if (!object1.equals(object2)) falhará. Além disso, torná-lo propenso a erros é o fato de que a reflexão raramente é usada, portanto, os programadores não estão tão familiarizados com suas armadilhas.

    
por 01.04.2013 / 15:01
fonte
10

O uso excessivo da reflexão provavelmente depende da linguagem usada. Aqui você está usando o Java. Nesse caso, a reflexão deve ser usada com cuidado, porque muitas vezes é apenas uma solução para um projeto ruim.

Então, você está comparando classes diferentes, este é um problema perfeito para substituição de métodos . Observe que 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 o link para obter um exemplo de como implementar a comparação de igualdade corretamente.

    
por 01.04.2013 / 11:47
fonte
0

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?

O Spring ama a reflexão usando anotações - assim como o Hibernate (e provavelmente dezenas / centenas de outras ferramentas).

Siga esses padrões se você usá-lo em seu próprio código. Use anotações para garantir que o IDE de seu usuário ainda possa ajudá-lo (mesmo que você seja o único "usuário" do seu código, o uso descuidado da reflexão provavelmente voltará a incomodá-lo na bunda).

No entanto, sem considerar como seu código será usado pelos desenvolvedores, até mesmo o uso mais simples da reflexão é provavelmente o uso excessivo.

    
por 10.12.2018 / 18:00
fonte
0

Acho que a maioria dessas respostas não entendeu o objetivo.

  1. Sim, você provavelmente deve escrever equals() e hashcode() , como observado por @ KarlBielefeldt.

  2. Mas, para uma turma com muitos campos, isso pode ser um clichê tedioso.

  3. Então, depende .

    • Se você precisa apenas de equals e hashcode raramente , é pragmático e provavelmente o.k., usar um cálculo de reflexão de propósito geral. Pelo menos como uma primeira passagem rápida e suja.
    • mas se você precisar de muito, por exemplo, esses objetos serão colocados no HashTables, para que o desempenho seja um problema, você deve definitivamente escrever o código. será muito mais rápido.
  4. Outra possibilidade: se as suas aulas realmente têm tantos campos que escrever um igual é entediante, considere

    • colocando os campos em um mapa.
    • você ainda pode escrever getters e setters personalizados
    • use Map.equals() para sua comparação. (ou Map.hashcode() )

por exemplo. ( note : ignorando as verificações nulas, provavelmente deveria usar Enums em vez de chaves String, muito não mostrado ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
    
por 10.12.2018 / 23:09
fonte
0

Acho que você tem dois problemas aqui.

  1. Qual é o código dinâmico vs estático que devo ter?
  2. Como expresso uma versão personalizada de igualdade?

Código dinâmico vs. estático

Esta é uma pergunta perene e a resposta é muito teórica.

Por um lado, seu compilador é muito bom em pegar todos os tipos de códigos ruins. 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 objeto Banana no código que está esperando um Cog . Ele informa isso através de um erro de compilação.

Agora, isso só pode ser feito quando é possível deduzir o tipo aceito e o tipo especificado no contexto. Quanto pode ser inferido, e quão geral é essa inferência, depende em grande parte da linguagem que está sendo usada. Java pode inferir informações de tipo por meio de mecanismos como Herança, Interfaces e Genéricos. A milhagem varia, outras linguagens fornecem menos mecanismos e outras fornecem mais. Ainda se resume ao que o compilador pode saber que é verdade.

Por outro lado, o 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 facilmente expressos usando o sistema de tipos da linguagem. Nesses casos, o compilador nem sempre pode saber o resultado antecipadamente, e pode até não ser capaz de saber que pergunta fazer. Reflexão, interfaces e a classe Object são a maneira de o Java lidar com esses problemas. Você terá que fornecer as verificações e manuseio corretos, mas não é insalubre ter esse tipo de código.

Seja para tornar seu código muito específico ou muito geral, ele se resume ao problema que você está tentando resolver. Se você pudesse facilmente expressá-lo usando o sistema de tipos, faça isso. Deixe o compilador jogar seus pontos strongs e ajudá-lo. Se o sistema de tipos não puder saber de antemão (código estrangeiro) ou se o sistema de tipos for inadequado para uma implementação geral do seu algoritmo, a reflexão (e outros meios dinâmicos) é a ferramenta certa a ser usada.

Esteja ciente de que pisar fora do sistema de tipos do seu idioma é surpreendente. Imagine andar até o seu amigo e começar uma conversa em inglês. De repente, deixe algumas palavras em espanhol, francês e cantonês, expressando seus pensamentos com precisão. Contexto vai dizer muito ao seu amigo, mas eles também podem não saber como lidar com essas palavras levando a todos os tipos de mal-entendidos. Está lidando com esses mal entendidos melhor do que explicando essas idéias em inglês usando mais palavras?

Igualdade personalizada

Embora eu entenda que o Java depende muito do método equals para comparar geralmente dois objetos, nem sempre ele é adequado em um determinado contexto.

Existe outra maneira, e também é um padrão Java. É chamado de Comparador .

O modo como você implementa seu comparador dependerá do que você está comparando e como.

  • Pode ser aplicado a quaisquer dois objetos, independentemente de sua implementação equals específica.
  • Poderia implementar um método de comparação geral (baseado em reflexões) para manipular quaisquer dois objetos.
  • As funções de comparação padrão podem ser adicionadas para tipos de objetos comumente comparados, garantindo segurança e otimização de tipos.
por 11.12.2018 / 05:08
fonte