Discussion:
Boeses Generics Problem (bounded wildcards)
(zu alt für eine Antwort)
Oliver Pfeiffer
2006-10-17 11:46:14 UTC
Permalink
Ich habe hier ein wirklich unangenehmes und leider bedeutendes Problem, das
man eventuell mit Generics lösen kann (oder auch nicht?). Vielleicht ist
auch der von mir eingeschlagene Weg bereits im Grundsatz falsch. Auf jeden
Fall würde ich mich über jede Hilfe freuen!

Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt, der weder type-safe noch compilierbar ist. Das
ganze dient lediglich dazu, die Problematik zu verdeutlichen.

Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
die jeweils mit einer combine(Interval):Interval Methode mit anderen
Intervallen kombiniert werden können. Das kniffelige liegt darin, dass ein
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
jedoch Supertyps) kombiniert werden dürfen. Das ganze muss natürlich ohne
Overloading nur mit Overriding erfolgen, ansonsten verliert man die
Combinator Flexibilität. Casten oder instanceof würde einen Verlust der
Typsicherhiet bedeuten.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006


class Test {

static class Interval<I extends Interval> {

I combine(I iv) {
iv.method();
return new I();
}

void method() {
// something interval specific
}

}

static class Subinterval<I extends Subinterval> extends Interval<I> {

@Override
I combine(I iv) {
iv.submethod();
return new I();
}

void submethod() {
// something subinterval specific
}

}

static class Combinator<I extends Interval> {

I trippleCombine(I i1, I i2, I i3) {
return i1.combine(i2).combine(i3);
}

}

public static void main(String[] args) {
Subinterval<Subinterval> a = new Subinterval<Subinterval>();
Subinterval<Subinterval> b = new Subinterval<Subinterval>();
Subinterval<Subinterval> c = new Subinterval<Subinterval>();

Subinterval<Subinterval> result =
new Combinator<Subinterval>().trippleCombine(a, b, c);
}

}
Stefan Ram
2006-10-17 11:59:32 UTC
Permalink
Post by Oliver Pfeiffer
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt,
Ich sehe in Deinem Posting weder Java-Pseudo-Code noch einen
MIME-Anhang.
Post by Oliver Pfeiffer
Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
"Intervallklassen"
Post by Oliver Pfeiffer
die jeweils mit einer combine(Interval):Interval Methode mit anderen
"combine(Interval):Interval-Methode"
Post by Oliver Pfeiffer
Intervallen kombiniert werden können. Das kniffelige liegt darin, dass ein
"Das Kniffelige"
Post by Oliver Pfeiffer
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
"Untertyps"
Post by Oliver Pfeiffer
jedoch Supertyps) kombiniert werden dürfen. Das ganze muss natürlich ohne
"Obertyps"
Post by Oliver Pfeiffer
Overloading nur mit Overriding erfolgen, ansonsten verliert man die
"Überladen nur mit Überlagern" (nach Guido Krüger)
Oliver Pfeiffer
2006-10-17 12:06:57 UTC
Permalink
Post by Stefan Ram
Post by Oliver Pfeiffer
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt,
Ich sehe in Deinem Posting weder Java-Pseudo-Code noch einen
MIME-Anhang.
Vermutlich weil ich den Code unterhalb der Signatur eingefügt habe.
Das war in der Tat ein wenig ungeschickt.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 12:12:11 UTC
Permalink
Post by Stefan Ram
Post by Oliver Pfeiffer
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt,
Ich sehe in Deinem Posting weder Java-Pseudo-Code noch einen
MIME-Anhang.
Post by Oliver Pfeiffer
Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
"Intervallklassen"
Post by Oliver Pfeiffer
die jeweils mit einer combine(Interval):Interval Methode mit anderen
"combine(Interval):Interval-Methode"
Post by Oliver Pfeiffer
Intervallen kombiniert werden können. Das kniffelige liegt darin, dass ein
"Das Kniffelige"
Post by Oliver Pfeiffer
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
"Untertyps"
Post by Oliver Pfeiffer
jedoch Supertyps) kombiniert werden dürfen. Das ganze muss natürlich ohne
"Obertyps"
Post by Oliver Pfeiffer
Overloading nur mit Overriding erfolgen, ansonsten verliert man die
"Überladen nur mit Überlagern" (nach Guido Krüger)
Ich glaube, korrekte oder unkorrekte Rechtschreibung, ist nicht das
Problem, beim Verständnis von Olivers Posting. Und der Gebrauch der
"Fachbegriffe" hält sich im denglishen Rahmen, dessen was hier
üblicherweise an Welsch gekaudert wird.

cu
Stefan Waldmann
2006-10-17 12:56:27 UTC
Permalink
Post by Stefan Ram
Post by Oliver Pfeiffer
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt,
Ich sehe in Deinem Posting weder Java-Pseudo-Code noch einen
MIME-Anhang.
Post by Oliver Pfeiffer
Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
"Intervallklassen"
Post by Oliver Pfeiffer
die jeweils mit einer combine(Interval):Interval Methode mit anderen
"combine(Interval):Interval-Methode"
Post by Oliver Pfeiffer
Intervallen kombiniert werden können. Das kniffelige liegt darin, dass ein
"Das Kniffelige"
Post by Oliver Pfeiffer
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
"Untertyps"
Post by Oliver Pfeiffer
jedoch Supertyps) kombiniert werden dürfen. Das ganze muss natürlich ohne
"Obertyps"
Post by Oliver Pfeiffer
Overloading nur mit Overriding erfolgen, ansonsten verliert man die
"Überladen nur mit Überlagern" (nach Guido Krüger)
Mir scheint du hast in letzter Zeit nicht genug zu Meckern gehabt und
holst dies hiermit gerade nach...

Es ist dir überlassen, wenn du alle Fachbegriffe krampfhaft ins Deutsche
übersetzen willst, auch wenn man dann zum Teil kaum mehr versteht was
gemeint ist. Aber anderen aufzwingen brauchst du das nun wirklich nicht.

Hättest du wenigstens noch was zur Lösung von Olivers Problem
beigetragen, hätte ich dir das rummäkeln ja auch vergönnt, aber so...
--
Programmierer [m], seltener auch ~in [w]:
Irdische, i.a. humanoide Lebensform, die in einem komplizierten
biochemischen Prozess Kaffee, Cola und Pizza in maschinenlesbaren
Programmcode umwandelt.
Oliver Pfeiffer
2006-10-17 11:59:38 UTC
Permalink
Oliver Pfeiffer <***@gmx.net> wrote in news:***@pfeiffer.myfqdn.de:

Es sind mehrere offensichtliche Probleme in meinem Java-Pseudo-Code
Post by Oliver Pfeiffer
static class Interval<I extends Interval> {
Rekursive bounded wildcards wie obigen lassen sich ohne <?> nicht typsicher
instanziieren!
Post by Oliver Pfeiffer
I combine(I iv) {
iv.method();
return new I();
}
Die Signatur müsste eigentlich lauten ...
Post by Oliver Pfeiffer
Interval<I> combine(I iv)
... ansonsten lassen sich keine neuen Intervalle generieren; new I()
funktioniert natürlich nicht. Allerdings würde bei oben aufgeführter
Alternative die Kaskadierbarkeit verschiedener combine(I):I Aufrufe der
Subtypen verloren gehen.
Post by Oliver Pfeiffer
static class Combinator<I extends Interval> {
I trippleCombine(I i1, I i2, I i3) {
return i1.combine(i2).combine(i3);
}
}
Auch bei obiger Deklaration würde der Compiler meckern, da I nach oben
unbekannt ist.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Oliver Pfeiffer
2006-10-17 12:04:06 UTC
Permalink
Post by Oliver Pfeiffer
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt, der weder type-safe noch compilierbar ist.
Möglicherweise wird der Code bei meinem Original-Posting bei einigen
Newsreadern nicht korrekt angezeigt, weil meine Signatur (--) darüber
platziert wurde. Hier also nochmal ohne Signatur. Sorry!


class Test {

static class Interval<I extends Interval> {

I combine(I iv) {
iv.method();
return new I();
}

void method() {
// something interval specific
}

}

static class Subinterval<I extends Subinterval> extends Interval<I> {

@Override
I combine(I iv) {
iv.submethod();
return new I();
}

void submethod() {
// something subinterval specific
}

}

static class Combinator<I extends Interval> {

I trippleCombine(I i1, I i2, I i3) {
return i1.combine(i2).combine(i3);
}

}

public static void main(String[] args) {
Subinterval<Subinterval> a = new Subinterval<Subinterval>();
Subinterval<Subinterval> b = new Subinterval<Subinterval>();
Subinterval<Subinterval> c = new Subinterval<Subinterval>();

Subinterval<Subinterval> result =
new Combinator<Subinterval>().trippleCombine(a, b, c);
}

}
Ralf Ullrich
2006-10-17 13:36:52 UTC
Permalink
Post by Oliver Pfeiffer
Ich habe hier ein wirklich unangenehmes und leider bedeutendes Problem, das
man eventuell mit Generics lösen kann (oder auch nicht?). Vielleicht ist
auch der von mir eingeschlagene Weg bereits im Grundsatz falsch. Auf jeden
Fall würde ich mich über jede Hilfe freuen!
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt, der weder type-safe noch compilierbar ist. Das
ganze dient lediglich dazu, die Problematik zu verdeutlichen.
Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
die jeweils mit einer combine(Interval):Interval Methode mit anderen
Intervallen kombiniert werden können. Das kniffelige liegt darin, dass ein
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
jedoch Supertyps) kombiniert werden dürfen. Das ganze muss natürlich ohne
Overloading nur mit Overriding erfolgen, ansonsten verliert man die
Combinator Flexibilität. Casten oder instanceof würde einen Verlust der
Typsicherhiet bedeuten.
Also, lösen wir uns mal davon dass es sich um Intervalle handelt, ich
glaube nämlich das lenkt nur vom eigentlichen Problem ab, weil eben jeder
hier eine feste Vorstellung davon hat, was ein Intervall sein soll und wie
es sich verhält.

Dann haben wir also eine Typhierarchie:

A extends Object
B extends A
C extends A

und wir haben eine Operation o (also dein combine), die sich bezüglich der
Typen so verhalten soll:

A o A -> A
A o B -> B
B o A -> nicht erlaubt (ergo compile-time-error), weil Kombination mit Supertyp nicht erlaubt sein soll.

