Discussion:
Algorithmus zum Finden des speziellsten Super-Typ eines multi-catch
(zu alt für eine Antwort)
Heiner Kücker
2017-01-31 00:51:41 UTC
Permalink
Hallo,

ab Java7 kann man beim catch einen Union-Typ notieren:

catch ( Exception1 | Exception2 e )

Erlaubt sind nur Ableitungen von java.lang.Throwable.

Man kann aber in den Exception-Klassen eigene Interfaces
implementieren und der Java-Compiler findet diese,
wodurch man Methoden aus diesen Interfaces aufrufen kann.

Ich suche einen Algorithmus, der den speziellsten gemeinsamen Typ findet.

Das Ergebnis kann ein Intersection-Typ sein, also eine Klasse oder Interface und beliebig viele weitere Interfaces.

Man müsste sich an der super- und implements-Hierarchie der Exception-Klassen rekursiv hocharbeiten.

Eine Idee wäre, die Schnittmenge aller erweiterten Klassen und Interfaces zu bilden.

Dann hat man aber immer noch weniger spezielle Ober-Typen in der Schnittmenge.

Ich bin dankbar für alle Ideen.

Heiner
Heiner Kücker
2017-01-31 08:48:03 UTC
Permalink
Post by Heiner Kücker
Hallo,
catch ( Exception1 | Exception2 e )
Erlaubt sind nur Ableitungen von java.lang.Throwable.
Man kann aber in den Exception-Klassen eigene Interfaces
implementieren und der Java-Compiler findet diese,
wodurch man Methoden aus diesen Interfaces aufrufen kann.
Ich suche einen Algorithmus, der den speziellsten gemeinsamen Typ findet.
Das Ergebnis kann ein Intersection-Typ sein, also eine Klasse oder Interface und beliebig viele weitere Interfaces.
Man müsste sich an der super- und implements-Hierarchie der Exception-Klassen rekursiv hocharbeiten.
Eine Idee wäre, die Schnittmenge aller erweiterten Klassen und Interfaces zu bilden.
Dann hat man aber immer noch weniger spezielle Ober-Typen in der Schnittmenge.
Ich bin dankbar für alle Ideen.
Heiner
Ich denke, ich mache das mit der Schnittmenge.

Dann ermittle ich von allen Typen/Klasse der
Schnittmenge alle übergeordneten Klassen und
Interfaces.

Diese entferne ich, wodurch nur die finalen
Blätter des Baumes übrig bleiben.

Etwas schwierig wird es mit Typ-Argumenten.

So was ist leider auch möglich:

public class TryCatch
{

static interface MyExceptionInterface<T>
{
// gemeinsame Methode beider union-Exceptions
T m();
}

static class MyException1
extends Exception
implements MyExceptionInterface<String>
{

public String m()
{
return "";
}

}

static class MyException2
extends Exception
implements MyExceptionInterface<Integer>
{

public Integer m()
{
return 0;
}

}

void m()
throws MyException1 , MyException2
{
try
{
m();
}
catch ( final MyException1 | MyException2 e )
{
// Aufruf gemeinsame Methode
System.err.println( e.m() );
Object o = e.m();
}
}

}

String und Integer als Typ-Argumente sind eigentlich disjoint.

Demzufolge gehört die Methode m mit jeweiligem Typ nicht zur Typ-Vereinigungsmenge.

Hier haben die Compiler-Bauer scheinabr ein Auge zugedrückt
und es wie Raw-Typen behandelt.

Gruesse
Heiner
Patrick Roemer
2017-01-31 10:09:21 UTC
Permalink
Post by Heiner Kücker
String und Integer als Typ-Argumente sind eigentlich disjoint.
Demzufolge gehört die Methode m mit jeweiligem Typ nicht zur Typ-Vereinigungsmenge.
Hier haben die Compiler-Bauer scheinabr ein Auge zugedrückt
und es wie Raw-Typen behandelt.
Nicht ganz so schlimm, bzw. je nach Sichtweise noch schlimmer. ;) Da
wird schon irgendwie der "speziellste gemeinsame Typ" inferiert, ganz
wie von Dir gewünscht - aber dabei wird nonchalant so getan, als seien
Generics, äh, sagen wir: punktuell covariant. Für Integer/String
wäre das (vereinfacht) MyExceptionInterface<? extends Object>, für
Integer/Long hingegen MyExceptionInterface<? extends Number>. Was
natürlich auch wieder Murks ist.

