Gabi und Sascha
Tags - Kategorien : Alle | Berlin | Bücher | Fotografie | Java | Linkhalde | Weichware | Verfassung
Warum auf Generics in Java besser verzichtet werden sollte

Generics sind seit der Java Version 5 Bestandteil der Sprache. Sie wurden von Entwicklern seit langem gefordert. Hintergrund ist auch die bisherige Typlosigkeit der Java Collection API. In Collections können vor Java 5 alle Datentypen, welche von Object abgeleitet wurden, abgelegt werden. Dieses Verhalten wird immer wieder gerne als Fehlerquelle herangezogen, um die Typsicherheit von Collections zu verlangen. Mit der Version 5 wurde dieser Forderung im letzten Jahr Rechnung getragen. Dies vermutlich auch vor dem Hintergrund, dass die .NET Plattform von Microsoft in der Version 2 ebenfalls einen Genericsmechanismus aufweisen wird.

Als ich noch mit C++ gearbeitet habe, habe ich auch gedacht, dass Generics, dort Templates genannt und mit etwas anderer Semantik versehen, ein sinnvolles Sprachkonstrukt sind. In C++ sind sie das auch, da es dort keine allgemeine Superklasse wie java.lang.Object gibt. Die C++ STL ist damit sicherlich einfacher zu bedienen. In Java hingegen sind Generics zumindest problematisch. Ich selbst hatte in den letzten 10 Jahren meines Arbeitens mit Java keinerlei Probleme mit der Collection API. Zumindest hatte ich keine der angeblich so häufig auftretenden casting Probleme. Warum nicht? Und warum sind Generics in diesem Zusammenhang nicht sinnvoll? Dies versuche ich im folgenden, anhand eines kleinen Beispiels, darzulegen.

Customer und Customers

Das Beispiel baut sich um eine Klasse Customer auf. Das Beispiel ist zwar etwas überstrapaziert, eignet sich aber hervorragend für die Beschreibung der Probleme. Ziel ist es eine Menge an Kunden zu verwalten. Ein erfahrener Entwickler wird sicherlich nicht brutal hingehen und alle Kunden in einer Collection ablegen. Er wird eine eigene Sammerklasse anbieten, in deren Implementierung er unterschiedliche Abfragen implementieren kann. Dies wird er schon machen, weil er weiss, dass diese Erweiterungsanforderungen immer kommen.

Die Klasse für eine Menge von Kunden nenne ich Customers. Um ihr neue Kunden hinzuzufügen bekommt sie eine add-Methode.


    public class Customers {
        private final List customers = new ArrayList();

        public void addCustomer(final Customer c) {
            customer.add(c);
        }
    }

Um eine Abfrage nach allen Benutzern durchzuführen werden unerfahrene Entwickler der Klasse eine Methode hinzufügen, die eine List zurück liefert, über deren Inhalt wiederum iteriert werden kann. So entsteht schnell arbeitender Code.


    public class Customers {
        private final List customers = new ArrayList();

        public void addCustomer(final Customer c) {
            customer.add(c);
        }
        public List<Customer> getCustomers() {
            return customers;
        }
    }

Allerdings geht bei diesem Vorgehen die Kapselung verloren. Ein Benutzer der API weiss, dass die Kunden intern in einer Liste gespeichert werden. Ein Umstieg auf Sets ist nicht mehr möglich. Diesem Umstand könnte noch begegnet werden, indem an Stelle einer Liste ein Datentyp Collection zurück gegeben wird. Die Tatsache, dass die Kapselung aufgebrochen wird ändert sich dadurch nicht. Jetzt lassen sich nicht einfach neue, effektive Suchanfragen in der Klasse Customers implementieren. Um dies zu demonstrieren wird das Beispiel erweitert.

Die Applikation bekommt die ID aus einer anderen Quelle als unserem Customer. Dies ist durchaus möglich und nicht ungewöhnlich. Um in der Menge der Kunden jetzt denjenigen mit der ID heraus zu finden, muss die gesamte Kundenliste durchlaufen werden.


    final String id = "123";
    final List customerList = customers.getCustomer();
    Customer customer = null;

    // Java 5 syntax
    for (Customer c : customerList) {
        final String cid = c.getId();
        if (id.equals(cid)) {
            customer = c;
            break;
        }
    }
    // Do something additional

Sicherlich ist es nicht sinnvoll jedesmal einen grossen Teil der Liste zu durcklaufen, nur um den richtigen Kunden zu finden. Der Code ist ineffektiv. Sinnvoller ist es die ID eines Kunden als Schlüssel (Key) in einer Map abzulegen. Hierfür wird die add-Methode erweitert:


    public class Customers {
        private final List customers = new ArrayList();
        private final Map customersById = new HashMap();

        public void addCustomer(final Customer c) {
            customer.add(c);
            customersbyId.put(c.getId(), c);
        }
        // ...
    }

Mit der Einführung eines neuen Containers in der Customers-Klasse muss es auf diese auch abfragende Zugriffe geben. Dem vorhergenden Code entsprechend, würde dies über einen Methode geschehen, welche die Map mit den ID zu Kunden Beziehung nach aussen führt:


    public class Customers {
        private final Map customersById = new HashMap();

        // ...
        public Map getCustomersById() {
            return customersById;
        }
    }