Habe ich dich soweit korrekt verstanden?

Wenn ja, dann ist schonmal deine Implementation von tripleCombine (hier
tC(x,y,z) geschrieben) problematisch, denn:

tC(x,y,z) := (x o y) o z

ist deine Definition und Implementation. Das heißt aber:

tC(A,B,A) und tC(B,A,A)

wären nicht erlaubt,

tC(A,A,B)

dagegen schon. Erwartet hätte ich, dass die Reihenfolge der Argumente im
tripleCombine keine Rolle spielt. Allerdings hätte ich auch schon erwartet
dass gilt A o B = B o A -> A, dass das Ergebnis also den allgemeinen
Obertyp hat und nicht den spezifischsten Untertyp. Es geht noch weiter:

tC(A,B,C)

ist nicht erlaubt, obwohl

A o B und A o C

beide erlaubt sind. Auch das widerspräche meiner Erwartung.

Nun kannst du ja alles genau so gemeint haben, wie ich es eben nicht
erwarte, aber aus deinem Posting, kann ich das bislang eben nicht sicher
herauslesen.

Also, kannst du nun nochmal versuchen, etwas abstrakter zu formulieren,
was du genau haben willst? (Ob du dann den Java-Compiler dazu bringen
kannst sich mit dem Typ-System genau so zu verhalten, wie du es wünschst,
steht auf einem anderen Blatt, aber bislang versteht hier glaube ich
keiner, was du eigentlich erreichen willst, was aber Voraussetzung für
weitere Hilfe wäre.)

cu
Oliver Pfeiffer
2006-10-17 14:17:30 UTC
Permalink
Post by Ralf Ullrich
Also, lösen wir uns mal davon dass es sich um Intervalle handelt, ich
glaube nämlich das lenkt nur vom eigentlichen Problem ab, weil eben
jeder hier eine feste Vorstellung davon hat, was ein Intervall sein
soll und wie es sich verhält.
Gerne! Wie gesagt, ich bin über jede Hilfe dankbar, da dieses Problem
gerade ein ziemlicher Blocker ist. Der Code sollte eh nur dem besseren
Verständnis dienen, wobei gerade die Rekursive Klassentypsierung etc. eher
verwirrend ist.
Post by Ralf Ullrich
A extends Object
B extends A
C extends A
und wir haben eine Operation o (also dein combine), die sich bezüglich
A o A -> A
A o B -> B
B o A -> nicht erlaubt (ergo compile-time-error), weil Kombination
mit Supertyp nicht erlaubt sein soll.
Nein, das ist nicht ganz richtig so. Jeder Typ soll mit einem beliebigen
Subtyp unter Verwendung der Operation, deren generische Signatur in der
Rootklasse definiert ist, aufgerufen werden können. Wobei der Ergebnistyp
immer dem der Operationsklasse entspricht, da der Operationstyp die neue
Instanz (von sich selbst) liefern muss und nichts über den überwiesenen
(evntl. Sub-)Typ weiß. Die zusätzlichen Informationen eines Subtyps würden
hier unberücksichtigt bleiben.

Also sieht das Verhalten so aus:

A o A -> A
A o B -> A
B o B -> B
B o A -> nicht erlaubt (ergo compile-time-error), weil Kombination
mit Supertyp nicht erlaubt sein soll.
B o C -> nicht erlaubt (ergo compile-time-error), weil Kombination
mit Fremdtyp nicht erlaubt sein soll.
Post by Ralf Ullrich
Habe ich dich soweit korrekt verstanden?
s.o.
Post by Ralf Ullrich
Wenn ja, dann ist schonmal deine Implementation von tripleCombine
Da der Ergebnistyp immer der Operationsklasse entspricht, sollte das für
alle typgleichen Permutationen funktionieren. Hierbei muss berücksichtigt
werden, dass die tripleCombine ebenfalls typisiert ist und in dem Beispiel
B gar nicht als A verwendet werden kann, da ein impliziter Cast hier nicht
typkompatibel wäre!?
Post by Ralf Ullrich
tC(x,y,z) := (x o y) o z
tC(A,A,A) := (A o A) o A = A o A = A
Post by Ralf Ullrich
Also, kannst du nun nochmal versuchen, etwas abstrakter zu
formulieren, was du genau haben willst?
Ich hoffe, es ist jetzt ein wenig klarer geworden?
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 14:47:33 UTC
Permalink
Post by Ralf Ullrich
Post by Ralf Ullrich
A extends Object
B extends A
C extends A
und wir haben eine Operation o (also dein combine), die sich bezüglich
A o A -> A
A o B -> A
B o B -> B
B o A -> nicht erlaubt (ergo compile-time-error), weil Kombination
mit Supertyp nicht erlaubt sein soll.
B o C -> nicht erlaubt (ergo compile-time-error), weil Kombination
mit Fremdtyp nicht erlaubt sein soll.
Hmm. Ist das nicht immer noch inkonsequent:

A a = new A();
B b = new B();

A x = b; // zulässig weil B extends A

b.combine(a); // unzulässig nach deinen Wünschen
x.combine(a); // zulässig nach deinen Wünschen, also auch:

((A)b).combine(a); // zulässig nach deinen Wünschen.

Es geht sogar noch schlimmer:

void doCombine(A x, A y) {
x.combine(y);
}

doCombine(new B(), new C()); //kann keinen compile-time-error geben, weil ja tatsächlich beide auch A sind!

Solche Sachen solltest du noch genauer untersuchen, vorher brauchst du gar
nicht darüber nachdenken, dafür Generics zu missbrauchen. Du kannst nur
das in Generics abbilden, was zur compile-time bekannt ist. In obigen
Beispielen ist aber nicht bekannt, dass x und y nicht nur bloße A sind.

cu
Oliver Pfeiffer
2006-10-17 15:00:40 UTC
Permalink
Post by Ralf Ullrich
A a = new A();
B b = new B();
A x = b; // zulässig weil B extends A
Du hast hier außer Acht gelassen, dass A und B typisiert und somit nicht
zuweisungkompatibel im Sinne der Vererbung sind.

LinkedList<String> ls = null;
List<Object> lo = ls; // unzulässig!

B<B> b = new B<B>();
A<A> a = b; // unzulässig!
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 15:11:12 UTC
Permalink
Post by Oliver Pfeiffer
Post by Ralf Ullrich
A a = new A();
B b = new B();
A x = b; // zulässig weil B extends A
Du hast hier außer Acht gelassen, dass A und B typisiert und somit nicht
zuweisungkompatibel im Sinne der Vererbung sind.
LinkedList<String> ls = null;
List<Object> lo = ls; // unzulässig!
B<B> b = new B<B>();
A<A> a = b; // unzulässig!
Falsch, wir haben gesagt:

B extends A

und das heißt dann eben nach Parametrisierung auch es muss ein X und Y
geben, so dass:

B<Y> extends A<X>

Ergo kann ich schreiben

A<X> a = new A<X>();
B<Y> b = new B<Y>();

A<X> x = b; // zulässig weil B extends A, resp. B<Y> extends A<X>

Und damit kann ich dann wieder den Widerspruch bauen:

b.combine(a); // unzulässig
x.combine(a); // zulässig


cu
Oliver Pfeiffer
2006-10-17 15:18:18 UTC
Permalink
Post by Ralf Ullrich
B extends A
und das heißt dann eben nach Parametrisierung auch es muss ein X und Y
B<Y> extends A<X>
Hier ein kurzer Auszug aus dem Sun Generics Tutorial:

"In general, if Foo is a subtype (subclass or subinterface) of Bar, and G
is some generic type declaration, it is not the case that G<Foo> is a
subtype of G<Bar>. This is probably the hardest thing you need to learn
about generics, because it goes against our deeply held intuitions."
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 15:42:56 UTC
Permalink
Post by Oliver Pfeiffer
Post by Ralf Ullrich
B extends A
und das heißt dann eben nach Parametrisierung auch es muss ein X und Y
B<Y> extends A<X>
"In general, if Foo is a subtype (subclass or subinterface) of Bar, and G
is some generic type declaration, it is not the case that G<Foo> is a
subtype of G<Bar>. This is probably the hardest thing you need to learn
about generics, because it goes against our deeply held intuitions."
Und wo ist der Widerspruch zu dem was ich geschrieben habe?

Ich habe ja nicht behauptet dass auch gilt Y extends X, sondern nur, dass
es ein X und Y gibt. Ich habe noch nichtmal behauptet, dass X oder Y ohne
Wildcards zu schreiben wären.

Und B extends A war ja eine Voraussetzung auf die wir uns für die
Diskussion geeinigt hatten.

Wenn du nun plötzlich bestreitest dass B extends A gilt, dann kann auch
A o B nicht mehr ein Ergebnis vom Typ A haben, sondern muss ja dann
verboten sein, weil B dann effektiv ein Fremdtyp ist.

Du musst dich schonmal entscheiden was du nun eigentlich haben willst und
welche "Axiome" du für deine "Rechenregeln" gelten lassen willst.

cu
Oliver Pfeiffer
2006-10-17 15:56:31 UTC
Permalink
Post by Ralf Ullrich
Ich habe ja nicht behauptet dass auch gilt Y extends X, sondern nur,
dass es ein X und Y gibt. Ich habe noch nichtmal behauptet, dass X
oder Y ohne Wildcards zu schreiben wären.
Das ist richtig! :)
Post by Ralf Ullrich
Ergo kann ich schreiben
A<X> a = new A<X>();
B<Y> b = new B<Y>();
A<X> x = b; // zulässig weil B extends A, resp. B<Y> extends A<X>
Und das geht in Java nur, wenn A und B normale (nicht typisierbare) Klassen
sind.
Post by Ralf Ullrich
Wenn du nun plötzlich bestreitest dass B extends A gilt, dann kann
auch A o B nicht mehr ein Ergebnis vom Typ A haben, sondern muss ja
dann verboten sein, weil B dann effektiv ein Fremdtyp ist.
Auch dieses wäre für meinen Anwendungsfall völlig ok. Die Einschränkung,
dass nur diesselben Typen miteinander in Relation gestellt werden dürfen,
macht es möglicherweise einfacher und entspricht auch eher dem Java
Generics Erwartungen.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 16:35:03 UTC
Permalink
Post by Oliver Pfeiffer
Post by Ralf Ullrich
Ich habe ja nicht behauptet dass auch gilt Y extends X, sondern nur,
dass es ein X und Y gibt. Ich habe noch nichtmal behauptet, dass X
oder Y ohne Wildcards zu schreiben wären.
Das ist richtig! :)
Post by Ralf Ullrich
Ergo kann ich schreiben
A<X> a = new A<X>();
B<Y> b = new B<Y>();
A<X> x = b; // zulässig weil B extends A, resp. B<Y> extends A<X>
Und das geht in Java nur, wenn A und B normale (nicht typisierbare) Klassen
sind.
Ahja? Soso? Hmm.