Wie auch immer, im Integer/Long-Fall ginge z.B. folgendes:

MyExceptionInterface<? extends Number> nei = e;
Number n = e.m();

Keine Ahnung, ob sich das konsistent aus der JLS begründen lässt. Falls
ja, liest sich das sicher lustig. :)

Viele Grüße,
Patrick
Michael Paap
2017-01-31 10:42:06 UTC
Permalink
Post by Patrick Roemer
Nicht ganz so schlimm, bzw. je nach Sichtweise noch schlimmer. ;) Da
wird schon irgendwie der "speziellste gemeinsame Typ" inferiert, ganz
wie von Dir gewünscht - aber dabei wird nonchalant so getan, als seien
Generics, äh, sagen wir: punktuell covariant.
"Punktuell covariant" ist nett ausgedückt. Aber naja, das sind sie ja in
einem gewissen Sinne auch. Mit dem Preis, dass man in Fällen wie diesen
die Wildcards am Hals hat.

Gruß,
Michael
Heiner Kücker
2017-01-31 17:38:35 UTC
Permalink
Post by Michael Paap
"Punktuell covariant" ist nett ausgedückt. Aber naja, das sind sie ja in
einem gewissen Sinne auch. Mit dem Preis, dass man in Fällen wie diesen
die Wildcards am Hals hat.
Ich schreibe hier mal was zur Erklärung kovariant/kontravariant:


Kovariant wird geschrieben als

? extends X


Kontravariant wird geschrieben als

? super X


Ziemlich unverständlich, was soll das bedeuten?

Nehmen wir mal eine Hierarchie von drei Klassen an:

class A {}

class B extends A {}

class C extends B {}


Wenn wir kovariant schreiben:

? extends A

dann darf dort A, B oder C stehen.

Dies verwenden wir in einer Zuweisung:

A = ? extends A

entspricht

A = A oder
A = B oder
A = C

Alles erlaubt, zuweisungs-kompatibel.


Jetzt mal andersherum:

? extends A = A

entspricht

A = A oder
B = A oder
C = A

Die zweite und dritte Zeile ist nicht erlaubt, nicht zuweisungs-kompatibel.

Das bedeutet,

? extends X

darf in einer Zuweisung nur rechts stehen, nur gelesen werden.


Jetzt das Ganze kontravariant:

Wenn wir kontravariant schreiben:

? super C

dann darf dort A, B oder C stehen.

Dies verwenden wir in einer Zuweisung:

? super C = C

entspricht

C = C oder
B = C oder
A = C

Alles erlaubt, zuweisungs-kompatibel.


Jetzt mal andersherum:

C = ? super C

entspricht

C = C oder
C = B oder
C = A

Die zweite und dritte Zeile ist nicht erlaubt, nicht zuweisungs-kompatibel.


Das bedeutet,

? super X

darf in einer Zuweisung nur links stehen, nur geschrieben werden.


Also ist

? extends X

eine mathematisch verklausulierte Schreibweise für nur-Lesen sowie

? super X

eine mathematisch verklausulierte Schreibweise für nur-Schreiben.


Im konkreten Beispiel des multi-catch mit "Punktuell kovariant" darf ein T-Wert eben nur gelesen werden.


Gruesse
Heiner
Michael Paap
2017-01-31 18:58:05 UTC
Permalink
[Allerlei Erklärung]

Hm, als Erklärung der Begriffe ko- bzw. kontravariant kann ich das grad
nicht so recht empfinden. Zumal man dazu genau genommen eigentlich erst
mal den Bezug der "Varianz" benennen müsste.

