Discussion:
Generics, Mehrere Paramter mit Bounds
(zu alt für eine Antwort)
R***@gmail.com
2006-09-13 23:22:16 UTC
Permalink
Hallo,

folgendes: Ich habe eine Unit-:
interface Unit { public String getString(); }
class MyUnit {
public String getString() {
return "Foo";
}
}
und eine Factoryklasse:
interface Factory<T extends Unit> { public T getInstance(); }
class MyFactory implements Factory<MyUnit> {
public MyUnit getInstance() {
return new MyUnit();
}
}

Nun möchte ich in einer anderen Klasse eine Methode bauen, die eine
Factoryinstanz bekommt und eine Liste, wo Units hineinkommen sollen:

public <T extends Unit,S super T> List<S> readUnits(List<S>
list,UnitFactory<T> fact) {
while(irgenwas) { Erzeuge Units mit fact.getInstance() und fülle sie
mit Daten (hier nicht vorgesehen) und list.add(<neue Unit>); }
return list;
}

Die Liste soll also eine Liste von sein, die die Objekte aus der
Factory halten kann (oder allgemeiner) und die Objekte aus der Factory
sollen konkrete Units sein. Aber so kann ich das nicht kompilieren
(Warum? nur eine Bound erlaubt?).

Mit
public <T extends Unit> List<? super T> readUnits(List<? super T>
list,UnitFactory<T> fact)
ginge es zwar, aber dann wäre die Liste ja eine Liste aus ?...
Ich könnte auch den Rückgabewert weglassen (es wird sowieso immer nur
dieselbe Liste wieder zurückgegeben), aber für geschachtelte Aufrufe
ist das so natürlich schöner.

Ich hoffe auf Erleuchtung :)