interface A<T> {}

interface B<T> extends A<B<T>> {}

public static <Y> void main(String[] args) {
B<Y> b = null;
// X = B<Y>
A<B<Y>> a = b;
}

Beachte: Was ich für X einsetzen muss hängt davon ab, welche Beziehung bei
der Definition von B<Y> extends A<X> zwischen den Parametrisierungen
hergestellt wird. Sobald du aber Bounds benutzt (also super, extends) kann
es natürlich sehr schnell passieren, dass du X in Abhängigkeit von Y nicht
mehr in gültiger Java-Syntax hinschreiben kannst. Das darfst du aber nicht
damit verwechseln, dass X nicht gibt.

Du darfst nur nicht glauben, dass X und Y simple Namen sind, sondern es
sind Platzhalter für Typ-Ausdrücke, eben Parameter.

cu

PS: Falls dir das Beispiel noch zu trivial ist, folgendes geht geauso:

interface A<T extends A> {}

interface B<T extends B> extends A<B<T>> {}

public static <Y extends B> void main(String[] args) {
B<Y> b = null;
// X = B<Y>
A<B<Y>> a = b;
}
Oliver Pfeiffer
2006-10-17 16:46:57 UTC
Permalink
Post by Ralf Ullrich
interface A<T> {}
interface B<T> extends A<B<T>> {}
public static <Y> void main(String[] args) {
B<Y> b = null;
// X = B<Y>
A<B<Y>> a = b;
}
Ok, wir haben glaube ich aneinander vorbei geschrieben. Dein Beispiel
funktioniert so natürlich ohne Einwände. :) Aber diese Abhängigkeit
zwischen den Typparametern wäre in meinem Fall sogar verzichtbar. Die
Schwierigkeit, das Ganze in derselben Klasse zu lösen, resultiert
vermutlich daraus, dass für Generics kein Typparameter für die
Implementationsklasse existiert.

Momentan denke ich darüber nach, die Funktionalität aus der Klasse
rauszuziehen, und die verschiedenen Signaturen in einem speziellen
Combinator Interface zu verstecken.

Aber vielleicht findet sich ja mit Deiner Hilfe noch eine Lösung, mir
raucht momentan ein wenig der Kopf vor lauter eckigen Klammern und
Fragezeichen.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 16:50:29 UTC
Permalink
Post by Oliver Pfeiffer
Die
Schwierigkeit, das Ganze in derselben Klasse zu lösen, resultiert
vermutlich daraus, dass für Generics kein Typparameter für die
Implementationsklasse existiert.
Doch existiert, siehe meine Antwort an Jochen.

cu
Oliver Pfeiffer
2006-10-17 17:06:30 UTC
Permalink
Post by Ralf Ullrich
Post by Oliver Pfeiffer
Die
Schwierigkeit, das Ganze in derselben Klasse zu lösen, resultiert
vermutlich daraus, dass für Generics kein Typparameter für die
Implementationsklasse existiert.
Doch existiert, siehe meine Antwort an Jochen.
Langsam wird es immer aufregender! :)) Wie würdest Du denn hiermit in
Anlehnung an mein Code-Beispiel den Signaturkonflikt mit den verschiedenen
Rückgabewerten je Subklasse lösen? Innerhalb von combine() kann man ja nur
die Operationsklasse instanziieren, die aber erstmal nicht kompatibel mit
dem Rückgabewert der Operation wäre.
Post by Ralf Ullrich
static class Interval<I extends Interval> {
I combine(I iv) {
return new Interval<I>();
}
}
static class Subinterval<I extends Subinterval> extends Interval<I> {
I combine(I iv) {
return new Subinterval<I>();
}
}
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 17:11:34 UTC
Permalink
Post by Oliver Pfeiffer
Post by Ralf Ullrich
Post by Oliver Pfeiffer
Die
Schwierigkeit, das Ganze in derselben Klasse zu lösen, resultiert
vermutlich daraus, dass für Generics kein Typparameter für die
Implementationsklasse existiert.
Doch existiert, siehe meine Antwort an Jochen.
Langsam wird es immer aufregender! :)) Wie würdest Du denn hiermit in
Anlehnung an mein Code-Beispiel den Signaturkonflikt mit den verschiedenen
Rückgabewerten je Subklasse lösen? Innerhalb von combine() kann man ja nur
die Operationsklasse instanziieren, die aber erstmal nicht kompatibel mit
dem Rückgabewert der Operation wäre.
Post by Ralf Ullrich
static class Interval<I extends Interval> {
I combine(I iv) {
return new Interval<I>();
}
}
static class Subinterval<I extends Subinterval> extends Interval<I> {
I combine(I iv) {
return new Subinterval<I>();
}
}
Was muss ich da lösen? Der Compiler erzeugt doch für SubInterval bereits
eine synthetische Methode:

synthetic Interval combine(Interval iv) {
if (iv instanceof SubInterval) {
return combine((SubInterval)iv);
}
throw ClassCastException();
}

Oder so ähnlich.

????

cu
Ralf Ullrich
2006-10-17 17:14:55 UTC
Permalink
Post by Ralf Ullrich
Was muss ich da lösen? Der Compiler erzeugt doch für SubInterval bereits
Genauer eine Bridge und die sieht so aus:

// access flags 4160
volatile bridge combine(Lde/jnana/dclj/interval/Interval;)Lde/jnana/dclj/interval/Interval;
L0 (0)
LINENUMBER 1 L0
ALOAD 0
ALOAD 1
CHECKCAST de/jnana/dclj/interval/Subinterval
INVOKEVIRTUAL de/jnana/dclj/interval/Subinterval.combine(Lde/jnana/dclj/interval/Subinterval;)Lde/jnana/dclj/interval/Subinterval;
ARETURN
MAXSTACK = 2
MAXLOCALS = 2
Oliver Pfeiffer
2006-10-17 17:27:08 UTC
Permalink
Post by Ralf Ullrich
Post by Oliver Pfeiffer
static class Interval<I extends Interval> {
I combine(I iv) {
return new Interval<I>();
}
}
static class Subinterval<I extends Subinterval> extends Interval<I> {
I combine(I iv) {
return new Subinterval<I>();
}
}
Was muss ich da lösen?
In dem Pseudo Beispiel hinkt es ja leider vorne und hinten. Insbesondere
die Return Anweisung lässt sich wegen der Typinkompatibilität so nicht
compilieren. Ich würde jetzt erwarten, dass man entweder erheblich an der
Typisierung schrauben muss, um eine Lösung zu formulieren, die ich so nicht
sehe; oder man könnte die Combinator Funktionalität aus der Klasse
herausziehen, was ich so eigentlich nicht möchte (alleine schon des
Lerneffekts wegen). Meinst Du denn, dass es hierfür eine All-In-One Lösung
als generische Klassen-/Methodensignatur gibt oder ist bei der
Problembeschreibung noch irgendwas ungeklärt?

PS: Ein großes Danke auf jeden Fall schonmal für Dein tolles Engagement!
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Oliver Pfeiffer
2006-10-17 17:14:29 UTC
Permalink
Post by Oliver Pfeiffer
static class Interval<I extends Interval> {
I combine(I iv) {
return new Interval<I>();
}
}
static class Subinterval<I extends Subinterval> extends Interval<I> {
I combine(I iv) {
return new Subinterval<I>();
}
}
PS: Das Code Beispiel soll übrigends nur zeigen, was eigentlich hier
benötigt wird. Diese Art der Typisierung führt natürlich nicht mal in die
Nähe von compilierbaren Quellcode. ;(

Am Ende sollte dann folgendes möglich sein (diesmal ohne Typparameter):

Subinterval a = new Subinterval();
Subinterval b = new Subinterval();
Subinterval c = a.combine(b);

Wie gesagt, folgendes wird nicht zwingend benötigt (stört aber auch nicht):

Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = a.combine(b);

Wogegen folgendes nicht erlaubt sein sollte:

Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = b.combine(a);
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 17:38:51 UTC
Permalink
Post by Oliver Pfeiffer
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = a.combine(b);
Was heißt jetzt nicht zwingend? Aus deinem OP: "Das kniffelige liegt
darin, dass ein
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
jedoch Supertyps) kombiniert werden dürfen."

Sind Subtypen jetzt erlaubt oder nicht?
Post by Oliver Pfeiffer
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = b.combine(a);
Und wie soll das (=das Verbieten) gehen solange SubSubinterval extends
Subinterval gilt?

Wenn S ein Subtype von C ist, C eine (public) Methode m hat, welche ein
Argument von Typ C verlangt,
s ein Objekt des Typs S, und c ein Objekt des Typs C ist, dann ist

s.m(c)

immer ein gültiger Ausdruck.

Und S muss ein Subtype von C sein, denn sonst ist c.m(s) kein gültiger
Ausdruck, soll es aber sein. (Oder nun doch wieder nicht?)


Ich geb's jetzt auf, weil du offensichtlich selber nicht weißt, welche
Voraussetzungen eigentlich erfüllt sein müssen, sondern immer zwischen
verschiedenen Behauptungen hin und her springst.

So long.
Oliver Pfeiffer
2006-10-17 18:07:24 UTC
Permalink
Post by Ralf Ullrich
Ich geb's jetzt auf, weil du offensichtlich selber nicht weißt, welche
Voraussetzungen eigentlich erfüllt sein müssen, sondern immer zwischen
verschiedenen Behauptungen hin und her springst.
Sicherlich ist es schwierig, solche Sachverhalte zu formulieren, deswegen
hatte ich ja auch einen Abschnitt Pseudo-Code dazugefügt, in der Hoffnung
damit die sprachlichen Lücken auszugleichen. :)

Und ich versichere Dir, dass ich nicht mutwillig zwischen verschiedenen
Behauptungen hin- und herspringe. Die einzige Einschränkung, bei der ich
zurück gerudert bin, bezieht sich auf die Subtypen (siehe mein anderes
Posting). Mir würde also auch eine Methodensignatur reichen, bei der nur
Intervalle desselben Typs kombinierbar sind. Ist mein Initialbeispiel mit
dem Quellcode wirklich so unverständlich (mal von dem falschen 'new I()'
abgesehen)?

