example - java hashcode vulnerability
toString(), equals() y hashCode() en una interfaz (12)
A Java solo le preocupa que los métodos estén definidos en alguna parte. La interfaz no lo obliga a redefinir los métodos en nuevas clases que heredan de la interfaz por primera vez si ya están definidos. Como java.lang.Object
ya implementa estos métodos, sus nuevos objetos se ajustan a la interfaz incluso si no anulan estos tres métodos por sí solos.
Entonces, tengo una interfaz con un montón de métodos que deben implementarse, los nombres de los métodos son irrelevantes.
Los objetos que implementan esta interfaz a menudo se ponen en colecciones, y también tienen un formato especial de String () que quiero que usen.
Entonces, pensé que sería conveniente poner hashCode (), equals () y toString () en la interfaz, para asegurarme de que recuerdo anular el método predeterminado para estos. Pero cuando agregué estos métodos a la interfaz, IDE / Compiler no se queja si no tengo esos tres métodos implementados, aunque los puse explícitamente en la interfaz.
¿Por qué no se aplicará esto para mí? Se queja si no implemento ninguno de los otros métodos, pero no aplica esos tres. ¿Lo que da? ¿Alguna pista?
Cualquier clase que implemente su interfaz también extiende Object. Object define hashCode, equals y toString y tiene implementaciones predeterminadas de los tres.
Lo que intentas lograr es bueno, pero no práctico.
Hay una implementación para esos métodos desde Object
.
Los 3 de esos métodos están definidos por java.lang.Object
que (implícitamente) se extiende por todas las demás clases; por lo tanto, existen implementaciones predeterminadas para esos métodos y el compilador no tiene nada de qué quejarse.
Otras personas han respondido tu pregunta real lo suficiente. En cuanto a una solución para su problema en particular, podría considerar crear sus propios métodos (tal vez getStringRepresentation, getCustomHashcode y equalsObject) y hacer que sus objetos amplíen una clase base cuyos métodos equals, toString y hashCode llamen a estos métodos.
Sin embargo, esto podría frustrar el propósito de usar una interfaz en primer lugar. Esta es una de las razones por las que algunas personas han sugerido que los iguales, toString y hashCode nunca deberían haberse incluido en la clase Object en primer lugar.
Si desea forzar la anulación de equals () y hashCode (), extienda desde una superclase abstracta, que define estos métodos como abstractos.
Su objeto ya contiene implementaciones de esos tres métodos, ya que cada objeto hereda esos métodos de Object, a menos que se anulen.
bien si ha declarado una interfaz en la que, por defecto, todos los métodos son abstractos y necesita proporcionar la funcionalidad, pero cuando la implementa en la subclase, proporciona el derecho de implementación. como puede ver, cada clase es la subclase de una superclase (en pocas palabras, Object es la superclase de todas las clases) de modo que si tiene una clase que implementa una interfaz, debe proporcionar la implementación de los métodos. pero una cosa necesita ser recordada aquí.
Independientemente de que, si no declaró estos métodos en la interfaz, todavía tenía este comportamiento para la subclase que implementó la interfaz en primer lugar.
entonces si no lo declaras, aún estará presente y otra cosa es que dado que estos métodos y otros métodos de la clase Object están presentes para todos los objetos de las clases, entonces no hay necesidad de implementación.
Todos los objetos en Java heredan de java.lang.Object
y Object proporciona implementaciones predeterminadas de esos métodos.
Si su interfaz contiene otros métodos, Java se quejará si no implementa completamente la interfaz al proporcionar una implementación de esos métodos. Pero en el caso de equals()
, hashCode()
y toString()
(así como algunos otros que no mencionó), la implementación ya existe.
Una forma en que puede lograr lo que desea es proporcionar un método diferente en la interfaz, por ejemplo, toPrettyString()
o algo así. Luego puede llamar a ese método en lugar del método predeterminado toString()
.
Adam te da la razón por la cual no puedes salirte con la tuya tratando de forzar iguales, hashCode y toString. Me gustaría ir a la siguiente implementación que toca la solución provista por Adam y Stephan:
public interface Distinct {
boolean checkEquals(Object other);
int hash();
}
public interface Stringable {
String asString();
}
public abstract class DistinctStringableObject {
@Override
public final boolean equals(Object other) {
return checkEquals();
}
@Override
public final int hashCode() {
return hash();
}
@Override
public final String toString() {
return asString();
}
}
Ahora, cualquier clase que requiera definirse claramente y representarse como una Cadena puede extender el DistinctStringableObject que forzará la implementación de checkEquals, hashCode y toString.
Muestra de la clase concreta:
public abstract class MyDistinctStringableObject extends DistinctStringableObject {
@Override
public final boolean checkEquals(Object other) {
...
}
@Override
public final int hash() {
...
}
@Override
public final String asString() {
...
}
}
Las clases abstractas no funcionarán si tienes un nieto, ya que su padre ya anuló los métodos equal y hashCode y luego vuelves a tener tu problema.
Intenta usar annotatins y APT ( http://docs.oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html ) para completarlo.
Parece que quiere obligar a sus clases a anular las implementaciones predeterminadas de esos métodos. Si es así, la forma de hacerlo es declarando una superclase abstracta que tiene los métodos declarados como abstractos. Por ejemplo:
public abstract class MyBaseClass implements ... /* existing interface(s) */ {
public abstract boolean equals(Object other);
public abstract int hashCode();
public abstract String toString();
}
Luego cambie sus clases actuales para extend
esta clase.
Este enfoque funciona, pero no es una solución ideal.
Puede ser problemático para una jerarquía de clases existente.
No es una buena idea obligar a las clases que implementan su interfaz existente a extender una clase abstracta específica. Por ejemplo, puede cambiar los parámetros en las firmas de métodos para usar la clase abstracta en lugar de la (s) interfaz (es) existente (s). Pero el resultado final es un código menos flexible. (Y la gente puede encontrar formas de subvertir esto de todos modos, por ejemplo, agregando su propia subclase abstracta que "implementa" los métodos con una llamada
super.<method>(...)
)La imposición de un patrón de jerarquía / implementación de clase particular es miope. No puede predecir si algún cambio de requisito futuro significará que sus restricciones causan dificultades. (Esta es la razón por la cual la gente recomienda programar contra interfaces en lugar de clases específicas).
Volviendo a su pregunta real sobre por qué su interfaz no obliga a una clase a volver a declarar esos métodos:
¿Por qué no se aplicará esto para mí? Se queja si no implemento ninguno de los otros métodos, pero no aplica esos tres. ¿Lo que da? ¿Alguna pista?
Una interfaz impone la restricción de que una clase concreta que la implementa tiene una implementación para cada uno de los métodos. Sin embargo, no requiere que la clase misma implemente esos métodos. Las implementaciones del método se pueden heredar de una superclase. Y en este caso, eso es lo que está sucediendo. Los métodos heredados de java.lang.Object
saftisfy la restricción.
JLS 8.1.5 establece lo siguiente:
"A menos que la clase declarada sea abstracta, todos los métodos de miembro abstracto de cada superinterfaz directa deben implementarse (§8.4.8.1) ya sea mediante una declaración en esta clase o mediante una declaración de método existente heredada de la superclase directa o una superinterfaz directa , porque una clase que no es abstracta no tiene permitido tener métodos abstractos (§8.1.1.1) ".