mfg Bastian Kirchmayr
Wanja Gayk
2006-09-14 10:08:08 UTC
Permalink
Post by R***@gmail.com
Hallo,
interface Factory<T extends Unit>
public <T extends Unit,S super T> List<S> readUnits(List<S> list, UnitFactory<T> fact){
Müste dort nicht viel mehr "UnitFactory<S>" stehen?
Du willst doch einen beliebigen Typ S zwischen Unit und T erzeugen und
zurückgeben, nicht T selbst.. so lese ich das doch, oder.

Wenn du ein:
UnitFactory<SomeUnit> fact ...
List<Unit> list ...
list = readUnits(list, fact);

machst, dann forderst du in der Methode praktisch ein

List<Unit> list;
SomeUnit descendant = factory.create(...
list.add(descendant);

Aber deine Liste hat den Typ-Parameter <Unit> nicht <? extends Unit>,
deswegen darf die Factory da kein "SomeUnit" reinschmeißen.

Eine Factory<S> wäre in diesem Fall passend zum Typ der Liste.

Das wäre zumindest mein erster Gedanke..

Gruß,
-Wanja-
--
Ada Byron, die Lochkarten für Babbages "Difference Engine" vorbereitete,
gilt als die erste Programmiererin der Weltgeschichte. Sie hat auch das
Verhaltensmuster für alle nachfolgenden Programmierer vorgegeben: Sie
trank und schluckte Drogen.
R***@gmail.com
2006-09-14 10:47:46 UTC
Permalink
Post by Wanja Gayk
Post by R***@gmail.com
interface Factory<T extends Unit>
public <T extends Unit,S super T> List<S> readUnits(List<S> list, UnitFactory<T> fact){
Müste dort nicht viel mehr "UnitFactory<S>" stehen?
Du willst doch einen beliebigen Typ S zwischen Unit und T erzeugen und
zurückgeben, nicht T selbst.. so lese ich das doch, oder.
Mein Gedanke war eher, dass T den Typ angibt, der von der Factory
erzeugt werden soll und S einen beliebigen Obertyp von T, um alle
möglichen Listen unterstützen zu können.
z.B. könnte ein Factory<List>-Objekt in eine List<List>, eine
List<Collection> oder eine List<Object> gepackt werden.
Post by Wanja Gayk
UnitFactory<SomeUnit> fact ...
List<Unit> list ...
list = readUnits(list, fact);
machst, dann forderst du in der Methode praktisch ein
List<Unit> list;
SomeUnit descendant = factory.create(...
list.add(descendant);
Aber deine Liste hat den Typ-Parameter <Unit> nicht <? extends Unit>,
deswegen darf die Factory da kein "SomeUnit" reinschmeißen.
Daher mein Ansatz mit <S super T>, aber ich kann in einer List<Unit>
doch problemlos eine SomeUnit reinschreiben (SomeUnit implements Unit
vorrausgesetzt).
Der Fehler tritt aber sowieso schon im Methodenkopf auf, nicht bei
einem Aufruf von list.add().

mfg Bastian
Wanja Gayk
2006-09-14 16:14:35 UTC
Permalink
Post by R***@gmail.com
Daher mein Ansatz mit <S super T>, aber ich kann in einer List<Unit>
doch problemlos eine SomeUnit reinschreiben (SomeUnit implements Unit
vorrausgesetzt).
Der Fehler tritt aber sowieso schon im Methodenkopf auf, nicht bei
einem Aufruf von list.add().
Hm.. da muss ich dann mal genauer hinschauen, ich habe das nur
überflogen. Nix für ungut. So firm bin ich mit Generics nämlich auch
noch nicht. ;-)

Gruß,
-Wanja-
--
Ada Byron, die Lochkarten für Babbages "Difference Engine" vorbereitete,
gilt als die erste Programmiererin der Weltgeschichte. Sie hat auch das
Verhaltensmuster für alle nachfolgenden Programmierer vorgegeben: Sie
trank und schluckte Drogen.
R***@gmail.com
2006-09-15 11:31:32 UTC
Permalink
Post by Wanja Gayk
Hm.. da muss ich dann mal genauer hinschauen, ich habe das nur
überflogen. Nix für ungut. So firm bin ich mit Generics nämlich auch
noch nicht. ;-)
kein Problem, thx :)

Ich habe jetzt erstmal auf den Rückgabewert verzichtet, also
public <T extends Unit> void readUnits(List<? super T>
list,UnitFactory<T> fact);
genommen, ist zwar nicht so schön, aber es wird gehen, vielleicht hat
ja noch jmd ne Lösung.

mfg Bastian
Ralf Ullrich
2006-09-15 12:07:01 UTC
Permalink
Post by R***@gmail.com
Ich habe jetzt erstmal auf den Rückgabewert verzichtet, also
public <T extends Unit> void readUnits(List<? super T>
list,UnitFactory<T> fact);
genommen, ist zwar nicht so schön, aber es wird gehen, vielleicht hat
ja noch jmd ne Lösung.
package de.jnana.dclj.generics;

import java.util.List;

public class Demo2 {
interface Unit {
public String getString();
}

static class MyUnit implements Unit {
public String getString() {
return "Foo";
}
}

interface Factory<T extends Unit> {
public T getInstance();
}

static class MyFactory implements Factory<MyUnit> {
public MyUnit getInstance() {
return new MyUnit();
}
}

public <T extends Unit> List<? super T> readUnits(List<? super T> list,
Factory<T> fact) {
while (list.isEmpty()) {
T unit = fact.getInstance();
list.add(unit);
}
return list;
}
}


Wenn man mit Generics arbeitet, kann man sich folgendes ins Hirn brennen:
super ist immer nur zusammen mit Wildcard zulässig, es heißt also immer "?
super" nie "T super".

Das Problem an der gezeigten kompilierbaren Lösung ist nun, dass der
Rückgabe-Typ auch nur noch mit wildcard definiert ist, d.h.

readUnits(new List<Object>(), new MyFactory()).get(0)

lässt sich nicht mehr kompilieren. Daher ist der Rückgabetyp ziemlich
nutzlos, insofern ist es gar nicht so unschön, dass du nun darauf
verzichtet hast.

Schöner wäre es allerdings, wenn du durch readUnits nicht vorschreiben
würdest, dass es eine List ist. Schließlich benutzt readUnits nur add()
und das ist schon bei Collection vorhanden. (vom isEmpty() für mein
Beispiel sehen wir mal ab, obwohl dafür das gleiche gilt.)

public <T extends Unit>
void readUnits(
Collection<? super T> coll,
Factory<T> fact) {

while (coll.isEmpty()) {
T unit = fact.getInstance();
coll.add(unit);
}
}

Das wäre dann meine bevorzugte Lösung.

cu
Stefan Ram
2006-09-15 12:26:26 UTC
Permalink
Post by Ralf Ullrich
Schöner wäre es allerdings, wenn du durch readUnits nicht vorschreiben
würdest, dass es eine List ist. Schließlich benutzt readUnits nur add()
und das ist schon bei Collection vorhanden.
Schade, daß die Java-API nicht atomarer so strukturiert ist,
daß es eine Schnittstelle mit genau einer add-Operation gibt:

interface Add< T >{ /** ... */ void add( T t ); }

Diese Schnittstelle würde dann von "Collection" erweitert
werden, und, wer will, braucht sich dann nur an "Add<T>" zu
binden.

In C++ ist so etwas Ähnliches möglich. Eine Schablone

template< typename Add >void link( Add a ){ a.add( this->x ); }

akzeptiert jedes a, das eine Operation namens "add" mit der
passenden Signatur implementiert. Hier stützt sich C++
allerdings nur auf die Signatur, während in Java oben noch die
Semantik der Schnittstelle berücksichtigt wird. In Java könnte
es zwei Schnittstellen mit einer "add"-Operation mit gleicher
Signatur aber unterschiedlicher Bedeutung geben, von denen
explizit eine verlangt und die andere ausgeschlossen werden
könnte, was mit der C++-Schablone nicht geht.
R***@gmail.com
2006-09-15 12:54:56 UTC
Permalink
Post by Ralf Ullrich
super ist immer nur zusammen mit Wildcard zulässig, es heißt also immer "?
super" nie "T super".
Hm, warum ist das so?

Und, wie ist es mit Sachen der Art
public <S,T extends S & Unit> readUnits(...); <- Hier sagt er keine
anderen Bounds nach eine Typvariablen

public <S.T extends S & Unit> readUnits(...); <- Hier beschränkt er
sich darauf mir mitzuteilen, dass S hier unerwartet kommt

oder anderen Arten, dasselbe zu sagen?
Post by Ralf Ullrich
Das Problem an der gezeigten kompilierbaren Lösung ist nun, dass der
Rückgabe-Typ auch nur noch mit wildcard definiert ist, d.h.
readUnits(new List<Object>(), new MyFactory()).get(0)
lässt sich nicht mehr kompilieren. Daher ist der Rückgabetyp ziemlich
nutzlos, insofern ist es gar nicht so unschön, dass du nun darauf
verzichtet hast.
Ich mir wäre ein Rückgabewert mit dem gleichen Typ, wie der
Eingabetyp natürlich lieber, weil ich auch mal geschachtelte Aufrufe
habe. Aber ich komme drüber hinweg.
Post by Ralf Ullrich
Schöner wäre es allerdings, wenn du durch readUnits nicht vorschreiben
würdest, dass es eine List ist. Schließlich benutzt readUnits nur add()
und das ist schon bei Collection vorhanden. (vom isEmpty() für mein
Beispiel sehen wir mal ab, obwohl dafür das gleiche gilt.)
Ich habe über eine Collection nachgedacht, aber die Klasse hat eine
festgelegte Reihenfolge von Units, die sich in der Liste widerspiegeln
soll. Insofern ist eine potentiell ungeordnete Collection nicht
passend, auch wenn alle meine benutzen Methoden tatsächlich aus
Collection sind.

mfg Bastian
Ralf Ullrich
2006-09-15 13:01:38 UTC
Permalink
Post by R***@gmail.com
Post by Ralf Ullrich
super ist immer nur zusammen mit Wildcard zulässig, es heißt also immer "?
super" nie "T super".
Hm, warum ist das so?
Weil es so in der JLS steht.
Post by R***@gmail.com
Und, wie ist es mit Sachen der Art
public <S,T extends S & Unit> readUnits(...); <- Hier sagt er keine
anderen Bounds nach eine Typvariablen
Weil es so in der JLS steht.
Post by R***@gmail.com
public <S.T extends S & Unit> readUnits(...); <- Hier beschränkt er
sich darauf mir mitzuteilen, dass S hier unerwartet kommt
Weil es so in der JLS steht.
Post by R***@gmail.com
oder anderen Arten, dasselbe zu sagen?
Tatsächlich sagst du gar nicht dasselbe, sondern Unsinn, du brabbelst wie
ein Kleinkind, das zwar die Worte kennt aber noch keine Syntax um mit
ihnen Sätze zu bilden.

Alle diese Typ-Definitionen benutzen zwar das richtige Vokabular, aber
verwenden keine gültige in der JLS beschriebene Syntax.


cu
R***@gmail.com
2006-09-15 13:11:35 UTC
Permalink
Post by Ralf Ullrich
Tatsächlich sagst du gar nicht dasselbe, sondern Unsinn, du brabbelst wie
ein Kleinkind, das zwar die Worte kennt aber noch keine Syntax um mit
ihnen Sätze zu bilden.
Alle diese Typ-Definitionen benutzen zwar das richtige Vokabular, aber
verwenden keine gültige in der JLS beschriebene Syntax.
Danke für die Blumen, ich hatte allerdings auf eine etwas
ausführlichere Erklärung gehofft, du kannst ja leider auch nichts
weiter sagen, als dass es nicht geht, das ist mir auch schon
aufgefallen. Das weißt sicherlich auf ein sehr viel "erwachseneres"
Wissen hin......

mfg Bastian
Ralf Ullrich
2006-09-15 13:21:03 UTC
Permalink
Post by R***@gmail.com
Post by Ralf Ullrich
Tatsächlich sagst du gar nicht dasselbe, sondern Unsinn, du brabbelst wie
ein Kleinkind, das zwar die Worte kennt aber noch keine Syntax um mit
ihnen Sätze zu bilden.
Alle diese Typ-Definitionen benutzen zwar das richtige Vokabular, aber
verwenden keine gültige in der JLS beschriebene Syntax.
Danke für die Blumen, ich hatte allerdings auf eine etwas
ausführlichere Erklärung gehofft, du kannst ja leider auch nichts
weiter sagen, als dass es nicht geht, das ist mir auch schon
aufgefallen. Das weißt sicherlich auf ein sehr viel "erwachseneres"
Wissen hin......
Sorry, aber das war nicht persönlich gemeint, sondern nur ein
anschauliches Beispiel.

Wenn du mehr über die Gründe wissen willst, dann wirst du dir das selber
anlesen müssen, ich bin damit zufrieden, dass sich ein paar ziemlich kluge
Leute zusammengesetzt haben und entschieden haben, was für Java 5 machbar
ist und was nicht. Diese Leute sind die Expert Group zur Generics JSR. Die
JSR war ziemlich lange offen und in dieser Zeit wurde in vielen Blogs und
Veranstaltungen immer wieder öffentlich darüber diskutiert, wie man
Generics in Java implementieren soll. Was die Vorzüge und Probleme mit
diversen Ansätzen sind. Fast alles was dazu geschrieben wurde ist auch
heute noch im Internet zu finden und zu lesen.

Wenn du jetzt ernsthaft glaubst, dass dir hier jemand in kurzen Sätzen
zusammenfassen kann, was dort teils über Jahre diskutiert wurde, und was
letztlich dazu geführt hat, dass ein "T super" eben nicht unterstützt
wird, dann bist du wahr(schein)lich auf dem falschen Dampfer.

cu
R***@gmail.com
2006-09-15 13:30:45 UTC
Permalink
Post by Ralf Ullrich
Wenn du mehr über die Gründe wissen willst, dann wirst du dir das selber
anlesen müssen, ich bin damit zufrieden, dass sich ein paar ziemlich kluge
Leute zusammengesetzt haben und entschieden haben, was für Java 5 machbar
ist und was nicht. Diese Leute sind die Expert Group zur Generics JSR. Die
JSR war ziemlich lange offen und in dieser Zeit wurde in vielen Blogs und
Veranstaltungen immer wieder öffentlich darüber diskutiert, wie man
Generics in Java implementieren soll. Was die Vorzüge und Probleme mit
diversen Ansätzen sind. Fast alles was dazu geschrieben wurde ist auch
heute noch im Internet zu finden und zu lesen.
Wenn du jetzt ernsthaft glaubst, dass dir hier jemand in kurzen Sätzen
zusammenfassen kann, was dort teils über Jahre diskutiert wurde, und was
letztlich dazu geführt hat, dass ein "T super" eben nicht unterstützt
wird, dann bist du wahr(schein)lich auf dem falschen Dampfer.
Nein, aber natürlicherweise kenne ich die Antwort auf eine von mir
gestellte Frage nicht und kann daher auch nicht genau abschätzen, was
die Antwort dazu erfordert. Aber um mal ein Beispiel auf eine kurze
Antwort einer Frage zu geben (auf die ich eine ungefähre Antwort
habe), die ich irgendwo gelesen haben:

"Warum kann man den Typparamter nicht zur Laufzeit feststellen?" -
"Weil Generics in Java als Type Erasure implementiert sind, d.h.
sämtliche Typparamter werden als ihre oberen Bounds betrachtet (i.d.r.
Object) und die entsprechenden Casts vom Compiler eingefügt. Daher ist
der Typparamter zur Laufzeit nicht mehr bekannt."

mfg Bastian
Ralf Ullrich
2006-09-15 13:38:32 UTC
Permalink
Post by R***@gmail.com
"Warum kann man den Typparamter nicht zur Laufzeit feststellen?" -
"Weil Generics in Java als Type Erasure implementiert sind, d.h.
sämtliche Typparamter werden als ihre oberen Bounds betrachtet (i.d.r.
Object) und die entsprechenden Casts vom Compiler eingefügt. Daher ist
der Typparamter zur Laufzeit nicht mehr bekannt."
Eine vergleichbar einfach Erklärung gibt es meines Wissens auf die Frage
nach "Warum kein 'T super'?" nicht. Und für seitenlange Erklärungen habe
ich ehrlich gesagt diesmal keine Lust (zumal ich auch erst mal ein paar
Stunden nach der genauen Antwort forschen müsste). Mehr als ein "Geht
nicht, weil nicht vorgesehen." kommt daher nicht.

cu
R***@gmail.com
2006-09-15 15:13:32 UTC
Permalink
Post by Ralf Ullrich
Eine vergleichbar einfach Erklärung gibt es meines Wissens auf die Frage
nach "Warum kein 'T super'?" nicht. Und für seitenlange Erklärungen habe
ich ehrlich gesagt diesmal keine Lust (zumal ich auch erst mal ein paar
Stunden nach der genauen Antwort forschen müsste). Mehr als ein "Geht
nicht, weil nicht vorgesehen." kommt daher nicht.
Na gut, wenns keine bessere Lösung gibt, werde ich halt bei meiner
bleiben.

thx
Bastian

Lesen Sie weiter auf narkive:
Loading...