Mach doch mal einen konkreten Vorschlag, wie ich die Unklarheiten besser
verdeutlichen kann? Soll ich ein kurzes Beispielprogramm schreiben, bei der
ich die entscheidenen Stellen mit instanceof einschränke?
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 19:15:01 UTC
Permalink
Post by Oliver Pfeiffer
Post by Ralf Ullrich
Ich geb's jetzt auf, weil du offensichtlich selber nicht weißt, welche
Voraussetzungen eigentlich erfüllt sein müssen, sondern immer zwischen
verschiedenen Behauptungen hin und her springst.
Sicherlich ist es schwierig, solche Sachverhalte zu formulieren, deswegen
hatte ich ja auch einen Abschnitt Pseudo-Code dazugefügt, in der Hoffnung
damit die sprachlichen Lücken auszugleichen. :)
Mir würde also auch eine Methodensignatur reichen, bei der nur
Intervalle desselben Typs kombinierbar sind.
Das heißt:

Gegeben sei folgende Hierarchie:

O
I extends O
U extends I

Und folgende Objekte-Referenzen:

O o = ...;
I i = ...;
U u = ...;

Gesucht ist eine Methodendeklaration für "? m(? x)", so dass, gilt:

[1] o.m(o) ==> zulässiger Ausdruck vom Typ O (MUSS)
[2] o.m(i) ==> compile-time error (SOLL)
[3] o.m(u) ==> compile-time error (SOLL)

[4] i.m(o) ==> compile-time error (MUSS)
[5] i.m(i) ==> zulässiger Ausdruck vom Typ I (MUSS)
[6] i.m(u) ==> compile-time error (SOLL)

[7] u.m(o) ==> compile-time error (MUSS)
[8] u.m(i) ==> compile-time error (MUSS)
[9] u.m(u) ==> zulässiger Ausdruck vom Typ U (MUSS)
Post by Oliver Pfeiffer
Mach doch mal einen konkreten Vorschlag, wie ich die Unklarheiten besser
verdeutlichen kann? Soll ich ein kurzes Beispielprogramm schreiben, bei der
ich die entscheidenen Stellen mit instanceof einschränke?
Habe ich hiermit gemacht. Beachte: O ist nur ein Platzhalter, da kann in
Wirklichkeit ein halber Kilometer Generic-Typ-Spezifikation stehen, á la
A<B,C<? super B>,D extends C, ....> Ebenso kann es dann einen ganzen
Kilometer für I geben und zwei Kilometer für U. Aber ganz egal wie
kompliziert der Typ-Ausdruck an diesen Stellen auch sein mag. Es gilt dann
immer noch I extends O, und U extends I.

Alles Klar?

So.

Erstes Problem: Wenn

[1] o.m(o);

zulässig ist, dann ist auch

((O)i).m(o);

zulässig. Und da dieser explizite Cast nach Java Typregeln nicht notwendig
ist, ist folglich auch

[4] i.m(o);

zulässig, hat allerdings nicht mehr zwingend den Typ O sondern ggf. '?
extends O', d.h. einen Typ, der zuweisungskompatibel zu O ist.

Aus Symmetriegründen müssen dann auch

[7] u.m(o);

und

[8] u.m(i);

zulässig sein.

Damit haben wir also schonmal drei MUSS-Kriterien, die über die Java
Typregeln, dem ersten MUSS Kriterium widersprechen.

Du musst jetzt entscheiden willst du lieber

a) auf die drei "MUSS compile-time error werfen", oder
b) auf das eine "o.m(o) MUSS ein Ergebnis vom Typ O haben" verzichten?

(Naja, eigentlich hast du keine Wahl, wenn du auf b) verzichtest, dann
verzichtest du effektiv auf [1], [5] und [9], also bliebe nicht mehr viel
zum Implementieren übrig.)

Du verzichtest also auf [4], [7], [8] und wir fassen wir nochmal neu
Zusammen:

[1] o.m(o) ==> zulässiger Ausdruck vom Typ O (MUSS)
[2] o.m(i) ==> compile-time error (SOLL)
[3] o.m(u) ==> compile-time error (SOLL)

[4] i.m(o) ==> zulässiger Ausdruck vom Typ ? extends O (folgt aus [1])
[5] i.m(i) ==> zulässiger Ausdruck vom Typ I (MUSS)
[6] i.m(u) ==> compile-time error (SOLL)

[7] u.m(o) ==> zulässiger Ausdruck vom Typ ? extends O (folgt aus [1])
[8] u.m(i) ==> zulässiger Ausdruck vom Typ ? extends O (folgt aus [1])
[9] u.m(u) ==> zulässiger Ausdruck vom Typ U (MUSS)

Zweites Problem: Wenn

[1] o.m(o);

zulässig ist, dann ist auch

o.m((O)i);

zulässig. Auch hier ist der Cast nicht zwingend nötig, kann es aber
werden, wenn m überladen wird. Letzlich ist aber somit aus o.m(i) immer
ein gültiger Ausdruck durch einen Cast machbar. Also ist

[2] o.m(i);

ein zulässiger Ausdruck vom Typ O. Dito. für [3] und [6].

Wieder hast du die Wahl auf das Werfen des Compile-Time-Fehlers zu
verzichten, wir kommen dann zu:

[1] o.m(o) ==> zulässiger Ausdruck vom Typ O (MUSS)
[2] o.m(i) ==> zulässiger Ausdruck vom Typ O (folgt aus [1])
[3] o.m(u) ==> zulässiger Ausdruck vom Typ O (folgt aus [1])

[4] i.m(o) ==> zulässiger Ausdruck vom Typ ? extends O (folgt aus [1])
[5] i.m(i) ==> zulässiger Ausdruck vom Typ I (MUSS)
[6] i.m(u) ==> zulässiger Ausdruck vom Typ I (folgt aus [5])

[7] u.m(o) ==> zulässiger Ausdruck vom Typ ? extends O (folgt aus [1])
[8] u.m(i) ==> zulässiger Ausdruck vom Typ ? extends O (folgt aus [1])
[9] u.m(u) ==> zulässiger Ausdruck vom Typ U (MUSS)

Damit haben wir gezeigt, dass schon allein mit deiner Minimal-Forderung
Post by Oliver Pfeiffer
Mir würde also auch eine Methodensignatur reichen, bei der nur
Intervalle desselben Typs kombinierbar sind.
Es keine Methodensignatur geben kann, die das von dir gewünschte Verbot
leistet, solange die Intervalle irgendwie in einer X extends Y Beziehung
zueinander stehen.

Damit sehe ich für dein Problem nur eine einzige Lösung:

interface IntervalAPI<Implementation> {
Implementation combine(Implementation o);
}

class Interval1 implements IntervalAPI<Interval1> {
Interval1 combine(Interval1 o) {
return new Interval1();
}
}

class Interval2 implements IntervalAPI<Interval2> {
Interval1 combine(Interval2 o) {
return new Interval2();
}
}


Interval1 a1 = ...
Interval1 a2 = ...
Interval2 b1 = ...
Interval2 b2 = ...

a1.combine(a2); // OK
b1.combine(b2); // OK
a1.combine(b1); // Fehler
b1.combine(a1); // Fehler


class Combinator<X extends IntervalAPI<X>> {
X tripleCombine(X a, X b, X c) {
return a.combine(b).combine(c);
}
}

Diese Lösung erfüllt deine Minimalforderung, erlaubt aber nicht mehr dass
die Eigenschaft, dass s ein Subintervall von i ist, sich auch in der
Klassenhierarchie niederschlägt. Denn wie gezeigt, sobald du das
verlangst, sind auch Ausdrücke zulässig, die du eigentlich verbieten
willst (z.B. s.m(i) ).

Wenn du es dennoch versuchst:

class Subinterval1 extends Interval1 {...}

gilt:

Interval1 i = ...
Subinterval1 s = ...

s.combine(s); // OK und vom Typ Interval1
s.combine(i); // OK und vom Typ Interval1
i.combine(s); // OK und vom Typ Interval1

cu

PS: Übrigens ist auch diese Lösung am Ende nicht "manipulationssicher":

class Interval3 implements IntervalAPI<Interval1> { ... }
Interval1 x = new Interval3().combine(new Interval1()); // OK!

aber dafür:

new Interval3().combine(new Interval3()); // Fehler!

und an einen Combinator<Interval1> kannst du ein Interval3-Objekt auch
nicht übergeben. (Aber an einen Combinator<? extends
IntervalAPI<Interval1>>, nur ist auf dem dann der Aufruf der
combine-Methode wegen des Wildcards im Rückgabetyp trotzdem verboten.)
Ralf Ullrich
2006-10-17 23:35:14 UTC
Permalink
Post by Oliver Pfeiffer
Subinterval a = new Subinterval();
Subinterval b = new Subinterval();
Subinterval c = a.combine(b);
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = a.combine(b);
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = b.combine(a);
Hier nochmal um Tippfehler bereinigt und mit "Beweis", dass es die
Anforderungen abdeckt:

package de.jnana.dclj.interval;

