¿Es una buena práctica usar la reflexión si reduce en gran medida la cantidad de código repetitivo?
Básicamente, existe una compensación entre el rendimiento y tal vez la legibilidad por un lado y la abstracción / automatización / reducción del código repetitivo por el otro lado.
Editar: Aquí hay un ejemplo del uso recomendado de la 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 obtienes dos Base
referencias de tipo y quieres ver si sus objetos correspondientes son del mismo (sub) tipo y son iguales.
Como soluciones, existe la 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 "get" (convención sobre configuración), llámelos a ambos objetos y llame a iguales en 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");
}
}
fuente
Respuestas:
La reflexión fue creado para un propósito específico, para descubrir la funcionalidad de una clase que era desconocido en tiempo de compilación, similar a lo que el
dlopen
ydlsym
funciones hace en C. Cualquier uso fuera de que se debe en gran medida examinada.¿Alguna vez se te ocurrió que los propios diseñadores de Java encontraron este problema? Es por eso que prácticamente cada clase tiene un
equals
método. 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 función de campos privados sin captadores. Usted no sabeEs por eso que cada objeto que quiere igualdad personalizada debe implementar un
equals
método. Eventualmente, querrás poner los objetos en un conjunto, o usarlos como un índice hash, luego tendrás que implementar deequals
todos modos. Otros lenguajes lo hacen de manera diferente, pero los usos de Javaequals
. Debes apegarte a las convenciones de tu idioma.Además, el código "repetitivo", si se coloca en la clase correcta, es bastante difícil de arruinar. La reflexión agrega complejidad adicional, lo que significa posibilidades adicionales de errores. En su método, por ejemplo, dos objetos se consideran iguales si uno regresa
null
para un campo determinado y el otro no. ¿Qué pasa si uno de tus captadores devuelve uno de tus objetos, sin un apropiadoequals
? Tuif (!object1.equals(object2))
fallará. También lo hace propenso a errores es el hecho de que la reflexión rara vez se usa, por lo que los programadores no están tan familiarizados con sus problemas.fuente
El uso excesivo de la reflexión probablemente depende del lenguaje utilizado. Aquí estás usando Java. En ese caso, la reflexión debe usarse con cuidado porque a menudo es solo una solución para un mal diseño.
Entonces, está comparando diferentes clases, este es un problema perfecto para la anulación de métodos . 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. Consulte /programming/27581/overriding-equals-and-hashcode-in-java para ver un ejemplo de cómo implementar la comparación de igualdad correctamente.
fuente
compare()
método, suponiendo que cualquier método que comience con "get" es un captador y es seguro y apropiado llamarlo como parte de una operación de comparación. Es una violación de la definición de interfaz prevista del objeto, y aunque eso puede ser conveniente, casi siempre es incorrecto.get
es un captador y no un método personalizado que devuelve algo?Creo que tienes dos problemas aquí.
Código dinámico vs estático
Esta es una pregunta perenne y la respuesta es muy testaruda.
Por un lado, su compilador es muy bueno para detectar todo tipo de código incorrecto. Lo hace a través de varias formas de análisis, siendo el análisis de tipo común. Sabe que no puede usar un
Banana
objeto en el código que espera unCog
. Te dice esto 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 a partir del contexto. Cuánto puede inferirse y qué tan general es esa inferencia, depende en gran medida del lenguaje que se utilice. Java puede inferir información de tipo a través de mecanismos como Herencia, Interfaces y Genéricos. El millaje varía, algunos otros idiomas proporcionan menos mecanismos y otros proporcionan más. Todavía se reduce a lo que el compilador puede saber que es verdad.
Por otro lado, su compilador no puede predecir la forma del código externo, y a veces se puede expresar un algoritmo general sobre muchos tipos que no se pueden expresar fácilmente usando el sistema de tipos del lenguaje. En estos casos, el compilador no siempre puede conocer el resultado de antemano, y es posible que ni siquiera pueda saber qué pregunta hacer. La reflexión, las interfaces y la clase Object son la forma en que Java maneja estos problemas. Tendrá que proporcionar los controles y el manejo correctos, pero no es poco saludable tener este tipo de código.
Si hacer que su código sea muy específico o muy general se reduce al problema que está tratando de manejar. Si pudiera expresarlo fácilmente utilizando el sistema de tipos, hágalo. Deje que el compilador aproveche sus puntos fuertes y lo ayude. Si el sistema de tipos no puede saber de antemano (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) son la herramienta adecuada para usar.
Solo tenga en cuenta que salir del sistema de tipos de su idioma es sorprendente. Imagina acercarte a tu amigo y comenzar una conversación en inglés. De repente, suelta algunas palabras del español, francés y cantonés para expresar tus pensamientos con precisión. El contexto le dirá mucho a su amigo, pero es posible que tampoco sepan cómo manejar esas palabras que conducen a todo tipo de malentendidos. Se trata de esos malos entendidos mejor que explicar esas ideas en Inglés utilizando más palabras?
Igualdad personalizada
Si bien entiendo que Java depende en gran medida del
equals
método para comparar generalmente dos objetos, no siempre es adecuado en un contexto dado.Hay otra forma, y también es un estándar Java. Se llama un comparador .
La forma en que implemente su comparador dependerá de lo que esté comparando y cómo.
equals
implementación específica .fuente
Prefiero evitar la programación reflexiva tanto como sea posible porque
También es mucho menos eficaz que las llamadas a métodos simples; solía ser más lento en un orden de magnitud o más.
Código de comprobación estática
Cualquier código reflexivo busca clases y métodos usando cadenas; en el ejemplo original busca cualquier método que comience con "get"; devolverá getters pero otros métodos además, como "gettysburgAddress ()". Las reglas podrían ajustarse en el código, pero el punto sigue siendo que es una verificación de tiempo de ejecución ; un IDE y el compilador no pueden ayudar. En general, no me gusta el código "mecanografiado en cadena" o "obsesivo primitivo".
Más difícil de razonar
El código reflexivo es más detallado que las llamadas a métodos simples. Más código = más errores, o al menos más potencial para errores, más código para leer, probar, etc. Menos es más.
Más difícil de refactorizar
Porque el código está basado en cadenas / dinámicamente; tan pronto como la reflexión esté en la escena, no puede refactorizar el código con un 100% de confianza utilizando las herramientas de refactorización de un IDE, ya que el IDE no puede detectar los usos reflexivos.
Básicamente, evite la reflexión en código general si es posible; Busque un diseño mejorado.
fuente
El uso excesivo de cualquier cosa por definición es malo, ¿verdad? Así que vamos a deshacernos del (más) por ahora.
¿Llamaría al uso interno increíblemente pesado de la reflexión de primavera un mal hábito?
Spring controla la reflexión mediante el uso de anotaciones, al igual que Hibernate (y probablemente docenas / cientos de otras herramientas).
Siga esos patrones si lo usa en su propio código. Use anotaciones para asegurarse de que el IDE de su usuario 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 usarán su código, incluso el uso más simple de la reflexión es probablemente un uso excesivo.
fuente
Creo que la mayoría de estas respuestas pierden el punto.
Sí, probablemente debería escribir
equals()
yhashcode()
, como señaló @KarlBielefeldt.Pero, para una clase con muchos campos, esto puede ser tedioso repetitivo.
Entonces, depende .
Otra posibilidad: si sus clases realmente tienen tantos campos que escribir un igual es tedioso, considere
Map.equals()
para su comparación. (oMap.hashcode()
)por ejemplo ( nota : ignorando las verificaciones nulas, probablemente debería usar Enums en lugar de las teclas de cadena, mucho no se muestra ...)
fuente