Normalerweise hat man diese Begriffe ja im Kontext des Überschreibens
von Methoden in Subtypen, wobei bzgl. des Rückgabetyps nur Kovarianz mit
dem LSP logisch vereinbar ist, bzgl. der Parametertypen nur
Kontravarianz (die dort niemand benötigt und die Java auch nicht erlaubt).
Post by Heiner Kücker
Also ist
? extends X
eine mathematisch verklausulierte Schreibweise für nur-Lesen sowie
? super X
eine mathematisch verklausulierte Schreibweise für nur-Schreiben.
Das liest man zwar gerne mal, es ist aber mindestens stark verkürzt.
Schau dir in folgendem Beispiel mal an, an welchen Stellen es hier
Compilerfehler gibt, und an welchen nicht. Es sind welche dabei, die
nicht zu deiner "Regel" passen. Weswegen ich die Regel auch nicht mag. ;-)

---------------------------------------------------
import java.util.*;

public class WildcardTest {
public static void main(String[] args) {
List<?> liste1 = null;
liste1.add(new Object());
liste1.add(new Tier());
liste1.add(new Vogel());
Object o1 = liste1.get(0);
Tier t1 = liste1.get(0);
Vogel v1 = liste1.get(0);

List<? extends Tier> liste2 = null;
liste2.add(new Object());
liste2.add(new Tier());
liste2.add(new Vogel());
Tier t2 = liste2.get(0);
Object o2 = liste2.get(0);
Vogel v2 = liste2.get(0);

List<? super Vogel> liste3 = null;
liste3.add(new Object());
liste3.add(new Tier());
liste3.add(new Vogel());
Object o3 = liste3.get(0);
Tier t3 = liste3.get(0);
Vogel v3 = liste3.get(0);
}
}

class Tier {}

class Vogel extends Tier {}

class Amsel extends Vogel {}
---------------------------------------------------

Gruß,
Michael
Heiner Kücker
2017-01-31 19:24:06 UTC
Permalink
Post by Michael Paap
Hm, als Erklärung der Begriffe ko- bzw. kontravariant kann ich das grad
nicht so recht empfinden. Zumal man dazu genau genommen eigentlich erst
mal den Bezug der "Varianz" benennen müsste.
Ich finde es plausibel.

Mit Varianz ist der Typ/Klasse/Interface gemeint.
Post by Michael Paap
Normalerweise hat man diese Begriffe ja im Kontext des Überschreibens
von Methoden in Subtypen, wobei bzgl. des Rückgabetyps nur Kovarianz mit
dem LSP logisch vereinbar ist, bzgl. der Parametertypen nur
Kontravarianz (die dort niemand benötigt und die Java auch nicht erlaubt).
Rückgabewerte kann man nur Lesen (kovarian).

Parameter kann man nur Schreiben (kontravariant).

Passt alles.
Post by Michael Paap
Post by Heiner Kücker
Also ist
? extends X
eine mathematisch verklausulierte Schreibweise für nur-Lesen sowie
? super X
eine mathematisch verklausulierte Schreibweise für nur-Schreiben.
Das liest man zwar gerne mal, es ist aber mindestens stark verkürzt.
Schau dir in folgendem Beispiel mal an, an welchen Stellen es hier
Compilerfehler gibt, und an welchen nicht. Es sind welche dabei, die
nicht zu deiner "Regel" passen. Weswegen ich die Regel auch nicht mag. ;-)
---------------------------------------------------
import java.util.*;
public class WildcardTest {
public static void main(String[] args) {
List<?> liste1 = null;
liste1.add(new Object());
liste1.add(new Tier());
liste1.add(new Vogel());
Object o1 = liste1.get(0);
Tier t1 = liste1.get(0);
Vogel v1 = liste1.get(0);
List<? extends Tier> liste2 = null;
liste2.add(new Object());
liste2.add(new Tier());
liste2.add(new Vogel());
Tier t2 = liste2.get(0);
Object o2 = liste2.get(0);
Vogel v2 = liste2.get(0);
List<? super Vogel> liste3 = null;
liste3.add(new Object());
liste3.add(new Tier());
liste3.add(new Vogel());
Object o3 = liste3.get(0);
Tier t3 = liste3.get(0);
Vogel v3 = liste3.get(0);
}
}
class Tier {}
class Vogel extends Tier {}
class Amsel extends Vogel {}
---------------------------------------------------
Gruß,
Michael
Die Compiler-Fehler passen, ausser bei

Object o* = liste*.get(0);

Nun ja, ein Object ist eben immer.