@SuppressWarnings({"unused","null"})
public class Ullrich {
interface IntervalAPI<Implementation> {
Implementation combine(Implementation o);
}

static class Interval1 implements IntervalAPI<Interval1> {
public Interval1 combine(Interval1 o) {
return new Interval1();
}
}

static class Interval2 implements IntervalAPI<Interval2> {
public Interval2 combine(Interval2 o) {
return new Interval2();
}
}

static class Combinator<X extends IntervalAPI<X>> {
X tripleCombine(X a, X b, X c) {
return a.combine(b).combine(c);
}
}

public static void main(String[] args) {

Interval1 a1 = null;
Interval1 a2 = null;
Interval2 b1 = null;
Interval2 b2 = null;

a1.combine(a2); // OK
b1.combine(b2); // OK
// a1.combine(b1); // Fehler
// b1.combine(a1); // Fehler

Combinator<Interval1> ac = null;
Combinator<Interval2> bc = null;

ac.tripleCombine(a1, a1, a1);
// ac.tripleCombine(b1,a1,a1); // Fehler
// ac.tripleCombine(a1,b1,a1); // Fehler
// ac.tripleCombine(a1,a1,b1); // Fehler
// ac.tripleCombine(b1,b1,b1); // Fehler

// bc.tripleCombine(a1, a1, a1); // Fehler
bc.tripleCombine(b1, b1, b1);
}

/*
*
* Am Ende sollte dann folgendes möglich sein (diesmal ohne Typparameter):
*
* Subinterval a = new Subinterval();
*
* Subinterval b = new Subinterval();
*
* Subinterval c = a.combine(b);
*/

static class Subinterval implements IntervalAPI<Subinterval> {
public Subinterval combine(Subinterval o) {
return new Subinterval();
}
}

static { // Teste was erlaubt sein muss:
Subinterval a = new Subinterval();
Subinterval b = new Subinterval();
Subinterval c = a.combine(b);
// Keine Type-Fehler oder -Warnungen ==> erfüllt.
}

/*
* Wogegen folgendes nicht erlaubt sein sollte:
*
* Subinterval a = new Subinterval();
*
* SubSubinterval b = new SubSubinterval();
*
* Subinterval c = b.combine(a);
*
*/

static class SubSubinterval implements IntervalAPI<SubSubinterval> {
public SubSubinterval combine(SubSubinterval o) {
return new SubSubinterval();
}
}

static { // Teste was verboten sein muss:
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
// Subinterval c = b.combine(a);
// ==> not applicable for arguments

// Umgehungen:
// Subinterval d = b; // cannot convert

// Subinterval c = b.combine((SubSubinterval)a); // cannot cast

// Subinterval c = ((Subinterval)b).combine(a); // cannot cast

// Es gibt Type-Fehler und sie können weder versehentlich
// noch absichtlich umgangen werden.
}

/*
* Wie gesagt, folgendes wird nicht zwingend benötigt (stört aber auch
* nicht):
*
* Subinterval a = new Subinterval();
*
* SubSubinterval b = new SubSubinterval();
*
* Subinterval c = a.combine(b);
*/

static { // Bonusrunde! Teste was erlaubt sein dürfte:
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();

// Subinterval c = a.combine(b); // not applicable for arguments

// Schade. Aber OK!
}
}

Und obwohl ich jetzt alles abdecke was Oliver fordert, fürchte ich, dass
er jetzt feststellen wird, dass dies doch nicht ist, was er will. Wetten,
dass?

cu
Oliver Pfeiffer
2006-10-18 08:26:53 UTC
Permalink
Post by Ralf Ullrich
Und obwohl ich jetzt alles abdecke was Oliver fordert, fürchte ich,
dass er jetzt feststellen wird, dass dies doch nicht ist, was er will.
Wetten, dass?
Die schmerzhafteste Einschränkung in Deinem Beispiel ist, dass die
Vererbung in der Klassenhierachie nicht mehr gegeben ist. Konkret bedeutet
dies hier einen Verlust von Flexibilität und Codeduplikation. Die
speziellsten Intervalle - mit aktuell nur sehr wenigen Zeilen Quellcode -
müssten dann die Funktionalität ihrer Superklassen adaptieren.

Wobei ich gerade ausprobiere, ob eine tatsächliche Spezialisierung von
Interval zu Subinterval mit doppelter Implementation der IntervalAPI
eventuell hier zum Ergebnis führt.

static class Subinterval
extends Interval
implements IntervalAPI<Subinterval>

Solange man alle umliegenden Strukturen so initialisiert, dass nur
IntervalAPI<Subinterval> bekannt ist, tut es ja erstmal nicht weh, dass
auch IntervalAPI<Interval> implementiert wird.

Vielen Dank schonmal für Deine Anregungen. Ich jetzt teste mal, inwieweit
ich hier die aktuelle Implementation "biegen" kann. :))
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Oliver Pfeiffer
2006-10-18 08:49:03 UTC
Permalink
Post by Oliver Pfeiffer
Wobei ich gerade ausprobiere, ob eine tatsächliche Spezialisierung von
Interval zu Subinterval mit doppelter Implementation der IntervalAPI
eventuell hier zum Ergebnis führt.
Was natürlich unmittelbar gar nicht geht, da dasselbe Interface
logischerweise nicht mit verschiedenen Typisierungen implementiert werden
kann. *seufz*
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Oliver Pfeiffer
2006-10-18 08:16:23 UTC
Permalink
Post by Ralf Ullrich
Habe ich hiermit gemacht. Beachte: O ist nur ein Platzhalter, da kann
in Wirklichkeit ein halber Kilometer Generic-Typ-Spezifikation stehen,
á la A<B,C<? super B>,D extends C, ....> Ebenso kann es dann einen
ganzen Kilometer für I geben und zwei Kilometer für U. Aber ganz egal
wie kompliziert der Typ-Ausdruck an diesen Stellen auch sein mag. Es
gilt dann immer noch I extends O, und U extends I.
Ok, ich hatte ursprünglich gehofft, dass zwar die Klassen sich erweitern,
aber man durch eine einschränkende Typisierung direkt mit der
Klassensignatur der Subklasse verhindern kann, dass gilt I<typisiert>
extends O<typisiert> und man so das Schlupfloch schließen kann.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Oliver Pfeiffer
2006-10-17 18:14:19 UTC
Permalink
Post by Ralf Ullrich
Und wie soll das (=das Verbieten) gehen solange SubSubinterval extends
Subinterval gilt?
Das ist ja der Knackpunkt, ich hatte halt gehofft, dass dieses "Verbieten"
mit entsprechenden bounded wildcards möglich wäre. Wichtig ist in dem unten
aufgeführten Beispiel, dass im Falle von Subinterval#combine() die Methode
Subinterval#submethod() durch korrekte Argumenttypisierung der combine()
Methode erreichbar ist. Außerdem passt natürlich die beiden return
Anweisung zu dem spezielleren Rückgabewert I der Methode.

Hier also der leicht korrigierte Quellcode, der hoffentlich zeigen sollte,
was ich wirklich dringend versuche, zu bewerkstelligen:

class Test {

static class Interval<I extends Interval> {

I combine(I iv) {
iv.method();
return new Interval<I>();
}

void method() {
// something interval specific
}

}

static class Subinterval<I extends Subinterval> extends Interval<I> {

@Override
I combine(I iv) {
iv.submethod();
return new Interval<I>();
}

void submethod() {
// something subinterval specific
}

}

static class Combinator<I extends Interval> {

I trippleCombine(I i1, I i2, I i3) {
return i1.combine(i2).combine(i3);
}

}

public static void main(String[] args) {
Subinterval<Subinterval> a = new Subinterval<Subinterval>();
Subinterval<Subinterval> b = new Subinterval<Subinterval>();
Subinterval<Subinterval> c = new Subinterval<Subinterval>();

Subinterval<Subinterval> result =
new Combinator<Subinterval>().trippleCombine(a, b, c);
}

}
Stefan Ram
2006-10-17 22:18:38 UTC
Permalink
Oliver Pfeiffer <***@gmx.net> writes:
0>Am Ende sollte dann folgendes möglich sein (diesmal ohne Typparameter):
1>Subinterval a = new Subinterval(); [...]
2>Wogegen folgendes nicht erlaubt sein sollte:
3>Subinterval a = new Subinterval();

In Zeile 1 und 3 steht dasselbe. Das soll also möglich und
nicht erlaubt sein.
Stefan Ram
2006-10-17 22:21:18 UTC
Permalink
[Supersedes]
...
(weiteres Supersedes folgte eventuell. Dies soll nur mein
vorheriges Posting überschreiben. Sorry.)
Stefan Ram
2006-10-17 22:32:01 UTC
Permalink
Post by Oliver Pfeiffer
Subinterval a = new Subinterval();
Subinterval b = new Subinterval();
Subinterval c = a.combine(b);
Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
Subinterval c = b.combine(a);
Das erfüllt meine Lösung von vorhin:

class Subinterval<I extends Subinterval>
{ I combine( I interval ){ return null; }}

class SubSubinterval extends Subinterval<SubSubinterval>
{ public SubSubinterval combine( SubSubinterval interval )
{ return null; }}

public class Main
{ public static void main( final java.lang.String[] commandLineArguments )
{ { Subinterval a = new Subinterval();
Subinterval b = new Subinterval();
Subinterval c = a.combine(b); }

{ Subinterval a = new Subinterval();
SubSubinterval b = new SubSubinterval();
/* Subinterval c = b.combine(a); */ }

java.lang.System.out.println( "***@2006-10-18T00:30:06+02:00" ); }}
Ralf Ullrich
2006-10-17 23:07:48 UTC
Permalink
[...]


Von wegen:

package de.jnana.dclj.interval;

public class Ram {

// RTW = Raw Type Warnung

static class Subinterval<I extends Subinterval/* RTW */> {
I combine(I interval) {
return null;
}
}

static class SubSubinterval extends Subinterval<SubSubinterval> {
@Override
public SubSubinterval combine(SubSubinterval interval) {
return null;
}
}


public static void main(final java.lang.String[] commandLineArguments) {
{
Subinterval/* RTW */a = new Subinterval/* RTW */();
Subinterval/* RTW */b = new Subinterval/* RTW */();
Subinterval/* RTW */c = a/* RTW */.combine(b);
}

{
Subinterval/* RTW */a = new Subinterval/* RTW */();
SubSubinterval b = new SubSubinterval();
/* Subinterval c = b.combine(a); */

// und weil man ja Raw-Types verwendet und bei dutzenden
// RTWs ein paar mehr auch nicht mehr auffallen:
Subinterval/* RTW */ d = b;
Subinterval/* RTW */ c = d/* RTW */.combine(a);
// Also effektiv b.combine(a)

// Und da man bei so vielen RTWs irgendwann aufgibt und
// @SuppressWarnings("unchecked") vor die Methode setzt,
// bringt die Lösung effektiv KEINE TYPSICHERHEIT.
}
}
}


Ohne weiteren Kommentar.

cu
Stefan Ram
2006-10-17 23:33:58 UTC
Permalink
Post by Ralf Ullrich
Subinterval/* RTW */a = new Subinterval/* RTW */();
Subinterval/* RTW */b = new Subinterval/* RTW */();
Subinterval/* RTW */c = a/* RTW */.combine(b);
Wenn ich Dein Programm ohne die zwei zusätzlichen Zeilen
übersetze, erhalte ich nur eine Warnung:

