¿Es un mal hábito (sobre) usar la reflexión?

14

¿Es una buena práctica usar la reflexión si reduce en gran medida la cantidad de código repetitivo?

Básicamente, existe un compromiso entre el rendimiento y la legibilidad de un lado y la abstracción / automatización / reducción del código de la placa de calderas del otro lado.

Edite: Este es un ejemplo de un uso recomendado de reflexión .

Para dar un ejemplo, suponga que hay una clase abstracta Base que tiene 10 campos y tiene 3 subclases SubclassA , SubclassB y SubclassC cada una con 10 campos diferentes; Todos son frijoles simples. El problema es que obtiene dos referencias de tipo Base y desea ver si sus objetos correspondientes son del mismo (sub) tipo y son iguales.

Como soluciones, hay una solución sin procesar en la que primero verifica si los tipos son iguales y luego verifica todos los campos o puede usar la reflexión y ver dinámicamente si son del mismo tipo e iterar sobre todos los métodos que comienzan con "obtener "(convención sobre configuración), llámalos en ambos objetos y llama a los 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");
        }
}
    
pregunta m3th0dman 01.04.2013 - 11:34

5 respuestas

23

La reflexión se creó con un propósito específico, para descubrir la funcionalidad de una clase que no se conocía en el momento de la compilación, similar a lo que hacen las funciones dlopen y dlsym en C. > en gran medida escrutado.

¿Alguna vez se le ocurrió que los diseñadores de Java se encontraron con este problema? Es por eso que prácticamente todas las clases tienen un método equals . Diferentes clases tienen diferentes definiciones de igualdad. En algunas circunstancias, un objeto derivado podría ser igual a un objeto base. En algunas circunstancias, la igualdad podría determinarse en base a campos privados sin intermediarios. Usted no sabe

Es por eso que cada objeto que quiere igualdad personalizada debe implementar un método equals . Eventualmente, querrá poner los objetos en un conjunto, o usarlos como un índice de hash, luego tendrá que implementar equals de todos modos. Otros lenguajes lo hacen de manera diferente, pero Java usa equals . Debes atenerse a las convenciones de tu idioma.

Además, el código "boilerplate", si se coloca en la clase correcta, es bastante difícil de arruinar. La reflexión agrega complejidad adicional, lo que significa oportunidades adicionales para los errores. En su método, por ejemplo, dos objetos se consideran iguales si uno devuelve null para un campo determinado y el otro no. ¿Qué sucede si uno de sus captadores devuelve uno de sus objetos, sin un equals apropiado? Tu if (!object1.equals(object2)) fallará. También lo hace propenso a los errores es el hecho de que la reflexión rara vez se utiliza, por lo que los programadores no están tan familiarizados con sus errores.

    
respondido por el Karl Bielefeldt 01.04.2013 - 15:01
10

El uso excesivo de la reflexión probablemente depende del lenguaje utilizado. Aquí estás utilizando Java. En ese caso, la reflexión debe usarse con cuidado porque a menudo es solo una solución para el mal diseño.

Entonces, estás comparando diferentes clases, este es un problema perfecto para anular el método . Tenga en cuenta que las instancias de dos clases diferentes nunca deben considerarse iguales. Puede comparar la igualdad solo si tiene instancias de la misma clase. Vea enlace para ver un ejemplo de cómo implementar la comparación de igualdad correctamente.

    
respondido por el Sulthan 01.04.2013 - 11:47
0

El uso excesivo de algo por definición es malo, ¿verdad? Así que vamos a deshacernos de (por encima) por ahora.

¿Llamarías al mal uso interno de la reflexión, sorprendentemente pesado, un mal hábito?

La primavera domina la reflexión mediante el uso de anotaciones, al igual que Hibernate (y probablemente docenas / cientos de otras herramientas).

Sigue esos patrones si lo usas en tu propio código. Use anotaciones para asegurarse de que el IDE de su usuario aún pueda ayudarlo (incluso si usted es el único "Usuario" de su código, el uso descuidado de la reflexión probablemente volverá a morderle el trasero).

Sin embargo, sin tener en cuenta cómo los desarrolladores utilizarán su código, incluso el uso más simple de la reflexión es probablemente el uso excesivo.

    
respondido por el Bill K 10.12.2018 - 18:00
0