Gruesse
Heiner
Michael Paap
2017-01-31 19:33:50 UTC
Permalink
Post by Heiner Kücker
Mit Varianz ist der Typ/Klasse/Interface gemeint.
"Varianz" kommt daher, dass etwas variiert wird. Beim Überschreiben von
Methoden "variiert man vom Supertyp zum Subtyp". Wenn man nun bzgl. des
Rückgabetyps ebenfalls vom Super- zum Subtyp variieren muss, um mit dem
LSP verträglich zu bleiben, hat man Kovarianz ("ko" im Sinne von
gleichgerichtet). Entsprechend wäre bei den Parametern Kontravarianz
logisch zulässig ("kontra" im Sinne von entgegengesetzt).
Post by Heiner Kücker
Post by Michael Paap
Normalerweise hat man diese Begriffe ja im Kontext des Überschreibens
von Methoden in Subtypen, wobei bzgl. des Rückgabetyps nur Kovarianz mit
dem LSP logisch vereinbar ist, bzgl. der Parametertypen nur
Kontravarianz (die dort niemand benötigt und die Java auch nicht erlaubt).
Rückgabewerte kann man nur Lesen (kovarian).
Parameter kann man nur Schreiben (kontravariant).
Passt alles.
Vielleicht ist es ja nur die flapsige Ausdrucksweise, aber ehrlich
gesagt klingt das für mich ein bisschen nach halbverdauten Faustregeln
ohne Verständnis für die Logik hinter dem Ganzen.
Post by Heiner Kücker
Die Compiler-Fehler passen, ausser bei
Object o* = liste*.get(0);
Nun ja, ein Object ist eben immer.
Ok, wenn du eine Widerlegung durch Gegenbeispiel so lässig abtust,
sollten wir es am Besten einfach dabei belassen. ;-)

Gruß,
Michael
Heiner Kücker
2017-01-31 16:26:58 UTC
Permalink
Post by Patrick Roemer
Post by Heiner Kücker
String und Integer als Typ-Argumente sind eigentlich disjoint.
Demzufolge gehört die Methode m mit jeweiligem Typ nicht zur Typ-Vereinigungsmenge.
Hier haben die Compiler-Bauer scheinabr ein Auge zugedrückt
und es wie Raw-Typen behandelt.
Nicht ganz so schlimm, bzw. je nach Sichtweise noch schlimmer. ;) Da
wird schon irgendwie der "speziellste gemeinsame Typ" inferiert, ganz
wie von Dir gewünscht - aber dabei wird nonchalant so getan, als seien
Generics, äh, sagen wir: punktuell covariant. Für Integer/String
wäre das (vereinfacht) MyExceptionInterface<? extends Object>, für
Integer/Long hingegen MyExceptionInterface<? extends Number>. Was
natürlich auch wieder Murks ist.
MyExceptionInterface<? extends Number> nei = e;
Number n = e.m();
Keine Ahnung, ob sich das konsistent aus der JLS begründen lässt. Falls
ja, liest sich das sicher lustig. :)
Viele Grüße,
Patrick
Ich habe es mit Long und Integer ausprobiert:

public class TryCatch
{

static interface MyExceptionInterface<T>
{
// gemeinsame Methode beider union-Exceptions
T m(T t);
}

static class MyException1
extends Exception
implements MyExceptionInterface<Long>
{

public Long m(
Long str )
{
return "";
}

}

static class MyException2
extends Exception
implements MyExceptionInterface<Integer>
{

public Integer m(
Integer i)
{
return 0;
}

}

void m()
throws MyException1 , MyException2
{
try
{
m();
}
catch ( MyException1 | MyException2 e )
{
// Aufruf gemeinsame Methode
System.err.println( e.m( null ) );
Number o = e.m( null );
// Parameter-Übergabe nicht kompilierbar, entspricht der punktuell-kovariant-Annnahme: Number o = e.m( Long.valueOf( 0 ) );
// Parameter-Übergabe nicht kompilierbar, entspricht der punktuell-kovariant-Annnahme: Number o = e.m( Integer.valueOf( 0 ) );
}
}

}

Genau wie von Dir angegenommen kann der Rückgabewert der Methode einem Number zugewiesen werden, aber Number ist nicht zuweisbar, also kovariant.

Keine Ahnung, wie Du darauf gekommen bist, schon ziemlich genial.