Main.java:35: warning: [unchecked] unchecked call to combine(I) as a member of t
he raw type Main.Subinterval
Subinterval/* RTW */c = a/* RTW */.combine(b);
^
1 warning

Ich übersetze mit " -Xmaxerrs 1", vielleicht unterdrückt dies
auch die anderen Warnungen?

Ich melde mich gegebenfalls wieder, falls es keine Warnungen
mehr gibt.
Ralf Ullrich
2006-10-18 00:12:12 UTC
Permalink
Post by Stefan Ram
Ich übersetze mit " -Xmaxerrs 1", vielleicht unterdrückt dies
auch die anderen Warnungen?
Ich übersetze mit dem Eclipse Compiler, und vielen optionalen Warnungen
aktiviert. Eclipse sieht mehr als Suns javac.

Die Warnung zu "Use of Raw Type" ist per default aus, bei mir aber an.

Wenn ich sie ausschalte, verliert mein Argument aber auch nichts an Kraft,
denn:

Subinterval d = b; //keine RTW mehr

und nun nehmen wir mal an
dass es hier noch viele Zeilen Code
dazwischen gäbe, die viel machen,
zum Beispiel Ausgaben,
Fehlerbehandlungen,
oder Thread Synchronisierungen,
und dann kommen wir hierher:

Subinterval c = d.combine(a); //immer noch eine RTW

Sieht doch ganz genau so aus, wie zuvor bei der erlaubten Zeile:

Subinterval c = a.combine(b); // ebenso immer noch eine RTW

Willst du ehrlich behaupten, dir würde der Fehler, der bei der Zuweisung
an d geschehen ist, und der keine Warnung ausgibt, auffallen?

cu
Oliver Pfeiffer
2006-10-17 15:12:52 UTC
Permalink
Post by Ralf Ullrich
((A)b).combine(a); // zulässig nach deinen Wünschen.
Dies gibt eine Compiler Warning, ähnlich ...

((List<Object>) new LinkedList<String>()).add(new Object());

... das lässt sich compilieren (mit warning) und kracht zur Laufzeit.
Post by Ralf Ullrich
void doCombine(A x, A y) {
x.combine(y);
}
doCombine(new B(), new C()); //kann keinen compile-time-error
geben, weil ja tatsächlich beide auch A sind!
Auch hier würde es einen Compile-Error geben, ähnlich ...

void doCombine(List<Object> x, List<Object> y) {
x.addAll(y);
}

doCombine(new LinkedList<String>(), new LinkedList<String>());
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Ralf Ullrich
2006-10-17 14:53:27 UTC
Permalink
Post by Oliver Pfeiffer
Wie gesagt, ich bin über jede Hilfe dankbar, da dieses Problem
gerade ein ziemlicher Blocker ist.
Bevor du nicht weitermachen kannst, trenn dich von der Forderung solche
Fehler zur Compile-Time feststellen zu können und mach stattdessen
Runtime-Checks in deinem Code.

Wenn du später dann herausgefunden hast, wie es mit Generics typsicher zu
machen ist, kannst du immer noch refactor'n.

Es gibt also keinen Grund das hier zum Showstopper zu deklarieren.

cu
Stefan Ram
2006-10-17 14:59:23 UTC
Permalink
Post by Ralf Ullrich
Es gibt also keinen Grund das hier zum Showstopper zu deklarieren.
Wer den Begriff "Showstopper" nicht kennt, findet eine Erklärung in:

http://www.purl.org/stefan_ram/pub/englisch_woerter_de

(nach "show-stopper" suchen.)
Oliver Pfeiffer
2006-10-17 15:03:39 UTC
Permalink
Post by Ralf Ullrich
Bevor du nicht weitermachen kannst, trenn dich von der Forderung
solche Fehler zur Compile-Time feststellen zu können und mach
stattdessen Runtime-Checks in deinem Code.
Ja, das wäre natürlich eine Alternative.
Post by Ralf Ullrich
Es gibt also keinen Grund das hier zum Showstopper zu deklarieren.
Der Code wäre dann nicht mehr typsicher und außerdem hätte man Mehraufwand
durch die nachträgliche Anpassung. Den Mehraufwand hätte ich gerne aus
Zeitgründen von Anfang an eingespart und als netter Nebeneffekt ist die
typsichere API deutlich leichter zu handhaben für spätere Erweiterungen,
wenn man die ganzen Laufzeitprüfungen nicht mehr vor Augen hat.
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Jochen Theodorou
2006-10-17 13:55:08 UTC
Permalink
Post by Oliver Pfeiffer
Ich habe hier ein wirklich unangenehmes und leider bedeutendes Problem, das
man eventuell mit Generics lösen kann (oder auch nicht?). Vielleicht ist
auch der von mir eingeschlagene Weg bereits im Grundsatz falsch. Auf jeden
Fall würde ich mich über jede Hilfe freuen!
Ich habe das Problem auf ein Minimum reduziert und unten einen kurzen
Java-Pseudo-Code angehängt, der weder type-safe noch compilierbar ist. Das
ganze dient lediglich dazu, die Problematik zu verdeutlichen.
Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
die jeweils mit einer combine(Interval):Interval Methode mit anderen
Intervallen kombiniert werden können. Das kniffelige liegt darin, dass ein
Interval nur mit anderen Intervallen desselben oder eines Subtyps (nicht
jedoch Supertyps) kombiniert werden dürfen. Das ganze muss natürlich ohne
Overloading nur mit Overriding erfolgen, ansonsten verliert man die
Combinator Flexibilität. Casten oder instanceof würde einen Verlust der
Typsicherhiet bedeuten.
Was ich deinem Code nicht so recht entnehmen kann ist was das denn für
abgeleitete Typen sind. Deine Einschränkung mit den Typen und ihrere
Kombinierbarkeit sorgt dafür dass du zum Beispiel a.combine(b) machen
kannst, dass dann aber nciht heisst, dass du b.combine(a) machen kannst.
Und auch nur deswegen suchst du nach einer Lösung mit Generics, denn du
willst den check zur Compilezeit ob das überhaupt geht.

Ich hätte bei dem Design kein gutes Gefühl. Du hast leider nichts
geschrieben was vermuten lassen würde man könne das Problem nicht mit
einer einzigen Intervall Klasse lösen. Ausserdem wäre da eventuell die
Möglichekit eine allgemeine Intervall Klasse zu schreiben, die sich dann
aus Teilintervallen zusammensetzt.

Dann gäbe es da noch die Möglichkeit dass du die combine-Methoden nicht
direkt aufrufst, sondernüber eine statische Methode, die dann für dich
die Typen prüft.

class IntervallUtils{
static <S1 extends Intervall<T>, S2 extends S1> Intervall<T>
combineIntervalls(S1<T> sub1, S2<T> sub2){
return sub1.combine(sub2)
}
}

und Aufruf mit:

IntervallType1<T> result =
IntervallUtils.<IntervallType1<T>, ChildofIntervallType1<T>>
combineIntervalls(a,b)

habe es nicht ausprobiert, aber zumindest so ähnlich müsste es klappen.
Der Aufruf ohne die Typen ist auch möglich, dann nimmt der Commpiler den
allgemeinsten Typ:

IntervallType1<T> result =
IntervallUtils.combineIntervalls(a,b)

sollte in dem Fall die Korrektheit nciht beeinflussen.
--
Jochen "blackdrag" Theodorou
Groovy Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/
Oliver Pfeiffer
2006-10-17 14:34:58 UTC
Permalink
Post by Jochen Theodorou
Was ich deinem Code nicht so recht entnehmen kann ist was das denn für
abgeleitete Typen sind. Deine Einschränkung mit den Typen und ihrere
Kombinierbarkeit sorgt dafür dass du zum Beispiel a.combine(b) machen
kannst, dass dann aber nciht heisst, dass du b.combine(a) machen kannst.
Und auch nur deswegen suchst du nach einer Lösung mit Generics, denn du
willst den check zur Compilezeit ob das überhaupt geht.
Das ist richtig. In der Praxis sieht es so aus, dass jeder Subtyp ein paar
mehr Informationen enthält als der erweiterte Supertyp, weswegen ein Typ A
nur mit demselben Typ A oder weiteren Subtypen As kombiniert werden kann,
die zumindest alle gesichert die Informationen vom Typ A bereitstellen.
Post by Jochen Theodorou
Ich hätte bei dem Design kein gutes Gefühl. Du hast leider nichts
geschrieben was vermuten lassen würde man könne das Problem nicht mit
einer einzigen Intervall Klasse lösen. Ausserdem wäre da eventuell die
Möglichekit eine allgemeine Intervall Klasse zu schreiben, die sich dann
aus Teilintervallen zusammensetzt.
Beides so nicht möglich, da die Leistungen der verschiedenen
Intervallklassen zu unterschiedlich sind. Ein Intervall speichert z.B.
Messwerte und ein davon abgeleitetes Intervall speichert sehr spezielle
Messwerte und ein völlig anderes Intervall speichert z.B. andere
Intervalle. ;)
Post by Jochen Theodorou
Dann gäbe es da noch die Möglichkeit dass du die combine-Methoden nicht
direkt aufrufst, sondernüber eine statische Methode, die dann für dich
die Typen prüft.
class IntervallUtils{
static <S1 extends Intervall<T>, S2 extends S1> Intervall<T>
combineIntervalls(S1<T> sub1, S2<T> sub2){
return sub1.combine(sub2)
}
}
Das würde dann aber bedeuten, dass die combine() Methode in Interval selber
gar nicht mehr typsicher wäre, ansonsten könnte sie ja auch direkt
aufgerufen werden, oder liege ich da falsch?
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Jochen Theodorou
2006-10-17 16:23:14 UTC
Permalink
[...]
Post by Oliver Pfeiffer
Post by Jochen Theodorou
Dann gäbe es da noch die Möglichkeit dass du die combine-Methoden nicht
direkt aufrufst, sondernüber eine statische Methode, die dann für dich
die Typen prüft.
class IntervallUtils{
static <S1 extends Intervall<T>, S2 extends S1> Intervall<T>
combineIntervalls(S1<T> sub1, S2<T> sub2){
return sub1.combine(sub2)
}
}
Das würde dann aber bedeuten, dass die combine() Methode in Interval selber
gar nicht mehr typsicher wäre, ansonsten könnte sie ja auch direkt
aufgerufen werden, oder liege ich da falsch?
"gar nicht mehr" ist falsch, ich würde sagen weniger... mach combine
halt protected. Das problem warum das nicht direkt geht ist, dass ich
keinen Weg kenne "this" in solch einem Generics-Ausdruck zu verwenden.
Dieses staitsche combineIntervall muss weder statisch sein, noch muss es
unbedingt in einer util Klasse sein, du kannst die Methode auch in
Intervall packen und combine nennen, die ursprünglichen combine Methoden
dann eben anders. Vom Wesen her bleibt es allerdings eine statische
Methode, denn es fehlt wie gesagt die Möglichkeit "this" hier irgendwie
zu benutzen. Ich persönlich würde es als statische Methode in Intervall
packen.