Creo que la mayoría de estas respuestas no entienden.

  1. Sí, probablemente deberías escribir equals() y hashcode() , como lo indica @KarlBielefeldt.

  2. Pero, para una clase con muchos campos, esto puede ser tedioso repetitivo.

  3. Entonces, depende .

    • Si solo necesitas iguales y hashcode raramente , es pragmático, y probablemente o.k., para usar un cálculo de reflexión de propósito general. Al menos como un primer paso rápido y sucio.
    • pero si necesita mucho, e.g. estos objetos se colocan en tablas hash, por lo que el rendimiento será un problema, definitivamente debe escribir el código. Será mucho más rápido.
  4. Otra posibilidad: si las clases realmente tienen tantos campos que escribir un igual es tedioso, considere

    • poniendo los campos en un mapa.
    • todavía puedes escribir getters y setters personalizados
    • usa Map.equals() para tu comparación. (o Map.hashcode() )

por ejemplo ( nota : ignorar las comprobaciones nulas, probablemente debería usar Enums en lugar de teclas de cadena, mucho no se muestra ...)

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);
}
    
respondido por el user949300 10.12.2018 - 23:09
0

Creo que tienes dos problemas aquí.

  1. ¿Cuánto código dinámico vs estático debería tener?
  2. ¿Cómo expreso una versión personalizada de igualdad?

Código dinámico vs estático

Esta es una pregunta perenne y la respuesta es muy crítica.

Por un lado, tu compilador es muy bueno para atrapar todo tipo de código incorrecto. Lo hace a través de varias formas de análisis, siendo el análisis de tipo uno común. Sabe que no puede usar un objeto Banana en el código que espera un Cog . Te lo dice a través de un error de compilación.

Ahora solo puede hacer esto cuando puede inferir tanto el Tipo aceptado como el Tipo dado del contexto. Cuánto se puede inferir y qué tan general es la inferencia, depende en gran medida del lenguaje que se utilice. Java puede inferir información de tipo a través de mecanismos tales como herencia, interfaces y genéricos. El kilometraje varía, algunos otros lenguajes proporcionan menos mecanismos y otros proporcionan más. Todavía se reduce a lo que el compilador puede saber que es verdad.

Por otra parte, su compilador no puede predecir la forma del código externo y, a veces, un algoritmo general puede expresarse sobre muchos tipos que no pueden expresarse fácilmente utilizando el sistema de tipos del idioma. En estos casos, el compilador no siempre puede conocer el resultado de antemano y es posible que ni siquiera sepa qué pregunta hacer. La reflexión, las interfaces y la clase de objetos son la forma en que Java maneja estos problemas. Deberá proporcionar los controles y el manejo correctos, pero no es poco saludable tener este tipo de código.

Ya sea para que su código sea muy específico o muy general, se trata del problema que está tratando de resolver. Si pudieras expresarlo fácilmente usando el sistema de tipos, hazlo. Deja que el compilador juegue a sus puntos fuertes y te ayude. Si el sistema de tipos no podría saberlo por adelantado (código extranjero), o el sistema de tipos no es adecuado para una implementación general de su algoritmo, entonces la reflexión (y otros medios dinámicos) es la herramienta correcta a utilizar.

Tenga en cuenta que es sorprendente salir del sistema de tipos de su idioma. Imagina caminar hacia tu amigo y comenzar una conversación en inglés. De repente, suelte algunas palabras del español, francés y cantonés para expresar sus pensamientos con precisión. El contexto le dirá mucho a tu amigo, pero también es posible que no sepan cómo manejar esas palabras que conducen a todo tipo de malentendidos. ¿Es mejor lidiar con esos malentendidos que explicar esas ideas en inglés con más palabras?

Igualdad personalizada

Aunque entiendo que Java se basa en gran medida en el método equals para comparar generalmente dos objetos, no siempre es adecuado en un contexto dado.

Hay otra forma, y también es un estándar de Java. Se llama un Comparator .

La manera en que implementes tu comparador dependerá de lo que estés comparando y cómo.

  • Se puede aplicar a cualquiera de los dos objetos, independientemente de su implementación específica equals .
  • Podría implementar un método de comparación general (basado en la reflexión) para manejar dos objetos cualquiera.
  • Las funciones de comparación estándar podrían agregarse para los tipos de objetos comparados comúnmente, garantizando la seguridad y optimización de tipos.
respondido por el Kain0_0 11.12.2018 - 05:08

Lea otras preguntas en las etiquetas