Innen generisch, außen nicht

Man kann bekanntlich ein Foo<String> und ein Foo<Integer> nicht in die gleiche Liste packen, ohne Typsicherheit zu verlieren, zu casten, Wildcards oder Raw-Types zu verwenden u.s.w.

Oder doch? Aber dazu müsste man eine Klasse haben, die “innen” generisch ist, und “außen” nicht. Spätestens jetzt dürften einige an meinem Geisteszustand zweifeln, aber es geht, probiert es aus:

public class Outer<T> {

    public class Inner {
        T t;

        public Inner(T t) {
            this.t = t;
        }
    }

    public static void main(String[] args) {
        List<Outer.Inner> list = new ArrayList<>();
        list.add(new Outer<String>().new Inner("blubb"));
        list.add(new Outer<Integer>().new Inner(42));
        for(Outer.Inner inner : list) {
            System.out.println(inner.t);
        }
    }

}

Ich sehe in diesem (zugegebenermaßen ziemlich verrückten) Konstrukt eine gewisse Parallele zu Scalas abstrakten Typmembern (Blog-Beitrag), auch wenn mir noch nicht klar ist, wie stark dieser Vergleich hinkt.

Seht ihr Anwendungs-Potentiel für diesen Trick?

Auf die Idee, dass man auch Outer<String>.Inner schreiben könnte, bin ich gar nicht gekommen. Dann ist meine Variante tatsächlich eine Art Raw-Type (obwohl ich keine Warnung dafür gesehen habe).

Was den Sinn angeht: Schon das Mitschleifen der Typparameter kann in bestimmten Situationen richtig lästig werden, z.B. bei zwei voneinander abhängigen Typhierarchien:

interface Foo<T extends FooFactory<?>> {
   Class<T> getFactoryClass();
}

interface FooFactory<T extends Foo<?>> {
   T getFoo();
}

Wie bekommt man in diesem Fall den Wildcard weg? Ich habe mit dem CRTP herumgespielt (also wie bei Enum<E extends Enum<E>>), nur funktioniert das bei dieser gegenseitigen Abhängigkeit nicht vollständig, es bleibt immer ein „Loch“. Ich habe gehofft, dass man das solche oder ähnliche Fälle eventuell über Tricks wie oben lösen könnte.

Mein (jaja: etwas veraltetes) Eclipse Juno hat da eine Raw-Types-Warnung rausgehauen :confused:

[QUOTE=Landei;90863]Was den Sinn angeht: Schon das Mitschleifen der Typparameter kann in bestimmten Situationen richtig lästig werden, z.B. bei zwei voneinander abhängigen Typhierarchien:

Ich habe gehofft, dass man das solche oder ähnliche Fälle eventuell über Tricks wie oben lösen könnte.[/QUOTE]

Ja, diese selbstreferentiellen Typen sind immer ein Krampf. Speziell dass es schon bei so einfachen Dingen wie „einer Hierarchie“ so schnell unübersichtlich wird: Node<T extends Node<T>>, mit all den Würgarounds wie dem getThis-Trick ( AngelikaLanger.com - Java Generics FAQs - Programming With Java Generics - Angelika Langer Training/Consulting ). Teilweise ist mir der Trade-Off dann einfach zu ungünstig: Klassen- und Methodedeklarationen wie

class SomeNode<A extends Node<A,?>, B> extends Node<A, B> {
    void doSomething(SomeNode<? super A, ? extends Node<? super B>> other) {... }
}

(nein, es macht keinen Sinn :wink:
im Vergleich zu

class SomeNode extends Node {
    void doSomething(Node other) {... }
}

und der Gefahr einer ClassCastException - irgendwann tendiert man schlicht um der Übersichtlichkeit Willen doch zu letzteren, so niederschmetternd diese Kapitulation auch ist.
Eine Lösung dafür wäre super. Aber inwieweit das Outer/Inner-Konstrukt da eine Hilfe wäre, hat sich mir noch nicht erschlossen…

EDIT: Ein bißchen Off-Topic nach http://forum.byte-welt.net/threads/11785-Nicht-instantiierbare-Klassen ausgelagert

So ganz erschließt sich mir der Sinn noch nicht. Outer.Inner ist ja auch erstmal ein raw type, solange man nicht Outer<String>.Inner schreibt - womit man den Typparameter doch wieder dabei hätte…

BTW, bezüglich des Kommentars im Blog

Übrigens: Wer solche seltsamen Konstruktoraufrufe wie im Beispielcode noch nicht gesehen hat, ist nicht allein – ich kannte die Syntax auch nicht…

Nachdem ich mal gesehen habe, dass jemand eine handvoll static Utility-Methods, die ich geschrieben hatte, mit new ContainingClass().staticMethod() aufrief und static inner classes mit new Outer().new Inner() instantiiert hat, bin ich mit /* Private constructor to prevent instantiation */ seeehr gewissenhaft :wink: (auch wenn gerade das in diesem Fall ja nicht gewünscht ist)

EDIT: Die Diskussion dazu geht hier weiter: http://forum.byte-welt.net/threads/11785-Nicht-instantiierbare-Klassen