Danke
Heiner Kücker
2017-01-31 16:29:28 UTC
Permalink
Post by Heiner Kücker
static class MyException1
extends Exception
implements MyExceptionInterface<Long>
{
public Long m(
Long str )
{
return "";
}
}
Kleine Korrektur:

static class MyException1
extends Exception
implements MyExceptionInterface<Long>
{

public Long m(
Long str )
{
return 0L;
}

}
Michael Paap
2017-01-31 19:26:15 UTC
Permalink
Post by Heiner Kücker
catch ( MyException1 | MyException2 e )
{
// Aufruf gemeinsame Methode
System.err.println( e.m( null ) );
Number o = e.m( null );
// Parameter-Übergabe nicht kompilierbar, entspricht der punktuell-kovariant-Annnahme: Number o = e.m( Long.valueOf( 0 ) );
// Parameter-Übergabe nicht kompilierbar, entspricht der punktuell-kovariant-Annnahme: Number o = e.m( Integer.valueOf( 0 ) );
}
}
}
Genau wie von Dir angegenommen kann der Rückgabewert der Methode einem Number zugewiesen werden, aber Number ist nicht zuweisbar, also kovariant.
Keine Ahnung, wie Du darauf gekommen bist, schon ziemlich genial.
Reine Logik. ;-)

Deine beiden Exception-Typen sind Subtypen von
MyExceptionInterface<Long> und MyExceptionInterface<Ineger>. Deren
gemeinsamer Supertyp ist nicht etwa MyExceptionInterface<Number>, sodern
MyExceptionInterface<? extends Number>.

Nun die Sichtweise des Compilers bzw. ihre Bedeutung:

Der Typ MyExceptionInterface<? extends Number> umfasst die Menge aller
MyExceptionInterface-Objekte, die mit *irgendeinem* Typparameter
parametrisiert sind, der Subtyp von Number (incl. Number) ist. Mit
welchem, ist aber unbekannt.

Dem Compiler ist an der Stelle, wo du m() aufrufst bzgl. des T aus

T m(T t);

nur bekannt, dass T Subtyp von Number (incl. Number ist). Und damit kann
er dich als Parameter nicht irgendeinen Subtyp X von Number oder Number
reingeben lassen, denn T könnte ja ein anderer Subtyp von Number oder
gar ein Subtyp des von dir gewählten Typs sein.

Hingegen lässt er dich das, was du rausbekommst, natürlich einer
Variablem vom Typ Number zuweisen, denn egal, welchen Typ T konkret hat,
es muss ein Subtyp von Number sein und damit ist die Zuweisung korrekt.

Nach meiner Erfahrung hilft es enorm, sich bei Wildcard-Typen immer klar
zu machen, wie die Extension des Typs aussieht, für welche Menge von
Objekten der Typ also steht.

Gruß,
Michael
Heiner Kücker
2017-01-31 22:33:53 UTC
Permalink
Post by Michael Paap
Post by Heiner Kücker
Post by Heiner Kücker
Punktuell kovariant
Keine Ahnung, wie Du darauf gekommen bist, schon ziemlich genial.
Reine Logik. ;-)
Nach der Ermittlung des speziellsten gemeinsamen Super-Typ könnte ein spezieller Typ auftauchen (wenn es sich um einen Behälter handeln würde).

Insofern gilt der speziellste gemeinsame Super-Typ nur lesend, also kovariant.

Darauf wäre ich sicher irgendwann auch gekommen.

Zusätzlich ist sowieso nicht sinnvoll, die Exception zu überschreiben.

Aber vielleicht gibt es in einem zukünftigen Java mal solche Typen auch an anderen Stellen, dann sollte man dies wissen.
Post by Michael Paap
Gruß,
Michael
Danke
Heiner

Lesen Sie weiter auf narkive:
Suchergebnisse für 'Algorithmus zum Finden des speziellsten Super-Typ eines multi-catch' (Newsgroups und Mailinglisten)
28
Antworten
Wird eine Instanz noch verwendet
gestartet 2006-02-17 13:36:20 UTC
de.comp.lang.java
39
Antworten
Stilfragen
gestartet 2003-08-07 07:47:16 UTC
de.comp.lang.java
Loading...