Achja.. es gäbe da natürlich noch eine Möglichkeit, die gefällt mir
allerdings nciht sonderlich... egal.. schreib eine Klasse

interface IntervallCombinator<S1 extends Intervall<T>, S2 extends S1> {
Intervall<T> combine(S1 a, S2 b);
}

und dann eben eine konkrete Implementierung davon benutzen um die
Intervalle zu kombinieren. Naja, nur so Ideen


Gruss theo
--
Jochen "blackdrag" Theodorou
Groovy Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/
Ralf Ullrich
2006-10-17 16:49:47 UTC
Permalink
Das problem warum das nicht direkt geht ist, dass ich keinen Weg kenne
"this" in solch einem Generics-Ausdruck zu verwenden.
Hä?

class C<P> {

// this instanceof C<P> ==> This == C<P>

// (other instanceof Subtype) && (Subtype extends This) ==> Subtype extends C<P>


<S extends C<P>> C<P> combine(S other) { ... }
}

Wo wäre das Problem? Wozu das als static utility method machen?

cu
Oliver Pfeiffer
2006-10-17 16:58:08 UTC
Permalink
Post by Ralf Ullrich
class C<P> {
// this instanceof C<P> ==> This == C<P>
// (other instanceof Subtype) && (Subtype extends This) ==>
Subtype extends C<P>
<S extends C<P>> C<P> combine(S other) { ... }
}
Würde man hier nicht durch das C<P> als konkreten Rückgabewert die
Kaskadiermöglichkeit der combine() Aufrufe späterer Subtypen verlieren?
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Jochen Theodorou
2006-10-17 19:12:58 UTC
Permalink
Post by Ralf Ullrich
Das problem warum das nicht direkt geht ist, dass ich keinen Weg kenne
"this" in solch einem Generics-Ausdruck zu verwenden.
Hä?
class C<P> {
// this instanceof C<P> ==> This == C<P>
this instanceof C<P>, ja, aber das heisst noch lange nicht das hier eine
Gleicheit gilt.
Post by Ralf Ullrich
// (other instanceof Subtype) && (Subtype extends This) ==> Subtype extends C<P>
<S extends C<P>> C<P> combine(S other) { ... }
}
Das ist _ein_ C<P> nicht _das_ C<P>

vereinfaches wir das ganze mal extrem

class C1 {
<S extends C1> int foo(S other) {return 1;}
}

class C2 extends C1{}
class C3 extends C1{}
class C4 extends C2{}

dann kann ich jetzt folgendes machen:

C1 c1 = new C1();
C2 c2 = new C2();
C3 c3 = new C3();
c1.foo(c2);
c2.foo(c1);
c1.foo(c3);
c3.foo(c1);
c2.foo(c3);
c3.foo(c2);
c1.foo(c4);
c2.foo(c4);
c3.foo(c4);
c4.foo(c4);

und das sind ja einige ungültige Kombinationen.

wenn ich allerdings C1 so definiere:

class C1 {
<S extends C1> int foo(S other) {return 1;}
static <S1 extends C1, S2 extends S1> int bar(S1 s1, S2 s2) {return 1;}
}

C1.bar(c1,c1);
C1.bar(c1,c2);
C1.bar(c1,c3);
C1.bar(c1,c4);

C1.bar(c2,c1); // Fehler
C1.bar(c2,c2);
C1.bar(c2,c3); // Fehler
C1.bar(c2,c4);

C1.bar(c3,c1); // Fehler
C1.bar(c3,c2); // Fehler
C1.bar(c3,c3);
C1.bar(c3,c4); // Fehler

C1.bar(c4,c1); // Fehler
C1.bar(c4,c2); // Fehler
C1.bar(c4,c3); // Fehler
C1.bar(c4,c4);

jetzt sieht die Sache gleich ganz anders aus. Der Compiler prüft jetzt
was ich von ihm verlangt habe.

Ich könnte jetzt hergehen und sowas definieren:

abstract class C1<S1 extends C1, S2 extends S1> {
abstract S1 bar(S1 s1, S2 s2);
}

class C2<T extends C2> extends C1<C2,T>{
@Override
C2 bar(C2 s1, T s2) {
return new C2();
}
}

class C4 extends C2{}

class C3<T extends C3> extends C1<C3,T>{
@Override
C3 bar(C3 s1, T s2) {
return new C3();
}
}

aber ich bekomme dann ja in C2 2 bar methoden und beide sind
ansprechbar. Das ist also Mist. Und ich kann ja nicht "new S1()" machen.
Aber gut, geben wir noch nicht auf.. fügen wir halt noch eine
newInstanceMethode hinzu.

abstract class C1<S1 extends C1, S2 extends S1> {
final S1 bar(S1 s1, S2 s2) {
return newInstance();
}
abstract <T extends S1> S1 newInstance();
}

class C2<T extends C2> extends C1<C2,T>{
@Override
<T2 extends C2> C2 newInstance() {
return new C2();
}
}

hey, ich hab es geschafft das new zu ersetzen... bringt mir leider
nichts. was etwas bringen würde wäre die Regel das Subtypen wissen was
mit ihren Supertypen anzustellen ist. Allerdings war ich nicht in der
Lage sowas zu formulieren... Die statische Methode zu beginn löst das
Problem der Prüfung der Typen, aber nicht das Problem des erzeugens
eines Objektes in der Art new S1(). Letzlich scheitert auch das, denn
ich muss sowas schreiben:

abstract class C1<S1 extends C1, S2 extends S1> {
static <S1 extends C1, S2 extends S1> S2 bar(S1 s1, S2 s2) {
return (S2) s2.handle(s1);
}
protected abstract S2 handle(S1 s1);
}

class C2 extends C1{

protected @Override
C2 handle(C1 s1) {
return new C2();
}

}

und in einem C3 extends C2 besteht nicht die Bedingung auch handle(C2)
zu überschreiben. Aber gut.. sowas kann man wohl nur erreichen, wenn man
gänzlich auf Vererbung verzichtet... daher mein Vorschlag mit dem
interface... der natürlich auch nciht so toll ist.

Gruss theo
--
Jochen "blackdrag" Theodorou
Groovy Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/
Ralf Ullrich
2006-10-17 19:30:47 UTC
Permalink
Post by Jochen Theodorou
class C2<T extends C2> extends C1<C2,T>{
@Override
<T2 extends C2> C2 newInstance() {
return new C2();
}
}
Irgendwie ist dein Posting/Gedankengang ziemlich verstümmelt und
vermutlich daher auch nicht nachvollziehbar. Nur ein Beispiel: Was soll T2
hier bewirken/bedeuten?

Auch sonst fehlen etliche Parametrisierungen, von denen man nur erraten
könnte, was du dort haben wolltest.

Sorry, kann ich somit nichts erwidern. Ist aber auch egal.

cu
Jochen Theodorou
2006-10-17 20:03:24 UTC
Permalink
Post by Ralf Ullrich
Post by Jochen Theodorou
class C2<T extends C2> extends C1<C2,T>{
@Override
<T2 extends C2> C2 newInstance() {
return new C2();
}
}
Irgendwie ist dein Posting/Gedankengang ziemlich verstümmelt und
vermutlich daher auch nicht nachvollziehbar. Nur ein Beispiel: Was soll
T2 hier bewirken/bedeuten?
na T kann ich ja nicht verwenden, denn das würde das andere T unsichtbar
machen. diese <> bei newInstance hat Eclipse automatisch eingefügt, weil
es auch in C1 ist und dort ist es um den Cast nicht machen zu müssen.
Post by Ralf Ullrich
Auch sonst fehlen etliche Parametrisierungen, von denen man nur erraten
könnte, was du dort haben wolltest.
welche sollen da fehlen? die Cs habe ich ja absichtlich nicht
parameterisiert um sich nicht durch kilometerlange
Genericskonstruktionen ablenken zu lassen. Oder anders gesagt, die habe
ich absichtlich weg gelassen.

Gruss theo
--
Jochen "blackdrag" Theodorou
Groovy Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/
Ralf Ullrich
2006-10-17 21:18:30 UTC
Permalink
Post by Jochen Theodorou
Post by Ralf Ullrich
Post by Jochen Theodorou
class C2<T extends C2> extends C1<C2,T>{
@Override
<T2 extends C2> C2 newInstance() {
return new C2();
}
}
Irgendwie ist dein Posting/Gedankengang ziemlich verstümmelt und
vermutlich daher auch nicht nachvollziehbar. Nur ein Beispiel: Was soll
T2 hier bewirken/bedeuten?
na T kann ich ja nicht verwenden, denn das würde das andere T unsichtbar
machen. diese <> bei newInstance hat Eclipse automatisch eingefügt, weil
es auch in C1 ist und dort ist es um den Cast nicht machen zu müssen.
s/T2/SchnurzPiepEgal/g

in deinem obigen Code.

Nochmal: Was soll T2 dort bewirken? Es taucht nur einmal auf, und zwar in
der Definition. Du kannst es genausogut weglassen.
Post by Jochen Theodorou
Post by Ralf Ullrich
Auch sonst fehlen etliche Parametrisierungen, von denen man nur erraten
könnte, was du dort haben wolltest.
welche sollen da fehlen? die Cs habe ich ja absichtlich nicht
parameterisiert um sich nicht durch kilometerlange Genericskonstruktionen
ablenken zu lassen. Oder anders gesagt, die habe ich absichtlich weg
gelassen.
Aber auf die kommt es doch gerade an!

