Mehrfache Interfaces mit inneren Klassen

Die im Beitrag über Multidispatch angedeutete Möglichkeit, mit inneren Klassen oder Wrappern anderen „equals()“ und „hashCode()“-Methoden anzubieten, sollte vielleicht etwas genauer gezeigt werden. Als Beispiel dient hier einmal Java, aber die Thematik ist auch in Scala, Ruby, Perl, C# und anderen Sprachen vorhanden, weil es gängig ist, dass die Hashtabellen gerne mit einer einer bestimmten Kombination aus hashCode() und eqals() zusammenarbeiten. Das zwingt dazu, diese beiden Methoden nach der gängigsten Verwendung in Hashtabellen zu definieren.

Die Idee lässt sich aber auch anwenden, um verschiedene Interfaces mit derselben Klasse zu implementieren.

Das Prinzip sieht so aus:


public interface A {
    public int fa(int x);
}

public interface B {
    public int fb(int x);
}

public class X implements A {

    ....
    public int fa(int x) {
        ....
    }
    private class Y implements B {
        public int fb(int x) {
             return fa(x) % 999;
        }
    }
    public B getAsB() {
        return new Y()
    }
}

Man kann also die Implementierung von B in der inneren Klasse Y vorsehen und dabei auf alle Methoden und Attribute der umgebenden Klasse zugreifen. Die Referenz auf die umgebende Klasse ist bei allen Instanzen der (nicht-statischen) inneren Klasse implizit dabei.

Hier ein Beispiel für verschiedene hashCode() und equals-Methoden:


public interface StringPair {
    public String getLhs();
    public String getRhs();
}

public class StringPairImpl implements StringPair {
    private final String lhs;
    private final String rhs;

    public StringPairImpl(String lhs, String rhs) {
        assert lhs != null;
        assert rhs != null;
        this.lhs = lhs;
        this.rhs = rhs;
    }

    public String getLhs() {
        return lhs;
    }

    public String getRhs() {
        return rhs;
    }

    public int hashCode() {
        return lhs.hashCode() * 91 + rhs.hashCode();
    }

    public boolean equals(Object obj) {
         if (this == obj) {
             return true;
         } else if (! obj instanceof StringPairImpl) {
             return false;
         } else {
             StringPairImpl other = (StringPairImpl) obj;
             return this.lhs.equals(other.lhs) && this.rhs.equals(other.rhs);
         }
    }

    private class IgnoreCase implements StringPair {

        public String getLhs() {
            return lhs;
        }

        public String getRhs() {
            return rhs;
        }

        public int hashCode() {
            return lhs.toUpperCase().hashCode() * 91 + rhs.toUpperCase().hashCode();
        }

        public boolean equals(Object obj) {
             if (this == obj) {
                 return true;
             } else if (! obj instanceof StringPairImpl) {
                 return false;
             } else {
                 StringPairImpl other = (StringPairImpl) obj;
                 return this.lhs.toUpperCase().equals(other.lhs.toUpperCase())
                     && this.rhs.toUpperCase().equals(other.rhs.toUpperCase());
             }
        }
    }

    public StringPair getWithCaseIgnored() {
        return new IgnoreCase();
    }
}

Die innere Klasse bietet also eine Implementierung desselben Interfaces, aber hashCode() und equals() ignorieren diesmal Groß- und Kleinschreibung.

Eine andere Möglichkeit wäre es bei Collections, eine immutable-Variante über eine innere Klasse anzubieten und eine getImmutable()-Methode einzubauen. Das ist aber bekanntlich nicht der Weg, den die JDK-library gewählt hat.

Was man aber dabei auch sieht: Leider muss man sehr viel „unnötigen“ Code schreiben, der eigentlich offensichtlich ist, aber eben doch nicht fehlen darf. Das erschwert sowohl das Schreiben als auch das Lesen des Codes, vor allem aber die Wartbarkeit. So wird die an sich gute Idee der inneren Klassen teilweise wieder verwässert.

Share Button

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*