Generic Verständnisproblem

Hallo Community,

ich habe folgenden Beispielcode:

public abstract class Handler<T extends Message> {
	public abstract void handle(T message);
}
public class Message {

}
public class Process {
	public static void main(String... args) {
		Handler<? extends Message> handler = createHandler("Testhandler");
		Message message = createMessage("Testmessage");
		handler.handle(message);
	}

	private static Message createMessage(String string) {
		return ..... //Hier Instanz bauen;
	}

	private static Handler<? extends Message> createHandler(String string) {
		return ..... //Hier Instanz bauen;
	}
}

In der Zeile handler.handle(message); erscheint mir die Meldung The method handle(capture#2-of ? extends Message) in the type Handler<capture#2-of ? extends Message> is not applicable for the arguments (Message). Warum hat Java hier ein Problem, und wie löse ich es?

Ganz einfach: Du sagst nicht, was das T in handler sein soll, es ist “irgendeines” (Wildcard “?”). Wie soll Handler deiner Meinung nach auf einen unbekannten Typ reagieren?

So wie es aussieht, könnte T hier einfach Message sein:

public class Process {
    public static void main(String... args) {
        Handler<Message> handler = createHandler("Testhandler");
        Message message = createMessage("Testmessage");
        handler.handle(message);
    }
 
    private static Message createMessage(String string) {
        return ..... //Hier Instanz bauen;
    }
 
    private static Handler<Message> createHandler(String string) {
        return ..... //Hier Instanz bauen;
    }
}

OK, du hast recht … aber … :wink:

Das war nur ein Beispiel, hier habe ich mal die createHandler Methode etwas erweitert:

public class Process {
	static Injector injector = InjectorFactory.instance().getInjector();

	public static void main(String... args) {
		Handler<? extends Message> handler = createHandler("Testhandler");
		Message message = createMessage("Testmessage");
		handler.handle(message);
	}

	private static Message createMessage(String string) {
		return injector.getInstance(TestMessage.class);
	}

	private static Handler<? extends Message> createHandler(String string) {
		return injector.getInstance(TestHandler.class);
	}
}

Wenn ich hier „? extends“ entferne, schimpft java bei „getInstance“ … (Übrigens: Google Guice). Er will dann hier von mir einen Cast haben. könnte ich machen, aber geht das nicht Cast frei? Wozu habe ich Generics?

PS: meine createMethoden sind in Wirklichkeit etwas komplexer, da ich hier mit Annotationen konfigurierte Handler / Messages raus suche.

PS2: Der vollständigkeitshalber noch TestHandler.java:

public class TestHandler extends Handler<TestMessage> {

	@Override
	public void handle(TestMessage message) {
		System.out.println(message.getTest());
	}

}

wenn du einen Handler<? extends Message> erzeugst/ irgendwo hast,
dann ist die Art der verarbeiteten Message, T, unbekannt,

-> dann kannst du die handle-Methode schlicht nicht sauber aufrufen,
genau wie bei einer List<? extends Message> kein add() möglich, wohl aber noch get(), man weiß nicht was genau drin ist, aber auf jeden Fall Message-Objekte

sort() geht auch noch, als Grenzfall generisch unsauberer Code aber sicher im Rahmen kann man die enthaltenen Elemente umsortiern
Collections: public static <T extends Comparable<? super T>> void sort(List<T> list)


das Herunterrechnen ist generell auch keine gute Idee:
TestHandler ist ein Handler für TestMessage, nicht für jede Art von Message

würde man ein TestHandler als Handler behandeln und generisch korrekt die Methode handle(Message) nutzen,
dann könnte man auch ein Objekt der Klasse RealMessage übergeben, also != TestMessage,

was würde wohl in TestHandler in der Methode handle(TestMessage) passieren,
mit Aufruf expliziter Methoden dieser TestMessage-Klasse auf ein RealMessage-Objekt?
schon vorher ClassCastException

ich könnte nun auch wieder ein List-Beispiel geben, aber wohl auch so ausreichend


mit dem zweiten Absatz glaube ich inzwischen, dass dein Anlegen-Code doch ganz gut so ist wie er ist,

wie er ist, das ist aber kaum zu erkenne, was ist InjectorFactory.instance().getInjector(), gibt es in Suchmaschinen nicht, ist das dein Code?

welchen Rückgabetyp liefert injector.getInstance(TestHandler.class)?
falls TestHandler oder zumindest Handler, dann deine Probleme erklärbar und korrekt von dieser Methode


mit einer Methode createHandler(String string) verläßt du generell jegliche generische Sicherheit,
an dieser Stelle ist unbekannt/ beliebig, was für ein Objekt aus dem String entsteht

den Rückgabetyp musst du dir passend setzen und innerhalb der Methode beliebig mit Casten und anderen Schweinereien das Richtige zurückgeben,
erst danach, ab der Rückgabe, kann wieder eine gewisse Vertrauenskette aus generischer Korrektheit aufgebaut werden

schreibe z.B.

    {
        Handler<TestMessage> handler = createHandler("Testhandler");
        TestMessage t = null;
        handler.handle(t);
    }


    private static <T extends Message> Handler<T> createHandler(String string)
    {
        return (Handler<T>) .. // irgendwas was hoffentlich am Ende stimmt
    }

dann liegt es allein in der Verantwortung des Aufrufers, den “Testhandler” als Handler zu verwenden,
der Aufrufer müsste der Methode auch gar nicht mitteilen, welchen Typ er annimmt, könnte auch erst nach createHandler() alleine casten

eine Sicherheit einzubauen, dass der MessageTyp zum String passt, ist relativ schwierig und immer etwas unsauber
sicherer wäre vielleicht die Richtung

    {
        Handler<TestMessage> handler = getInstance(TestHandler.class);
        TestMessage t = null;
        handler.handle(t);
    }

    private static <T extends Message, U extends Handler<T>>U getInstance(Class<U> cl)
    {
        return (U)cl.newInstance();
    }

(kann Fehler enthalten), aber hier habe ich wohlweislich schon getInstance-Namen verwendet, denn das macht anscheinend diese Methode,
und du willst ja wohl nicht ohne Grund getHandler(String) dabei haben…

[quote=SlaterB]mit einer Methode createHandler(String string) verläßt du generell jegliche generische Sicherheit,
an dieser Stelle ist unbekannt/ beliebig, was für ein Objekt aus dem String entsteht[/quote]

Das ist glaube ich der Knackpunkt an meinem Konstrukt. Ich muss mit meiner Zuordnung Handler => Message so oder so aufpassen und Generics können mir nur bedingt Sicherheit bieten. Bei Funktionionstests würde hier ein Problem schnell sichtbar werden und ggfs. kann man auch mit Unittests einiges automatisieren. Die einzigste Sicherheit, die mir der Compiler bieten könnte, wäre, wenn ich Handler und Message in eine Klasse vereine. Das fänd ich nun nicht so prinkelnd, obgleich es funktionell keine Probleme geben würde.

Übrigens: Injector ist von Google Guice und die Factory habe ich nur zum initialen bauen mit dem AbstractModule. getInstance liefert eine Instanz der übergebenen Klasse aus dem Container.