Und sie sind auch keineswegs so trivial, dass man sie einfach weglassen
könnte, weil sie jeder richtig errät, bzw. sie sich zwangsläufig ergeben.
Beispiel: Vervollständige doch mal deinem Code an allen Stellen, an denen
Post by Jochen Theodorou
abstract class C1<S1 extends C1<***,***>, S2 extends S1> {
static <S1 extends C1<***,***>, S2 extends S1> S2 bar(S1 s1, S2 s2) {
return (S2) s2.handle(s1);
}
protected abstract S2 handle(S1 s1);
}
class C2<***,***> extends C1<***,***> {
C2 handle(C1 s1) {
return new C2();
}
}
Und beseitige alle Folgewarnungen und -fehler bezüglich des Arguments oder
der Rückgabe von handle() (und evtl. bar()).

Trivial ist was anderes.

Klar, du kannst es auch einfach weglassen. Aber dann stellt sich die
Frage, warum nicht einfach die ganzen Generics weglassen? Solange du
Raw-Types benutzen musst um sie zu deklarieren, hast du keine wirkliche
Typsicherheit. Du kannst hier zum Beispiel nicht einfach handle ein S2
zurückgeben lassen, wenn dieses nur vom Raw-Type von C1 abhängig ist,
sonst steht Typunsicherheit Tür und Tor offen, ähnlich wie hier:

static <E> void copyList(List<? extends E> src, List<? super E> dst) {
for (E e : src) {
dst.add(e);
}
}

static List handle(List<?> l) {
return l;
}

public static void main(String[] args) {
List<String> ls = null;
List<Integer> li = null;
List<Object> lo = null;

List a, b;

copyList(ls, ls);
// copyList(ls, li); // cte (compile time error)
copyList(ls, lo);
// copyList(li, ls); // cte
copyList(li, li);
copyList(li, lo);
// copyList(lo, ls); // cte
// copyList(lo, li); // cte
copyList(lo, lo);

// und jetzt die Begründung warum handle nicht einfach
// einen Raw-Type zurückgeben darf:
copyList(handle(ls), li);
copyList(handle(li), ls);
copyList(handle(lo), ls);
copyList(handle(lo), li);
}

cu
Ralf Ullrich
2006-10-17 22:07:41 UTC
Permalink
Post by Jochen Theodorou
vereinfaches wir das ganze mal extrem
Zu extrem fürchte ich!
Post by Jochen Theodorou
class C1 {
<S extends C1> int foo(S other) {return 1;}
static <S1 extends C1, S2 extends S1> int bar(S1 s1, S2 s2) {return 1;}
}
class C2 extends C1{}
class C3 extends C1{}
class C4 extends C2{}
C1.bar(c1,c1);
C1.bar(c1,c2);
C1.bar(c1,c3);
C1.bar(c1,c4);
C1.bar(c2,c1); // Fehler
C1.bar(c2,c2);
C1.bar(c2,c3); // Fehler
C1.bar(c2,c4);
C1.bar(c3,c1); // Fehler
C1.bar(c3,c2); // Fehler
C1.bar(c3,c3);
C1.bar(c3,c4); // Fehler
C1.bar(c4,c1); // Fehler
C1.bar(c4,c2); // Fehler
C1.bar(c4,c3); // Fehler
C1.bar(c4,c4);
jetzt sieht die Sache gleich ganz anders aus. Der Compiler prüft jetzt was
ich von ihm verlangt habe.
Naja, nicht wirklich, denn je nachdem was zuvor an Zuweisungen erfolgt
ist, kann die Warnung auch sehr schnell verschwunden sein:

package de.jnana.dclj.interval;

public class Theo4 {

static class C1 {
<S extends C1> int foo(S other) {
return 1;
}

static <S1 extends C1, S2 extends S1> int bar(S1 s1, S2 s2) {
return 1;
}
}

static class C2 extends C1 {
// nop
}

static class C3 extends C1 {
// nop
}

static class C4 extends C2 {
// nop
}

@SuppressWarnings("unused")
public static void main(String[] args) {
C1 c1 = null;
C2 c2 = null;
C3 c3 = null;
C4 c4 = null;

C1 c2as1 = c2; // Keine Warnung hier!!!
C1 c3as1 = c3; // Keine Warnung hier!!!
C1 c4as1 = c4; // Keine Warnung hier!!!

C1.bar(c1, c1);
C1.bar(c1, c2);
C1.bar(c1, c3);
C1.bar(c1, c4);

// C1.bar(c2, c1); // Fehler
C1.<C1, C1> bar(c2, c1); // oder auch kein Fehler
C1.bar((C1) c2, c1); // oder auch kein Fehler
C1.bar(c2as1, c1);
C1.bar(c2, c2);
// C1.bar(c2, c3); // Fehler
C1.<C1, C1> bar(c2, c3); // oder auch kein Fehler
C1.bar((C1) c2, c3); // oder auch kein Fehler
C1.bar(c2as1, c3);
C1.bar(c2, c4);

// C1.bar(c3, c1); // Fehler
C1.<C1, C1> bar(c3, c1); // oder auch kein Fehler
C1.bar((C1) c3, c1); // oder auch kein Fehler
C1.bar(c3as1, c3);

// ... usw.
}
}

Deswegen müsstest du eben Cx durchgehend parametrisieren:

C1<A> c1;
C2<B> c2;

C1<B> c2as1 = c2;

C1.bar(c2as1,c1); // schlägt nun wirklich fehl.

Und solange du sicherstellst, dass _nicht_ gilt:

C1<B> extends C1<A>

kannst du die Fehler auch nicht mehr versehentlich über Zuweisungen oder
Rückgabewerte von Methodenverhindern, wie ich es eingangs für deine
Beispiele gezeigt habe. Das erst ist Typsicherheit!

Allerdings gibt es dann auch keinen Weg mehr

C1.bar(c1,c2);

zu erlauben. Auch das schlägt nun natürlich fehl. Aber ich habe ja im
anderen Posting an Oliver schon gezeigt, dass man das eine (hier
bar(c1,c2)) nicht ohne das andere (hier bar(c2,c1)) haben kann.

cu
Stefan Ram
2006-10-17 14:11:49 UTC
Permalink
Post by Oliver Pfeiffer
Grundsätzlich möchte ich verschiedene ableitbare Intervall Klassen bauen,
"ableitbar"?

Wenn die Klasse erweiterbar sein soll, dann verwende nicht
"final" bei der Deklaration.

Was "Intervallklasse" bedeutet, weiß ich auch nicht.
Post by Oliver Pfeiffer
die jeweils mit einer combine(Interval):Interval Methode mit anderen
Intervallen kombiniert werden können.
Du willst Klassen, die mit Intervallen kombiniert werden
können?
Post by Oliver Pfeiffer
Das kniffelige liegt darin, dass ein Interval nur mit anderen
Intervallen desselben oder eines Subtyps (nicht jedoch
Supertyps) kombiniert werden dürfen.
Eine gewagte Vermutung:

class Interval<I extends Interval>
{ I combine( I interval ){ return null; }}

class Interval0 extends Interval<Interval0>
{ public Interval0 combine( Interval0 interval )
{ return null; }}

class Interval1 extends Interval<Interval1>
{ public Interval1 combine( Interval1 interval )
{ return null; }}

class Interval2 extends Interval0 {}

public class Main
{ public static void main( final java.lang.String[]
commandLineArguments )
{ new Interval0().combine( new Interval0() );
new Interval0().combine( new Interval2() );
/* new Interval0().combine( new Interval1() ); */
/* new Interval0().combine( new Interval() ); */
java.lang.System.out.println( "***@2006-10-17T16:06:26+02:00" ); }}
Oliver Pfeiffer
2006-10-17 14:45:53 UTC
Permalink
Post by Oliver Pfeiffer
class Interval<I extends Interval>
{ I combine( I interval ){ return null; }}
class Interval0 extends Interval<Interval0>
{ public Interval0 combine( Interval0 interval )
{ return null; }}
So hatte ich auch angefangen. Allerdings verlierst Du in Interval0 durch
die nicht-generische Signatur die Möglichkeit, weitere generische Subtypen
von Interval0 anzulegen. Außerdem müsste natürlich jede combine() Operation
eine neue Instanz des Operationstyps zurückliefern.
Post by Oliver Pfeiffer
Die Signatur müsste eigentlich lauten ...
Post by Oliver Pfeiffer
Interval<I> combine(I iv)
... ansonsten lassen sich keine neuen Intervalle generieren; new I()
funktioniert natürlich nicht. Allerdings würde bei oben aufgeführter
Alternative die Kaskadierbarkeit verschiedener combine(I):I Aufrufe der
Subtypen verloren gehen.
Aber schonmal danke für Deinen Vorschlag!
--
Grüße - Regards
Oliver Pfeiffer
ICQ-ID 84320006
Oliver Pfeiffer
2006-10-20 10:42:57 UTC
Permalink
Post by Oliver Pfeiffer
Ich habe hier ein wirklich unangenehmes und leider bedeutendes
Problem, das man eventuell mit Generics lösen kann (oder auch nicht?).
Vielleicht ist auch der von mir eingeschlagene Weg bereits im
Grundsatz falsch. Auf jeden Fall würde ich mich über jede Hilfe
freuen!
Ich habe mit Hilfe der zahlreichen Lösungsvorschläge aus diesem Thread nun
folgende Variante implementiert, die für mich die wenigsten Kompromisse
bedeutet und immer noch angenehm zu nutzen ist! Herzlichen Dank an dieser
Stelle für die rege Beteiligung!

Gruß
Oliver Pfeiffer

======================

public class Test {

static interface Interval {

long getStart();
long getEnd();
long getLength();
boolean intersects(Interval other);
boolean contains(Interval other);

}

static interface ExtInterval<I> extends Interval {

I combine(I other);
I intersect(I other);
I subinterval(Interval other);

}

static abstract class IntervalBase
implements Interval {

public final long getStart() {
// return ...
}

public final long getEnd() {
// return ...
}

public final long getLength() {
// return ...
}

public final boolean intersects(Interval other) {
// return ...
}

public final boolean contains(Interval other) {
// return ...
}

}

static final class SubInterval extends IntervalBase
implements ExtInterval<SubInterval> {


public SubInterval combine(SubInterval other) {
// return new SubInterval(...);
}

public SubInterval intersect(SubInterval other) {
// return new SubInterval(...);
}

public SubInterval subinterval(Interval other) {
// return new SubInterval(...);
}

}

public static void main(final String[] args) {
SubInterval a = new SubInterval();
SubInterval b = new SubInterval();
SubInterval c = new SubInterval();
if (a.intersects(b) && b.intersects(c)) {
SubInterval d = a.combine(b).combine(c);
// ...
}
}

}

Loading...