In der zurückgegebenen Map sind jetzt allerdings keinerlei sematische Informationen mehr vorhanden. Es ist nicht bekannt, was es für einen Zusammenhang zwischen dem Schlüsseltypen String und dem Werttypen Customer gibt. Neben diesem gravierenden Problem wird allerdings auch das Arbeiten von Operationen auf den Mengen problematisch. Hierfür können nicht mehr einfach alle, der von den Containerklassen gelieferten, Methoden genutzt werden. Vielmehr muss der Benutzer jetzt, als Beispiel, zum löschen sowohl in der List und in der Map den jeweiligen Kunden löschen. Entsprechendes gilt für das hinzufügen. Um dies zu wissen, muss er aber wiederum wissen wie der, eigentlich gekapselte gehörte, innere Aufbau der Klasse aussieht. Hierfür muss er nicht unbedingt den Quellcode kennen, es reichen Hinweise in der Dokumentation. In Alternative kann der Entwickler der Klasse Customers Löschmethoden in der Klasse selbst anbieten und nur unveränderliche Container zurück liefern oder die Löschmethoden in den jeweiligen Containermethoden überschreiben, indem er z.B. diese in anonymen Klassen bei der Auslieferung umschliesst. Allerdings wird hierdurch der Code entweder unnötig aufgebläht oder die Semantik des nicht möglichen löschens oder hinzufügens im Container verwirrt den Benutzer. Der Sinn, durch Generics weniger Code zu produzieren wird ad absurdum geführt. Die Möglichkeit Fehler zu machen steigt enorm an.

Sinnvoll ist es die Methoden jeweils in der Klasse Customers selbst zu implementieren. Dadurch wird die Klasse leichter wartbar und für den Benutzer einfacher zu bedienen. Bei Änderungen, z.B. dem hinzufügen einer neuen Abfragemethode, muss nicht auch der benutzende Code angepasst werden. In diesem Fall reichen untypisierte Container innerhalb der kapselden Klasse vollkommen aus.

Fazit

Ich habe bisher keine unproblematische Verwendung der in Java 5 eingeführten Generics gefunden. Das hier beschriebene Beispiel zeigt auf einfache Art und Weise, wie man sich dem falschen Einsatz eines Sprachfeaturs, dessen Nutzen ins Gegenteil verwandeln kann. Aus diesem Grund sollte in den Codeingstandards eines Unternehmen auf den Einsatz von Generics besonders eingegangen werden. Bis zum Beweis des sinnvollen Gegenteils würde ich Generics sogar ganz verbieten.

War letzte Woche bei meinen Eltern und dem Rest der Familie zu besuch. In Riesenbeck wurde am Wochenende Kirmes gefeiert. Eigentlich ein netter Grund, dorthin zu fahren, denn man trifft viele Leute, die man lange nicht mehr gesehen hat.

Es waren halt die üblichen Fahrgeschäfte und Fressbuden aufgebaut, aber wichtig ist ja eigentlich nur die Stimmung. Und die war, finde ich, recht gut. Man mußte nicht mit den Leuten reden, mit denen man nicht reden wollte, sondern grüßte sie nur (im Dorf grüßt man halt jeden, den man kennt). Und da viele Leute aus meiner ehemaligen Klasse da waren, die ich teilweise gar nicht wieder erkannt habe, war das alles schon total witzig. Viele von ihnen waren Sonntags nachmittags auf der Kinderkirmes unterwegs. Und selbst mein Partner aus dem Tanzkurs ist neulich erst zum zweiten Mal Vater geworden. Seine Frau ist auch rothaarig.......

Und es war auch wie sonst immer, wenn Kirmes ist, ich sah aus wie ein Streusselkuchen! Ich hatte die ganze letzte Zeit keine Hautprobleme, aber nu wieder. Schrecklich!!! Irgendwie holt einen die Vergangenheit immer wieder ein. Ob es daran lag, dass Kirmes war?

Schon lange nicht mehr so ein doofes Buch gelesen. Spielt voll auf ein anderes Buch der Autorin an, aber steht nicht im Klappentext. Glücklicherweise hatte ich es schon gelesen. Und eigentlich sind die Bücher von ihr auch ganz nett, halt sehr blutrünstig, aber nett. Doch dieses Buch trifft irgenwie nicht die goldene Mitte. Einmal beschreibt sie eine Obduktion so detailliert, bzw. deren Ergebnisse, dass mir die Luft wegblieb und ich das Buch erst einmal zur Seite legen musste.

Leider wird sie erst auf den letzten 20 Seiten spannender, aber das Ende ist schon vorher klar. Schade eigentlich, denn die anderen Bücher gefielen mit sehr gut.

Meinen ersten Eintrag in den Blog mit meinem neuen iBook widme ich meiner besten Freundin Nicole. Ich möchte mich noch einmal ganz doll bei dir bedanken, dass du während der Riesenbecker Kirmes so lange auf mich/uns gewartet hast :-)

Trotzdem hättest du einfach ohne besondere Einladung vorbeikommen können.

Beste Freundinnen sollte man festhalten, sie tun einem gut!