Discussion:
Frage zu Generics
(zu alt für eine Antwort)
Michael Matzl
2005-11-22 18:41:07 UTC
Permalink
Hallo!

Habe eine Frage bzgl. Generics zu folg. Code:

import java.util.*;

public class GenericsProblem {

public GenericsProblem() {

Map<Integer, String> map = new HashMap<Integer, String>();
map.put(3,"drei");
String s[] = new String[3];
s[0] = map.get(3);
s[1] = map.get(4);
s[2] = map.get("test");

System.out.println(s[0]+","+s[1]+","+s[2]);
}

public static void main(String[] args) { new GenericsProblem(); }
}


Obwohl als Key für die HashMap nur Integer-Objekte zugelassen sind,
gibt es keinen Compile-Error bei dem Statement

s[2] = map.get("test");

Der Grund ist, dass get() Objects als Parameter erwartet. Bei

map.put("x","y");

bricht der Compiler mit einem Fehler ab. Put() ist type-safe.

Warum ist get() nicht type-safe?

Bin schon gespannt auf eure Antworten.

lg
michl
Stefan Matthias Aust
2005-11-22 22:20:20 UTC
Permalink
Post by Michael Matzl
Warum ist get() nicht type-safe?
Oh, das ist es, aber es ist nicht der Typ, den du erwartest :) AFAIK
hat man Object statt den konkreten Typ gewählt, damit alter Code nicht
bricht. Nur weil man nach einem Key mit falschen Typ fragt, soll der
Code nicht brechen, sondern einfach null zurückliefern.
--
Stefan Matthias Aust // Ergo bibamus, ne sitiamus, vas repleamus!
Dirk Försterling
2005-11-23 07:44:35 UTC
Permalink
Post by Stefan Matthias Aust
Post by Michael Matzl
Warum ist get() nicht type-safe?
Oh, das ist es, aber es ist nicht der Typ, den du erwartest :) AFAIK
Die Semantik von get(), containsKey(), containsValue() und equals() ist
mit Object Parameter vollständig definiert. Daher hat das hier gar
nicht so viel mit type safety zu tun.

Eine Antwort von "false" oder "null" auf die Frage nach einem Objekt
falschen Typs ist korrekt (Es gibt in der Map eben kein Mapping für
diesen key). Das steht auch so in der API-Doku.

Da eine Map<Integer, String> keine String-Keys enthält, ist die Antwort
auf get( "test" ) eindeutig "null". Wozu also ein Compilerfehler?
Post by Stefan Matthias Aust
hat man Object statt den konkreten Typ gewählt, damit alter Code nicht
bricht. Nur weil man nach einem Key mit falschen Typ fragt, soll der
Code nicht brechen, sondern einfach null zurückliefern.
Hast Du dafür ein Beispiel? Ich glaube nicht, daß mit einem K key in
get() alter Code kaputt geht. Alter Code würde nämlich sowieso nur
Map benutzen, also Map<Object,Object>, dann würde K=Object sein und
get( K key ) würde zu get( Object key ).

Vielmehr würde sich durch get( K key ) die Typsicherheit nicht erhöhen.
Es werden damit keine Situationen korrigiert in denen auf ein Objekt über
einen falschen Typ zugegriffen werden könnte.

Mit dem typisierten Parameter in den genannten Methoden würde man sich
wieder unnötige Typecasts einhandeln. Z.B. bei sowas:

Vector<Number> keys;
Map<Integer, String> imap;
Map<Double, String> dmap;

// ...

for( Number n : keys ) {
if( imap.containsKey( n ) || dmap.containsKey( n ) ) {
System.out.println( "hab ich" );
}
}


Ein get() mit K key ist also nicht nur unnötig sondern könnte auch
Code verkomplizieren.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
42 means: for tea, too.
Stefan Matthias Aust
2005-11-23 11:31:50 UTC
Permalink
Post by Dirk Försterling
Post by Stefan Matthias Aust
hat man Object statt den konkreten Typ gewählt, damit alter Code nicht
bricht. Nur weil man nach einem Key mit falschen Typ fragt, soll der
Code nicht brechen, sondern einfach null zurückliefern.
Hast Du dafür ein Beispiel? Ich glaube nicht, daß mit einem K key in
get() alter Code kaputt geht. Alter Code würde nämlich sowieso nur
Map benutzen, also Map<Object,Object>, dann würde K=Object sein und
get( K key ) würde zu get( Object key ).
Nein ich habe kein gutes Beispiel. Ich halte meine Behauptung inzwischen
für falsch...
--
Stefan Matthias Aust // Ergo bibamus, ne sitiamus, vas repleamus!
Ralf Ullrich
2005-11-23 15:54:23 UTC
Permalink
Post by Stefan Matthias Aust
Ich halte meine Behauptung inzwischen
für falsch...
Warum? Deine Antwort war bisher die einzige wenigstens nahezu korrekte. Nur, dass es eben nicht
darum geht ob Generified- und Raw-Code kompatibel sind, sondern darum ob (korrekter) Raw-Code bei
Beibehaltung der Semantik "generified" werden kann.

cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Stefan Matthias Aust
2005-11-24 17:37:26 UTC
Permalink
Post by Ralf Ullrich
Ich halte meine Behauptung inzwischen für falsch...
Warum? Deine Antwort war bisher die einzige wenigstens nahezu korrekte.
Nun, ich konnte auf die schnelle keinen Code konstruieren, der nicht
mehr kompiliert, wenn man statt get(E) ein get(Object) benutzt.

Hier ist ein minimales Beispiel:

class X<T> {
void get(T t) {
}

{
X x = new X();
x.get("");
}

Ich hätte gedacht, obiger Code kompiliert nicht, aber er tut es doch.
Daher war mein Rückwärtskompatibilitätsargument nicht mehr haltbar.
Post by Ralf Ullrich
Nur, dass es eben nicht darum geht ob Generified- und Raw-Code
kompatibel sind, sondern darum ob (korrekter) Raw-Code bei Beibehaltung
der Semantik "generified" werden kann.
Daran hatte ich nicht gedacht, aber das ist natürlich das richtige
Argument. Keys werden mit equals() verglichen und da ist der Typ des
Objekts egal.
--
Stefan Matthias Aust // Ergo bibamus, ne sitiamus, vas repleamus!
Wanja Gayk
2005-11-23 11:45:43 UTC
Permalink
Post by Dirk Försterling
Da eine Map<Integer, String> keine String-Keys enthält, ist die Antwort
auf get( "test" ) eindeutig "null". Wozu also ein Compilerfehler?
Die Generics sind doch gerade dazu da solche Typfehler auszuschließen,
warum also nicht?
Post by Dirk Försterling
Vielmehr würde sich durch get( K key ) die Typsicherheit nicht erhöhen.
Es werden damit keine Situationen korrigiert in denen auf ein Objekt über
einen falschen Typ zugegriffen werden könnte.
Allerdings werden damit situationen verhindert, indem man mit einem
Objekt falschen Typs als Key auf ein Objekt in der Map zugreifen will.
Wenn der Typ-parameter für Key unnötog wäre, könnte man ihn prinzipiell
auch gleich weglassen (ja: ich weiß Map#keySet(), aber was bringt es,
wenn die Keys von beliebigem Typ sein können?).
Post by Dirk Försterling
Mit dem typisierten Parameter in den genannten Methoden würde man sich
[Snip]

Unnötige Typecasts zu entfernen ist IMO Aufgabe des Compilers.

Gruß,
-Wanja-
--
"Gewisse Schriftsteller sagen von ihren Werken immer: 'Mein Buch, mein
Kommentar, meine Geschichte'. [..] Es wäre besser, wenn sie sagten:
'unser Buch, unser Kommentar, unsere Geschichte'; wenn man bedenkt, dass
das Gute darin mehr von anderen ist als von ihnen." [Blaise Pascal]
Dirk Försterling
2005-11-23 14:55:03 UTC
Permalink
Post by Wanja Gayk
Post by Dirk Försterling
Da eine Map<Integer, String> keine String-Keys enthält, ist die Antwort
auf get( "test" ) eindeutig "null". Wozu also ein Compilerfehler?
Die Generics sind doch gerade dazu da solche Typfehler auszuschließen,
warum also nicht?
Sie sind in erster Linie dazu da, um Collections und generische Methoden
sicherer implementieren zu können. Nicht um allgemein typfehler zu
verhindern und speziell sogar solche wie den genannten.
Post by Wanja Gayk
Post by Dirk Försterling
Vielmehr würde sich durch get( K key ) die Typsicherheit nicht erhöhen.
Es werden damit keine Situationen korrigiert in denen auf ein Objekt über
einen falschen Typ zugegriffen werden könnte.
Allerdings werden damit situationen verhindert, indem man mit einem
Objekt falschen Typs als Key auf ein Objekt in der Map zugreifen will.
Wenn der Typ-parameter für Key unnötog wäre, könnte man ihn prinzipiell
auch gleich weglassen (ja: ich weiß Map#keySet(), aber was bringt es,
wenn die Keys von beliebigem Typ sein können?).
Das ist nicht fatal, sondern kommt einem Flüchtigkeitsfehler bei
der Programmierung gleich. Ich bekomme mit dem falschen Typ eben
kein Objekt aus der Map. Na und? Wenn der Typ des Keys nicht stimmt
kann per Definition eben kein Element mit diesem Key in der Map
enthalten sein. Was willst Du? Vom Compiler das Denken abgenommen
bekommen? Dann schreib Dir ne Wrapper-Klasse.
Post by Wanja Gayk
Post by Dirk Försterling
Mit dem typisierten Parameter in den genannten Methoden würde man sich
[Snip]
Unnötige Typecasts zu entfernen ist IMO Aufgabe des Compilers.
Wenn Du nicht gerade das gleiche gemeint hast wie ich, dann brauche
ich noch eine Bestätigung:
Du möchtest also, unter der Voraussetzung, daß containsKey nur den
Key-Typ akzeptiert, daß folgendes ohne Casts funktioniert:

Vector<Object> keys;
Map<Integer, String> imap;
Map<Double, String> dmap;
Map<String, String> smap;

// ...

for( Object o : keys ) {
if( imap.containsKey( o ) || dmap.containsKey( o ) || smap.containsKey( o )) {
System.out.println( "hab ich" );
}
}


??

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
bash: randomsig.pl: command not found
Ingo R. Homann
2005-11-23 15:07:23 UTC
Permalink
Hi,
Post by Dirk Försterling
Das ist nicht fatal, sondern kommt einem Flüchtigkeitsfehler bei
der Programmierung gleich. Ich bekomme mit dem falschen Typ eben
kein Objekt aus der Map. Na und? Wenn der Typ des Keys nicht stimmt
kann per Definition eben kein Element mit diesem Key in der Map
enthalten sein. Was willst Du? Vom Compiler das Denken abgenommen
bekommen? Dann schreib Dir ne Wrapper-Klasse.
Unser Argument ist ja, das so eine Wrapper-Klasse gar nicht nötig sein
sollte!

Und außerdem: Wenn sun sich dafür entschieden hätte, es typsicher zu
machen, würdest Du mit einer Map<Object,String> immer noch glücklich
werden können (selbst in dem konstruierten Beispiel, welches Du anführst)!

Ciao,
Ingo
Dirk Försterling
2005-11-23 15:21:09 UTC
Permalink
Post by Ingo R. Homann
Unser Argument ist ja, das so eine Wrapper-Klasse gar nicht nötig sein
sollte!
Ich finde nicht, daß sie nötig ist. Wie bereits erwähnt funktioniert
get() ja unabhängig vom Parametertypen innerhalb seiner Spezifikation.
Post by Ingo R. Homann
Und außerdem: Wenn sun sich dafür entschieden hätte, es typsicher zu
machen, würdest Du mit einer Map<Object,String> immer noch glücklich
werden können (selbst in dem konstruierten Beispiel, welches Du anführst)!
Nein. Die Keys wären dann nicht mehr Typsicher. Ich könnte also in meine
eigentlich als <Integer, String> definierte Map plötzlich auch <Double,
Integer> einfügen und hätte später ein Typenproblem, wenn ich mir die
Keys mit keySet() abhole. Das kann dann zu einer ClassCastException
führen.

Solche eine Situation kann ich mit get( Object key ) (oder den anderen
Nur-Lese-Methoden) nicht erzeugen. Der Nutzen eines Typisiert definierten
get( K key ) wäre also mehr ein Hindernis als eine Sicherheits- bzw.
Robustheits-Eigenschaft, da ich dann unter Umständen gezwungen wäre auf
Map<Object, Special> zurückzugreifen oder doch zu den hässlichen und
Fehleranfälligen Casts zurückzukehren.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
bIQapqu'meH tar DaSop 'e' DatIvnIS
Timo Stamm
2005-11-23 12:03:19 UTC
Permalink
Post by Michael Matzl
Warum ist get() nicht type-safe?
Weil type safety bei get() nur wenig Nutzen bringt. Der Compiler könnte
mit typsicherem get() zwar in deinem Beispiel darauf aufmerksam machen,
dass du an der Stelle niemals ein Objekt zurückbekommen würdest, aber
mehr auch nicht.

Dafür würde es dem zentralen Paradigma vom OOP widersprechen: der
Kapselung. Kapselung heisst auch, dass Objekte so wenig wie möglich
abhängig von bestimmten anderen Typen sein sollten.

Ein Beispiel:

private Map<String, Special> m = ...

public Special lookup(Object key) {
return m.get(key);
}


Diese lookup Methode nimmt jedes Objekt an. Du hast hier also keinerlei
Abhängigkeit was den Typ angeht. Wenn der key nicht passt, bekommst du
null zurück. Ich sehe keinen Grund warum man String als Typ des keys
festlegen sollte.

Mit Generics kannst du deine Anwendung zwar viel besser durchtypisieren
als mit Java 1.4, aber das ist nicht immer wünschenswert. Es macht
keinen Sinn, immer alles auf typen festzulegen. Deine Architektur wird
dadurch starr.

Meine erste Schlussfolgerung nachdem ich Java benutzt habe war: immer
typsicher machen. Meine aktuelle Schlussfolgerung ist: nur typsicher
machen wenn nötig, und bestimmte Schnittstellen eben möglichst
unabhängig von einem Typ lösen. Siehe auch das Facade design pattern.
Eine Map implementiert in diesem Sinne das Facade pattern.


Timo
Ingo R. Homann
2005-11-23 12:28:25 UTC
Permalink
Hi,
Post by Timo Stamm
Post by Michael Matzl
Warum ist get() nicht type-safe?
Weil type safety bei get() nur wenig Nutzen bringt. Der Compiler könnte
mit typsicherem get() zwar in deinem Beispiel darauf aufmerksam machen,
dass du an der Stelle niemals ein Objekt zurückbekommen würdest, aber
mehr auch nicht.
Was heisst "mehr nicht"? Der Compiler findet etwas, was ganz
offensichtlich ein Bug ist. Wenn das kein Vorteil ist!!!
Post by Timo Stamm
... dass Objekte so wenig wie möglich
abhängig von bestimmten anderen Typen sein sollten....
Das ist doch kein Paradigma der OOP - zumindest nicht in diesem Kontext!
Post by Timo Stamm
Mit Generics kannst du deine Anwendung zwar viel besser durchtypisieren
als mit Java 1.4, aber das ist nicht immer wünschenswert. Es macht
keinen Sinn, immer alles auf typen festzulegen. Deine Architektur wird
dadurch starr.
Wer unbedingt will, kann ja eine Map<Object,Integer> anlegen. Aber wer
explizit eine Map<String,Integer> anlegt, sollte einen Compiler-Fehler
bekommen, wenn er sich nicht an seine Deklaration hält.
Post by Timo Stamm
Meine erste Schlussfolgerung nachdem ich Java benutzt habe war: immer
typsicher machen. Meine aktuelle Schlussfolgerung ist: nur typsicher
machen wenn nötig, und bestimmte Schnittstellen eben möglichst
unabhängig von einem Typ lösen.
Ich würde eher sagen: *Immer* typsicher machen, aber den Typ nicht
spezieller wählen, als sinnvoll. (So formuliert ist letzteres eine
Tautologie! :-)

Ciao,
Ingo
Timo Stamm
2005-11-23 14:01:01 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Timo Stamm
Post by Michael Matzl
Warum ist get() nicht type-safe?
Weil type safety bei get() nur wenig Nutzen bringt. Der Compiler
könnte mit typsicherem get() zwar in deinem Beispiel darauf aufmerksam
machen, dass du an der Stelle niemals ein Objekt zurückbekommen
würdest, aber mehr auch nicht.
Was heisst "mehr nicht"? Der Compiler findet etwas, was ganz
offensichtlich ein Bug ist. Wenn das kein Vorteil ist!!!
Ich habe nicht behauptet dass es kein Vorteil wäre. Ich schrieb der
Nutzen wäre gering.
Post by Ingo R. Homann
Post by Timo Stamm
... dass Objekte so wenig wie möglich
abhängig von bestimmten anderen Typen sein sollten....
Das ist doch kein Paradigma der OOP - zumindest nicht in diesem Kontext!
Ich schrieb ja dass das Paradigma Kapselung wäre. Das heisst: immer nur
so wenig Informationen herausgeben wie nötig. Da für keys in einer Map
nur hashCode() und equals() voraussetzen, ist für die korrekte Funktion
von get() nur Object nötig.
Post by Ingo R. Homann
Wer unbedingt will, kann ja eine Map<Object,Integer> anlegen.
Dann könnten keys in der Map landen die equals() und hasCode() nicht
sauber implementieren.
Post by Ingo R. Homann
Aber wer
explizit eine Map<String,Integer> anlegt, sollte einen Compiler-Fehler
bekommen, wenn er sich nicht an seine Deklaration hält.
Post by Timo Stamm
Meine erste Schlussfolgerung nachdem ich Java benutzt habe war: immer
typsicher machen. Meine aktuelle Schlussfolgerung ist: nur typsicher
machen wenn nötig, und bestimmte Schnittstellen eben möglichst
unabhängig von einem Typ lösen.
Ich würde eher sagen: *Immer* typsicher machen, aber den Typ nicht
spezieller wählen, als sinnvoll. (So formuliert ist letzteres eine
Tautologie! :-)
Du definierst "sinnvoll" nicht.

Ich würde behaupten dass man eben gerade nicht *immer* alles typsicher
machen sollte. An bestimmten Stellen braucht man eine Facade, die eben
keine Kenntnis über den Typ vorraussetzt. Dadurch verhindert man
Abhängigkeiten und kommt zu diskreten Komponenten (-> Kapselung --> OOP).

Die Map sehe ich halt als Implementation so einer Facade. Um das
konkreter zu machen, noch mal ein Beispiel:

public class SpecialKey extends String {...}

public class SpecialLookupService {

private Map<SpecialKey, Special> m = ...

public Special lookup(SpecialKey key) {
return m.get(key);
}
}

SpecialLookupService ist die zentrale Klasse meiner Anwendung, über die
ich mir Special-Instanzen hole.

Jetzt könnte es durchaus sein, dass ich die keys aus einer Datenbank
ziehe. Die Datenbank und der Treiber kennen aber den Typ SpecialKey nicht.

Ich kann jetzt also über den Spaltennamen gehen und neue Objekte
erstellen. Vielleicht kann ich mir auch sparen, eine neue String-Instanz
anzulegen, indem ich SpecialKey als Interface deklariere und mit einem
SpecialKeyImplementationStringDelagator arbeite der das Interface
implementiert und Methoden an den übergebenen String delegiert.

Ich kann aber auch auf den ganzen Mist sparen und einfach "Special
lookup(Objekt key)" als Signatur verwenden. Geht aber nur wenn get()
auch Object annimt.


Timo
Ingo R. Homann
2005-11-23 14:38:08 UTC
Permalink
Hi,
Post by Timo Stamm
Post by Ingo R. Homann
Post by Timo Stamm
... dass Objekte so wenig wie möglich
abhängig von bestimmten anderen Typen sein sollten....
Das ist doch kein Paradigma der OOP - zumindest nicht in diesem Kontext!
Ich schrieb ja dass das Paradigma Kapselung wäre. Das heisst: immer nur
so wenig Informationen herausgeben wie nötig.
Naja, und in diesem Kontext ist das IMHO nicht zutreffend.
Typinformationen sind schließlich auch ein Paradigma der OOP. Und wenn
sie genutzt werden können, um Bugs zu vermeiden, sollte das unbedingt,
in jedem Fall und ohne Ausnahme zwangsläufig getan werden.
Post by Timo Stamm
Post by Ingo R. Homann
Wer unbedingt will, kann ja eine Map<Object,Integer> anlegen.
Dann könnten keys in der Map landen die equals() und hasCode() nicht
sauber implementieren.
Es gibt keinen Mechanismus, der erzwingt, dass equals() und hashCode()
vernünftig implementiert werden. Das stimmt.

Das hat aber doch nix damit zu tun, dass der key typsicher sein sollte.
Post by Timo Stamm
Post by Ingo R. Homann
Ich würde eher sagen: *Immer* typsicher machen, aber den Typ nicht
spezieller wählen, als sinnvoll. (So formuliert ist letzteres eine
Tautologie! :-)
Du definierst "sinnvoll" nicht.
Sonst wärs ja keine Tautologie, Mensch! ;-)
Post by Timo Stamm
Ich würde behaupten dass man eben gerade nicht *immer* alles typsicher
machen sollte. An bestimmten Stellen braucht man eine Facade, die eben
keine Kenntnis über den Typ vorraussetzt. Dadurch verhindert man
Abhängigkeiten und kommt zu diskreten Komponenten (-> Kapselung --> OOP).
Richtig. Nur hat diese sehr allgemeine Aussage mit diesem speziellen
Fall nichts zu tun.
Post by Timo Stamm
Die Map sehe ich halt als Implementation so einer Facade. Um das
public class SpecialKey extends String {...}
public class SpecialLookupService {
private Map<SpecialKey, Special> m = ...
public Special lookup(SpecialKey key) {
return m.get(key);
}
}
SpecialLookupService ist die zentrale Klasse meiner Anwendung, über die
ich mir Special-Instanzen hole.
Jetzt könnte es durchaus sein, dass ich die keys aus einer Datenbank
ziehe. Die Datenbank und der Treiber kennen aber den Typ SpecialKey nicht.
Ich kann jetzt also über den Spaltennamen gehen und neue Objekte
erstellen. Vielleicht kann ich mir auch sparen, eine neue String-Instanz
anzulegen, indem ich SpecialKey als Interface deklariere und mit einem
SpecialKeyImplementationStringDelagator arbeite der das Interface
implementiert und Methoden an den übergebenen String delegiert.
Ich kann aber auch auf den ganzen Mist sparen und einfach "Special
lookup(Objekt key)" als Signatur verwenden. Geht aber nur wenn get()
auch Object annimt.
Mal davon abgesehen, dass es gerade bei einer Map (schlimmer noch
speziell bei einer TreeMap, aber Du hast hier ja "nur" eine HashMap)
gefährlich bzw. fatal sein kann (kann, nicht muss), keys
unterschiedlichen Typs zu verwenden (auch wenn es direkte Subtypen
sind), spräche doch (wenn die Map so typsicher wäre, wie ich es gerne
hätte) in deinem Fall nichts dagegen, einfach eine Map<String,Special>
anstelle einer Map<SpecialKey,Special> zu verwenden!

Ciao,
Ingo
Dirk Försterling
2005-11-23 15:07:32 UTC
Permalink
Post by Ingo R. Homann
Mal davon abgesehen, dass es gerade bei einer Map (schlimmer noch
speziell bei einer TreeMap, aber Du hast hier ja "nur" eine HashMap)
gefährlich bzw. fatal sein kann (kann, nicht muss), keys
unterschiedlichen Typs zu verwenden (auch wenn es direkte Subtypen
Tut man ja nicht. Deswegen will man ja Map<K,V>.

Daß die Nur-Lese-Methoden nicht auf den K-Typ als Parameter eingeschränkt
werden ist immer nicht fatal: Das Ergebnis eines solchen aufrufs mit
falschem Key-Typ ist immer korrekt. Bei get(): null - Kein Element mit
diesem Key in der Map enthalten.


-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
"port scanner" - Abtaster auf der linken Raumschiffseite (ST TOS#73)
Dirk Försterling
2005-11-23 15:11:35 UTC
Permalink
Post by Dirk Försterling
falschem Key-Typ ist immer korrekt. Bei get(): null - Kein Element mit
diesem Key in der Map enthalten.
Nicht, daß ich gerne mit mir selbst rede: Aber natürlich kann auch
"null" in der Map enthalten sein. Daher sollte man, falls man "null"s
in der Map speichert zunächst containsKey() konsultieren.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
bIQapqu'meH tar DaSop 'e' DatIvnIS
Ingo R. Homann
2005-11-23 15:14:17 UTC
Permalink
Hi,
Post by Dirk Försterling
Post by Ingo R. Homann
Mal davon abgesehen, dass es gerade bei einer Map (schlimmer noch
speziell bei einer TreeMap, aber Du hast hier ja "nur" eine HashMap)
gefährlich bzw. fatal sein kann (kann, nicht muss), keys
unterschiedlichen Typs zu verwenden (auch wenn es direkte Subtypen
Tut man ja nicht. Deswegen will man ja Map<K,V>.
Genau. Und das aber auch konsequent, bitte. Für put UND für get.
Post by Dirk Försterling
Daß die Nur-Lese-Methoden nicht auf den K-Typ als Parameter eingeschränkt
werden ist immer nicht fatal: Das Ergebnis eines solchen aufrufs mit
falschem Key-Typ ist immer korrekt.
Puh, wie oft soll ich das wiederholen: Blödsinniges Verhalten wird nicht
allein dadurch besser, dass man es in die Doku reinschreibt.

Ciao,
Ingo
Dirk Försterling
2005-11-23 15:26:39 UTC
Permalink
Post by Ingo R. Homann
Puh, wie oft soll ich das wiederholen: Blödsinniges Verhalten wird nicht
allein dadurch besser, dass man es in die Doku reinschreibt.
Bitte nicht im Kreis drehen.

Es zwingt Dich ja niemand, die mitgelieferten Bibliotheken zu benutzen,
wenn du sie nicht magst. ;-)

Statt untypisiert von blödsinnigem Verhalten zu schreiben, würde ein
konkretes Beispiel helfen, daß mir die Fatalität von untypisiertem
get()-Parameter bei Maps zu erläutern.

Bisher entdecke ich dadurch mehr Nachteile als Vorteile.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
"Konsidda det ö Diwoass!" (A. Schwarzenegger als Douglas Quaid)
Ingo R. Homann
2005-11-23 16:03:34 UTC
Permalink
Hi,
Post by Dirk Försterling
Statt untypisiert von blödsinnigem Verhalten zu schreiben, würde ein
konkretes Beispiel helfen, daß mir die Fatalität von untypisiertem
get()-Parameter bei Maps zu erläutern.
Mit genau diesem Beispiel fing doch die Diskussion an:

Map<Integer,String> map=...
map.put(new Integer(1),"foo");
...
String s=map.get("1");

Der Compiler könnte hier wunderbar merken, dass in der letzten Zeile ein
Programmierfehler vorliegt, und s daher *immer* null ist.

Aber er merkt es nicht!

Ciao,
Ingo
Ralf Ullrich
2005-11-23 16:22:56 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Dirk Försterling
Statt untypisiert von blödsinnigem Verhalten zu schreiben, würde ein
konkretes Beispiel helfen, daß mir die Fatalität von untypisiertem
get()-Parameter bei Maps zu erläutern.
Map<Integer,String> map=...
map.put(new Integer(1),"foo");
...
String s=map.get("1");
Der Compiler könnte hier wunderbar merken, dass in der letzten Zeile ein
Programmierfehler vorliegt, und s daher *immer* null ist.
Aber er merkt es nicht!
Und das ist auch gut so! Es ist nämlich kein Programmierfehler! Wäre es ein Programmierfehler, dann
wäre konsequenterweise auch folgendes ein Programmierfehler:

"eins".equals(new Integer(1));

Natürlich ist das immer false, aber deswegen noch lange kein Programmierfehler.

Für die Gleichheitsrelation ist nämlich der Typ nicht zwingend notwendig. Zwei Dinge verschiedenen
Typs sind eben grundsätzlich auch verschieden, sprich: nicht gleich.

Dagegen ist für die Ordnungsrelation (siehe Comparable<T>) der Typ zwingend notwendig. Da zwischen
zwei Dingen verschiedenen Typs grundsätzlich keine Ordnung definiert ist.

Beispiel:

"eins".compareTo(new Integer(1));

ist kompilierbarer Java 1.4 Code, der aber stets eine ClassCastException wirft, also nicht lauffähig
ist. Unter Java 1.5 kommt ein Compile-Time Error.

Das Beispiel mit equals hingegen kompiliert und läuft unter Java 1.4 und folglich muss es auch unter
Java 1.5 kompilieren und laufen. Und das geht nur mit equals(Object) (statt einem hypothetischen
equals(T) ).

Vergleichbares gilt auch für die generified Collection Interfaces in Bezug auf Methoden wie get,
contains, removeAll, etc. worum es ja in diesem Thread eigentlich geht.


cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 07:37:18 UTC
Permalink
Hi Ralf,
Post by Ralf Ullrich
Und das ist auch gut so! Es ist nämlich kein Programmierfehler! Wäre es
ein Programmierfehler, dann wäre konsequenterweise auch folgendes ein
"eins".equals(new Integer(1));
Natürlich ist das immer false, aber deswegen noch lange kein
Programmierfehler.
Du argumentierst hier im Kreis, ähnlich wie Dirk. Du sagst "das
Verhalten ist spezifiziert, also ist es kein Programmierfehler". Würde
der Ausdruck oben einen Compiler-Fehler liefern, könnte ich genauso
argumentieren: "es ist spezifiziert, dass es einen Compiler-Fehler
liefert, also ist es auch ein Programmierfehler". Solche Argumente
bringen nix.

Fakt ist aber: Ein Ausdruck, der immer dasselbe Ergebnis liefert, macht
keinen Sinn und kann wegoptimiert werden. Das ist ein deutliches Zeichen
dafür, dass der Programmierer eigentlich was ganz anderes gedacht hat,
und er einen ("semantischen") Bug eingebaut hat.

Der Compiler könnte das - wenn er nur etwas schlauer wäre - problemlos
bemerken und ankreiden.
Post by Ralf Ullrich
Vergleichbares gilt auch für die generified Collection Interfaces in
Bezug auf Methoden wie get, contains, removeAll, etc.
Ganz genau!

Ciao,
Ingo
Dirk Försterling
2005-11-24 08:54:25 UTC
Permalink
Post by Ingo R. Homann
Post by Ralf Ullrich
"eins".equals(new Integer(1));
Natürlich ist das immer false, aber deswegen noch lange kein
Programmierfehler.
Du argumentierst hier im Kreis, ähnlich wie Dirk. Du sagst "das
Verhalten ist spezifiziert, also ist es kein Programmierfehler". Würde
Ich glaube nicht, daß das Beispiel so wörtlich gemeint war.
Post by Ingo R. Homann
Fakt ist aber: Ein Ausdruck, der immer dasselbe Ergebnis liefert, macht
"hat"
Post by Ingo R. Homann
keinen Sinn und kann wegoptimiert werden. Das ist ein deutliches Zeichen
dafür, dass der Programmierer eigentlich was ganz anderes gedacht hat,
und er einen ("semantischen") Bug eingebaut hat.
Für das Konkrete Beispiel könnte man noch darüber streiten. Ich finde,
wenn jemand wörtlich "eins".equals(new Integer(1)) verwendet und nicht
weiß daß das immer false ist, dann kann auch kein Compiler mehr helfen.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
"Leave the bugs in! Call them 'features'" - Some guys at M$ ?
Ralf Ullrich
2005-11-24 09:23:17 UTC
Permalink
Post by Ingo R. Homann
Fakt ist aber: Ein Ausdruck, der immer dasselbe Ergebnis liefert, macht
keinen Sinn und kann wegoptimiert werden. Das ist ein deutliches Zeichen
dafür, dass der Programmierer eigentlich was ganz anderes gedacht hat,
und er einen ("semantischen") Bug eingebaut hat.
Der Compiler könnte das - wenn er nur etwas schlauer wäre - problemlos
bemerken und ankreiden.
Soso, ein Ausdruck der immer dasselbe Ergebnis liefert "macht" also keinen Sinn?

Bist du dir sicher?


.

.

Komisch, ich finde in vielen meiner Programme folgendes Konstrukt:

private static final boolean DEBUG = true; // or false;
...
if (DEBUG) {
// do some debug output
}
...


Ich finde diesen Code überhaupt nicht sinnlos und würde mich ziemlich darüber ärgern, wenn der
Compiler das als Fehler ankreiden würde und mich damit zu Aus- und Einkommentier-Orgien zwingen
würde, wo ich jetzt schlicht ein s/true/false/ machen kann und darauf vertraue, dass HotSpot (o.ä.)
das entstehende "if (false) {...}" im produktiven Betrieb komplett wegoptimiert.

Im übrigen hat Dirk recht, du solltest mein voriges Beispiel nicht ganz so wörtlich nehmen.

cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 10:02:26 UTC
Permalink
Hi,
Post by Ralf Ullrich
Post by Ingo R. Homann
Fakt ist aber: Ein Ausdruck, der immer dasselbe Ergebnis liefert,
macht keinen Sinn und kann wegoptimiert werden. Das ist ein deutliches
Zeichen dafür, dass der Programmierer eigentlich was ganz anderes
gedacht hat, und er einen ("semantischen") Bug eingebaut hat.
Der Compiler könnte das - wenn er nur etwas schlauer wäre - problemlos
bemerken und ankreiden.
Soso, ein Ausdruck der immer dasselbe Ergebnis liefert "macht" also keinen Sinn?
Das sagt man da, wo ich "weg" komme, so. ;-)
Post by Ralf Ullrich
Bist du dir sicher?
private static final boolean DEBUG = true; // or false;
...
if (DEBUG) {
// do some debug output
}
...
Da ändert man den Wert der Variable ja auch ab und zu mal, so dass der
Ausdruck tatsächlich nicht konstant (über verschiedene Compiliervorgänge
hinweg) ist.

Eine Parallele zu dem von dir genannten String.equals(Integer)-Beispiel
sehe ich daher hier nicht.

Würde in deinem Code stehen...

if(false) {
// do some debug output
}

...dann würde ich dir Recht geben: Das macht keinen Sinn!
Post by Ralf Ullrich
Im übrigen hat Dirk recht, du solltest mein voriges Beispiel nicht ganz
so wörtlich nehmen.
Ich habe das Beispiel auch nicht insofern wörtlich genommen, dass ich
denke, irgendwo im Programmtext "eins".equals(new Integer(1)) zu finden.

Aber sinngemäß kann ich mir schon vorstellen, soetwas irgendwo zu finden:

class Servlet extends HttpServlet {
void doGet(HttpRequest requ, ...) {
if(requ.getParameter("some_id").equals(1)) {
...
}
}
}

Oder noch "besser":

void foo(Long x) {
if(x.equals(0)) {
...
}
}

Das ganze könnte man natürlich noch besser verstecken.

Das sind "Stolperfällchen", die müssen einfach nicht sein.

Ciao,
Ingo
Timo Stamm
2005-11-23 17:37:51 UTC
Permalink
Post by Ingo R. Homann
Mal davon abgesehen, dass es gerade bei einer Map (schlimmer noch
speziell bei einer TreeMap, aber Du hast hier ja "nur" eine HashMap)
gefährlich bzw. fatal sein kann (kann, nicht muss), keys
unterschiedlichen Typs zu verwenden (auch wenn es direkte Subtypen
sind), spräche doch (wenn die Map so typsicher wäre, wie ich es gerne
hätte) in deinem Fall nichts dagegen, einfach eine Map<String,Special>
anstelle einer Map<SpecialKey,Special> zu verwenden!
Dann bekomme ich aber kein Set<SpecialKey> bei keySet(), was ich
unbeding haben möchte :)
Wanja Gayk
2005-11-23 12:38:18 UTC
Permalink
Post by Timo Stamm
private Map<String, Special> m = ...
public Special lookup(Object key) {
return m.get(key);
}
Diese lookup Methode nimmt jedes Objekt an. Du hast hier also keinerlei
Abhängigkeit was den Typ angeht. Wenn der key nicht passt, bekommst du
null zurück. Ich sehe keinen Grund warum man String als Typ des keys
festlegen sollte.
z.B. weil man verhinderm will, dass jemand sowas macht:

private Map<String, Special> m= ...
private List<String> someList ...
private List<Integer> someOtherList ...

//..
final Special something = someOtherList.get(index);
//..

Die Schusseligkeit, dass man eigentlich someList hätte nehmen müssen,
würde sofort auffallen, wenn der Key typsicher wäre.
Post by Timo Stamm
Mit Generics kannst du deine Anwendung zwar viel besser durchtypisieren
als mit Java 1.4, aber das ist nicht immer wünschenswert. Es macht
keinen Sinn, immer alles auf typen festzulegen. Deine Architektur wird
dadurch starr.
Niemand hindert dich daran eine untypisierte Map oder eine entsprechend
typisierte Map, wie Map<Object, Special> zu nehmen, wenn es sinnvoll
ist. Ein Sinn und Zweck von Generics ist nun mal die Typen entsprechend
absichern zu können. Da ist es IMO unsinnig, wenn das im Bedarfsfall
nicht geleistet wird.

Gruß,
-Wanja-
--
"Gewisse Schriftsteller sagen von ihren Werken immer: 'Mein Buch, mein
Kommentar, meine Geschichte'. [..] Es wäre besser, wenn sie sagten:
'unser Buch, unser Kommentar, unsere Geschichte'; wenn man bedenkt, dass
das Gute darin mehr von anderen ist als von ihnen." [Blaise Pascal]
Dirk Försterling
2005-11-23 14:57:49 UTC
Permalink
Post by Wanja Gayk
Post by Timo Stamm
null zurück. Ich sehe keinen Grund warum man String als Typ des keys
festlegen sollte.
private Map<String, Special> m= ...
private List<String> someList ...
private List<Integer> someOtherList ...
//..
final Special something = someOtherList.get(index);
//..
Die Schusseligkeit, dass man eigentlich someList hätte nehmen müssen,
würde sofort auffallen, wenn der Key typsicher wäre.
Der key _ist_ Typsicher. Es wird keine ClassCastException zur Laufzeit
auftreten. Zudem entspricht die Antwort von get() mit dem überein, was
die Doku behauptet.
Post by Wanja Gayk
Niemand hindert dich daran eine untypisierte Map oder eine entsprechend
typisierte Map, wie Map<Object, Special> zu nehmen, wenn es sinnvoll
ist.
Mit Map<Object,Special> machst Du aber die Typsicherheit wirklich
kaputt, da get() dann nur noch Objects liefert und put() beliebige
Objects akzeptiert.
Post by Wanja Gayk
Ein Sinn und Zweck von Generics ist nun mal die Typen entsprechend
absichern zu können. Da ist es IMO unsinnig, wenn das im Bedarfsfall
nicht geleistet wird.
Wenn das wirklich der Fall wäre, dann müßte es ja "Typesafes" heißen
und nicht "Generics". Die Generics sind in erster Linie dazu da,
um Containertypen und ähnliches ohne hässliche Casts zu gestalten,
so daß man sicherstellen kann, daß die in einem Container gespeicherten
Objekte nicht bezüglich ihres Typs verwechselt werden. Und das ist der
Fall. Daß z.B. get() einer Map ohne Key-Typ im Parameter definiert wurde
hat damit nur indirekt zu tun.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
A professor is one who talks in someone else's sleep.
Ingo R. Homann
2005-11-23 15:11:19 UTC
Permalink
Hi,
Post by Dirk Försterling
Der key _ist_ Typsicher. Es wird keine ClassCastException zur Laufzeit
auftreten.
Erstens ist das nicht die allgemein anerkannte Definiton von "typsicher",...
Post by Dirk Försterling
Zudem entspricht die Antwort von get() mit dem überein, was
die Doku behauptet.
...und zweitens ist das blödsinnige Verhalten einer Klasse nicht
automatisch dadurch besser, dass es dokumentiert ist.
Post by Dirk Försterling
Post by Wanja Gayk
Niemand hindert dich daran eine untypisierte Map oder eine entsprechend
typisierte Map, wie Map<Object, Special> zu nehmen, wenn es sinnvoll
ist.
Mit Map<Object,Special> machst Du aber die Typsicherheit wirklich
kaputt, da get() dann nur noch Objects liefert
Keineswegs! Es liefert 'Specials'!
Post by Dirk Försterling
und put() beliebige Objects akzeptiert.
Nur für die Keys - und das ist es doch, was Du (bei get()) gerade
befürwortest!
Post by Dirk Försterling
Wenn das wirklich der Fall wäre, dann müßte es ja "Typesafes" heißen
und nicht "Generics". Die Generics sind in erster Linie dazu da,
um Containertypen und ähnliches ohne hässliche Casts zu gestalten,
so daß man sicherstellen kann, daß die in einem Container gespeicherten
Objekte nicht bezüglich ihres Typs verwechselt werden.
Aber die Keys sollen verwechselt werden dürfen?

Ciao,
Ingo
Dirk Försterling
2005-11-23 15:32:37 UTC
Permalink
Post by Ingo R. Homann
Post by Dirk Försterling
Der key _ist_ Typsicher. Es wird keine ClassCastException zur Laufzeit
auftreten.
Erstens ist das nicht die allgemein anerkannte Definiton von
"typsicher",...
Ja, wo ist sie denn?
Post by Ingo R. Homann
Post by Dirk Försterling
Mit Map<Object,Special> machst Du aber die Typsicherheit wirklich
kaputt, da get() dann nur noch Objects liefert
Keineswegs! Es liefert 'Specials'!
Post by Dirk Försterling
und put() beliebige Objects akzeptiert.
Nur für die Keys - und das ist es doch, was Du (bei get()) gerade
befürwortest!
Post by Dirk Försterling
Wenn das wirklich der Fall wäre, dann müßte es ja "Typesafes" heißen
und nicht "Generics". Die Generics sind in erster Linie dazu da,
um Containertypen und ähnliches ohne hässliche Casts zu gestalten,
so daß man sicherstellen kann, daß die in einem Container gespeicherten
Objekte nicht bezüglich ihres Typs verwechselt werden.
Aber die Keys sollen verwechselt werden dürfen?
Werden sie nicht. Du wirst durch get( Object key ) nie in die
Situation geraten, daß Du einen Integer-key nach seiner .length()
fragst.
Bei get() und den ganzen anderen Zugriffsmethoden steht
eben nicht die Datenpflege im Vordergrund, sondern die Bedeutung
der Operation. Und die stimmt so wie es ist. Oder nicht? Ich warte
immernoch auf Beispiele.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
"Wie zur Hölle hat er das überleben können?" - Spawn
Ralf Ullrich
2005-11-23 15:40:55 UTC
Permalink
Hi Michael,
Post by Michael Matzl
Warum ist get() nicht type-safe?
Bin schon gespannt auf eure Antworten.
Kompliment zu deiner (nur scheinbar) simplen Frage, denn alle bisherigen Antworten darauf waren
ziemlich falsch, obwohl doch die Gründe in der hoffentlich allseits bekannten 70kB PDF Datei
generics-tutorial.pdf beschrieben sind (siehe S. 5). (Stand 16:00 Uhr MEZ)


Hier angepasst für Map wiedergegeben:

Wäre Map#get definiert als "V get(K key)", dann gäbe folgender Code einen Compile-Time-Error:

Map<?,String> m = new HashMap<String,String>();
String s = m.get("special");

und das wiederum hieße, das generische Map-Interface wäre nicht länger in der Lage den gleichen
API-Contract zu erfüllen, wie der Raw-Type und damit wäre die Rückwärtskompatiblität gebrochen.



Nochmal, um es noch deutlicher zu sagen:

Map#get wurde als "V get(Object key)" "generified" damit folgender Code ebenfalls "generified"
werden kann:

Map m = ... ; // a Map from unknown keys to Strings
String s = m.get("special");

Ansonsten, könnte der Code nämlich nur dann "generified" werden, wenn man den Typ der Keys weiß. Man
dürfte also niemals den Wildcard an Stelle des Key-Typs verwenden.



Noch deutlicher als bei get() wird das Phänomen natürlich bei den contains-Methoden:

Map m<?,?> = ...; // any Map
if (m.containsKey(anyKey)) {
// do something
}

Dieser Code würde illegal, wenn containsKey den Key-Type voraussetzen würde.

cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ralf Ullrich
2005-11-23 16:01:47 UTC
Permalink
Post by Ralf Ullrich
Map m<?,?> = ...; // any Map
Tstsk, da hat meine Brainware CodeWriter-Implementierung wohl gesponnen. hätte natürlich

Map<?,?> m = ...; // any Map

sein sollen.

cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Dirk Försterling
2005-11-23 16:07:11 UTC
Permalink
Post by Ralf Ullrich
Kompliment zu deiner (nur scheinbar) simplen Frage, denn alle bisherigen
Antworten darauf waren ziemlich falsch, obwohl doch die Gründe in der
Hey, jau. Vielen Dank für das Beispiel. :)

An die Wildcards hatte ich hier gar nicht gedacht. Ich hatte sogar das
generics-tutorial.pdf verfügbar, doch meine verschrumpelte Walnuß hat auf
Seite 5 bisher nur was über den get() Ergebnistyp gelesen. In den Genuß,
der get()-Benutzung von Maps unbekannter Typen bin ich bisher noch nicht
gekommen...

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
"mit DSL kann man im Internet schneller warten"
Ingo R. Homann
2005-11-23 16:08:11 UTC
Permalink
Hi,
Post by Ralf Ullrich
Wäre Map#get definiert als "V get(K key)", dann gäbe folgender Code
Map<?,String> m = new HashMap<String,String>();
String s = m.get("special");
...
Map#get wurde als "V get(Object key)" "generified" damit folgender Code
Map m = ... ; // a Map from unknown keys to Strings
String s = m.get("special");
Was spricht - wenn man den key-Typ tatsächlich nicht kennt (was mir
fragwürdig erscheint, aber egal) und man die Map nachträglich
"generifizieren" will, denn gegen eine Map<Object,Special>?

Ciao,
Ingo
Ralf Ullrich
2005-11-23 16:28:49 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Ralf Ullrich
Wäre Map#get definiert als "V get(K key)", dann gäbe folgender Code
Map<?,String> m = new HashMap<String,String>();
String s = m.get("special");
...
Map#get wurde als "V get(Object key)" "generified" damit folgender
Map m = ... ; // a Map from unknown keys to Strings
String s = m.get("special");
Was spricht - wenn man den key-Typ tatsächlich nicht kennt (was mir
fragwürdig erscheint, aber egal) und man die Map nachträglich
"generifizieren" will, denn gegen eine Map<Object,Special>?
Ciao,
Ingo
Generify this code:

public static Integer SPECIAL_INTEGER = new Integer(1);
public static String SPECIAL_STRING = "eins";


boolean checkForSpecialKeys(Map m) {
return m.containsKey(SPECIAL_STRING) || m.containsKey(SPECIAL_INTEGER);
}


Kriegst du das mit Map<Object,Special> hin?



Und wenn ja, funktioniert das auch noch mit:

Map<Comparable<?>,?> m = ...;
if (checkForSpecialKeys(m)) {
// do something
}


cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 07:57:52 UTC
Permalink
Hi,
Post by Ralf Ullrich
public static Integer SPECIAL_INTEGER = new Integer(1);
public static String SPECIAL_STRING = "eins";
boolean checkForSpecialKeys(Map m) {
return m.containsKey(SPECIAL_STRING) || m.containsKey(SPECIAL_INTEGER);
}
Kriegst du das mit Map<Object,Special> hin?
Ja:

boolean checkForSpecialKeys(Map<Object,ObjectOderWhatever> m) {
return m.containsKey(SPECIAL_STRING) || m.containsKey(SPECIAL_INTEGER);
}

(Das war wohl noch die Fangfrage? ;-)
Post by Ralf Ullrich
Map<Comparable<?>,?> m = ...;
if (checkForSpecialKeys(m)) {
// do something
}
Nein. Aber es funktioniert, wenn ich die Signatur von
checkForSpecialKeys entsprechend ändere:

boolean checkForSpecialKeys(Map<Comparable<?>,?> m) {
return m.containsKey(SPECIAL_STRING) || m.containsKey(SPECIAL_INTEGER);
}

Jetzt könntest Du argumentieren, dass ich dann nur noch
"Comparable-Maps" prüfen lassen kann.

Aber IMHO ist das Beispiel sowieso sehr konstruiert. Wer tut schon
*völlig* unterschiedliche Objekte in eine Map, die lediglich den
gemeinsamen Supertyp 'Object' haben? Und wenn ich eine Map auf Strings
und Integers testen will, dann macht es keinen Sinn, eine Map darauf zu
testen, die sowieso nur ganz andere Objekte enthält, wo also ohnehin
immer false zurückgegeben wird. Im Gegenteil: So würde mir der Compiler
sogar mitteilen, dass da irgendwas im Argen liegt!

Ich finde nicht, dass sun alles daran setzen sollte, dass Leute ihren
(IMHO schlecht designeten) Code parametrisieren können. Wenn - um
bestehenden (IMHO schlechten) Code parametrisieren zu können - vorher
ein kleineres Refactoring nötig sein sollte, finde ich das nicht schlimm.

Und aktuelle Problemstellungen, die ohnehin neu Designed werden müssen,
lassen sich IMHO problemlos erschlagen.

Aber vielleicht übersehe ich was? Kann vielleicht jemand mal ein
Praxis-nahes Beispiel bringen?

Ciao,
Ingo
Ralf Ullrich
2005-11-24 11:01:52 UTC
Permalink
Post by Ingo R. Homann
Aber vielleicht übersehe ich was? Kann vielleicht jemand mal ein
Praxis-nahes Beispiel bringen?
Ja, du übersiehst etwas und das Beispiel checkForSpecialKeys war im Prinzip bereits Praxis-nah, aber
vielleicht brauchst du auch einfach ein komplexeres Beispiel um es zu verstehen. Bitte schön:

IRHMap ist eine Variante des Map-Interfaces mit den von dir gewünschten Definitionen für
containsKey, containsValue und get.

isCompleteEnumMapping ist eine Methode, die überprüft, ob in einer Map für jeden möglichen Wert
eines Enum-Typs ein passendes Mapping vorhanden ist. Wie du siehst, lässt sich diese Methode mit der
"herkömmlichen" Map einwandfrei implementieren. Aber mit der IRHMap gibt es dann Probleme.

Viel Spaß beim durchdenken und experimentieren. (Wenn du es schaffst eine funktionierende
Implementation der Methode für IRHMap vorzuweisen, wäre ich sehr daran interessiert. Wichtig: die
unten aufgeführten Dummy-Aufrufe für IRHMap sollten sich alle kompilieren lassen.)

cu


import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class GenericDemo {

public interface IRHMap<K, V> {

int size();

boolean isEmpty();

boolean containsKey(K key);

boolean containsValue(V value);

V put(K key, V value);

V remove(K key);

void putAll(Map<? extends K, ? extends V> t);

void clear();

Set<K> keySet();

Collection<V> values();

Set<Map.Entry<K, V>> entrySet();

boolean equals(Object o);

int hashCode();
}

enum NUMBERS {
ONE, TWO, THREE, FOUR
}

enum LETTERS {
ALPHA, BETA, GAMMA, DELTA
}

public static boolean isCompleteEnumMapping(Class<? extends Enum> clz,
Map<? extends Enum, ?> map) {
for (Enum e : clz.getEnumConstants()) {
if (!map.containsKey(e))
return false;
}
return true;
}

public static boolean isCompleteEnumMapping(Class<? extends Enum> clz,
IRHMap<? extends Enum, ?> map) {
for (Enum e : clz.getEnumConstants()) {
if (!map.containsKey(e)) // compile time error!!
return false;
}
return true;
}

public static void main(String[] args) {
Map<NUMBERS, String> mapOne = new HashMap<NUMBERS, String>();
mapOne.put(NUMBERS.ONE, "1");
mapOne.put(NUMBERS.TWO, "2");
mapOne.put(NUMBERS.THREE, "3");
mapOne.put(NUMBERS.FOUR, "4");
Map<LETTERS, String> mapTwo = new HashMap<LETTERS, String>();
mapTwo.put(LETTERS.ALPHA, "1");
mapTwo.put(LETTERS.BETA, "2");
mapTwo.put(LETTERS.GAMMA, "3");
mapTwo.put(LETTERS.DELTA, "4");
Map<Enum, String> mapThree = new HashMap<Enum, String>();
mapThree.putAll(mapOne);
mapThree.putAll(mapTwo);
isCompleteEnumMapping(NUMBERS.class, mapOne); // true!
isCompleteEnumMapping(LETTERS.class, mapTwo); // true!
isCompleteEnumMapping(NUMBERS.class, mapThree); // true!
isCompleteEnumMapping(LETTERS.class, mapThree); // true!

// dummies for IRHMap
IRHMap<NUMBERS, String> irhOne = null;
IRHMap<LETTERS, String> irhTwo = null;
IRHMap<Enum, String> irhThree = null;
isCompleteEnumMapping(NUMBERS.class, irhOne);
isCompleteEnumMapping(LETTERS.class, irhTwo);
isCompleteEnumMapping(NUMBERS.class, irhThree);
isCompleteEnumMapping(LETTERS.class, irhThree);
}
}
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 12:55:06 UTC
Permalink
Hi,
Post by Ralf Ullrich
Ja, du übersiehst etwas und das Beispiel checkForSpecialKeys war im
Prinzip bereits Praxis-nah, aber vielleicht brauchst du auch einfach ein
IRHMap ist eine Variante des Map-Interfaces mit den von dir gewünschten
Definitionen für containsKey, containsValue und get.
isCompleteEnumMapping ist eine Methode, die überprüft, ob in einer Map
für jeden möglichen Wert eines Enum-Typs ein passendes Mapping vorhanden
ist. Wie du siehst, lässt sich diese Methode mit der "herkömmlichen" Map
einwandfrei implementieren. Aber mit der IRHMap gibt es dann Probleme.
Viel Spaß beim durchdenken und experimentieren. (Wenn du es schaffst
eine funktionierende Implementation der Methode für IRHMap vorzuweisen,
wäre ich sehr daran interessiert. Wichtig: die unten aufgeführten
Dummy-Aufrufe für IRHMap sollten sich alle kompilieren lassen.)
Danke für das (ausführliche und Praxis-nahe!) Beispiel!

(Leider war ich zwischendurch Mittagessen, daher meine verspätete
Antwort :-)

Allerdings bestätigt das ganze mein Argument aus meinem letzten Posting
in diesem Subthread (oder zumindest *fühle* ich mich bestätigt -
vielleicht kannst Du das ja widerlegen):

Du bringst hier IMHO ein Design (genauer *eine* Methoden-Deklaration),
die ich als so nicht sinnvoll ansehen würde, und sich in der Tat auch
nicht exakt parametrisieren läßt. Allerdings läßt sich das abstrakte
Problem (bei gleicher Funktionalität) sehr einfach lösen, wenn man die
Methoden-Deklaration geringfügig ändert (um nicht zu sagen sogar
vereinfacht):

Aus ...

public static boolean isCompleteEnumMapping(Class<? extends Enum> clz,
IRHMap<? extends Enum, ?> map)

... mach ...

public static <K> boolean isCompleteEnumMapping(K[] ks, IRHMap<K, ?> map)

Das ist IMHO viel einfacher, plausibler und sogar flexibler, weil auch
eine weitere Art von Aufruf funktioniert, von denen ich für jede der
Maps eine in den Test-Teil angehängt habe. Den kompletten Source hänge
ich mal unten an.

Wie gesagt, Du könntest jetzt argumentieren, dass (um alten Code zu
parametrisieren) vorher ein Mini-Refactoring nötig ist. Aber das ist
(A) mit einer IDE wie Eclipse quasi vollautomatisch machbar
(B) sowieso meistens nötig, wenn man alten Code "tunen" will
(C) sollte das kein Argument sein für eine Verkomplizierung des neuen Codes

Bin gespannt auf Deine anderen Einwände.

Ciao,
Ingo


import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class GenericDemo
{

public interface IRHMap<K, V>
{

int size();

boolean isEmpty();

boolean containsKey(K key);

boolean containsValue(V value);

V put(K key, V value);

V remove(K key);

void putAll(Map<? extends K, ? extends V> t);

void clear();

Set<K> keySet();

Collection<V> values();

Set<Map.Entry<K, V>> entrySet();

boolean equals(Object o);

int hashCode();
}

enum NUMBERS {
ONE, TWO, THREE, FOUR
}

enum LETTERS {
ALPHA, BETA, GAMMA, DELTA
}

public static boolean isCompleteEnumMapping(Class<? extends Enum> clz,
Map<? extends Enum, ?> map)
{
for (Enum e : clz.getEnumConstants())
{
if (!map.containsKey(e))
return false;
}
return true;
}

public static <K> boolean isCompleteEnumMapping(K[] ks, IRHMap<K, ?> map)
{
for (K e : ks)
{
if (!map.containsKey(e)) // no compile time error!!
return false;
}
return true;
}

public static void main(String[] args)
{
Map<NUMBERS, String> mapOne = new HashMap<NUMBERS, String>();
mapOne.put(NUMBERS.ONE, "1");
mapOne.put(NUMBERS.TWO, "2");
mapOne.put(NUMBERS.THREE, "3");
mapOne.put(NUMBERS.FOUR, "4");
Map<LETTERS, String> mapTwo = new HashMap<LETTERS, String>();
mapTwo.put(LETTERS.ALPHA, "1");
mapTwo.put(LETTERS.BETA, "2");
mapTwo.put(LETTERS.GAMMA, "3");
mapTwo.put(LETTERS.DELTA, "4");
Map<Enum, String> mapThree = new HashMap<Enum, String>();
mapThree.putAll(mapOne);
mapThree.putAll(mapTwo);
isCompleteEnumMapping(NUMBERS.class, mapOne); // true!
isCompleteEnumMapping(LETTERS.class, mapTwo); // true!
isCompleteEnumMapping(NUMBERS.class, mapThree); // true!
isCompleteEnumMapping(LETTERS.class, mapThree); // true!

// dummies for IRHMap
IRHMap<NUMBERS, String> irhOne = null;
IRHMap<LETTERS, String> irhTwo = null;
IRHMap<Enum, String> irhThree = null;
isCompleteEnumMapping(NUMBERS.class.getEnumConstants(), irhOne);
GenericDemo.<NUMBERS> isCompleteEnumMapping(new NUMBERS[] {
NUMBERS.ONE,
NUMBERS.TWO}, irhOne);
isCompleteEnumMapping(LETTERS.class.getEnumConstants(), irhTwo);
GenericDemo.<LETTERS> isCompleteEnumMapping(new LETTERS[] {
LETTERS.ALPHA,
LETTERS.BETA}, irhTwo);
isCompleteEnumMapping(NUMBERS.class.getEnumConstants(), irhThree);
isCompleteEnumMapping(LETTERS.class.getEnumConstants(), irhThree);
GenericDemo.<Enum> isCompleteEnumMapping(new Enum[] { LETTERS.ALPHA,
NUMBERS.ONE}, irhThree);
}
}
Ralf Ullrich
2005-11-24 13:18:53 UTC
Permalink
Post by Ingo R. Homann
Du bringst hier IMHO ein Design (genauer *eine* Methoden-Deklaration),
die ich als so nicht sinnvoll ansehen würde, und sich in der Tat auch
nicht exakt parametrisieren läßt.
Oh schade, und ich dachte du kennst dich mit Generics aus:

1. Sie ließe sich korrekt parametrisieren, nur hast du es scheinbar nicht geschafft:

public static <E extends Enum> boolean isCompleteEnumMapping(Class<E> clz,
IRHMap<? super E, ?> map) {

2. Hättest du einen kleinen Fehler in meinem Beispiel finden können. Korrekt wäre nämlich:

public static boolean isCompleteEnumMapping(Class<? extends Enum> clz,
Map<?, ?> map) {

3. Was bedeutet 1. und 2.?

Map<?,?> mapFour = null;
isCompleteEnumMapping(NAMES.class, mapFour);
IRHMap<?,?> irhFour = null;
isCompleteEnumMapping(NAMES.class, irhFour); // compile time error

Map<String,?> mapFive = null;
isCompleteEnumMapping(NAMES.class, mapFive);
IRHMap<String,?> irhFive = null;
isCompleteEnumMapping(NAMES.class, irhFive); // compile time error

Das ist es, was es bedeutet. Und du sagst hier, das ist genau was du wünschst, weil es sich ganz
sicher um Programmierfehler handelt, und Sun und ich (und viele andere) sagen, dass dies keine
Programmierfehler sind. (Und BTW: nur bei mapFive kannst du sicher sein, das es immer false ist. Bei
mapFour hast du schlicht keine ausreichenden Informationen.)
Post by Ingo R. Homann
public static <K> boolean isCompleteEnumMapping(K[] ks, IRHMap<K, ?> map)
Und warum soll folgender Code sich nicht kompilieren lassen? (was er aufgrund deiner Def. nämlich
nicht tut?)

String[] sa = null;
IRHMap<Object,Object> im = null;
...
isCompleteEnumMapping(sa, im);

(Oder schwieriger:
String[] sa = null;
IRHMap<?,?> im = null;
...
isCompleteEnumMapping(sa, im);
)


cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 14:06:09 UTC
Permalink
Hi,
Geht so - ich versuche, immer dazuzulernen. Speziell den ?-Wildcard und
den lower-bound (super) habe ich in der Praxis allerdings noch nicht
eingesetzt / einsetzen müssen / vermisst.
Hast Du das jetzt im Nachhinein festgestellt, oder war es eine
Fangfrage? ;-)
Post by Ralf Ullrich
public static <E extends Enum> boolean
isCompleteEnumMapping(Class<E> clz,
IRHMap<? super E, ?> map) {
Klingt nicht völlig unlogisch. Allerdings mit der ganz o.g. Einschränkung!

Sorry, im Folgenden wird mir dein Design zu kompliziert, daher kann ich
da nicht näher drauf eingehen. Mir ist auch nicht klar, was Du gegen
mein (einfacheres) Design einzuwenden hast.
Post by Ralf Ullrich
Post by Ingo R. Homann
public static <K> boolean isCompleteEnumMapping(K[] ks, IRHMap<K, ?> map)
Und warum soll folgender Code sich nicht kompilieren lassen? (was er
aufgrund deiner Def. nämlich nicht tut?)
String[] sa = null;
IRHMap<Object,Object> im = null;
...
isCompleteEnumMapping(sa, im);
Moment. Wir müssen da mal drei Fälle unterscheiden, gegeben sei

String[] sa = null;
IRHMap<Object, Object> im = null;

Dann gibt es folgende Aufrufe:

(1)
GenericDemo.<String> isCompleteEnumMapping(sa, im); // compile-error

(2)
GenericDemo.<Object> isCompleteEnumMapping(sa, im);

(3)
GenericDemo.isCompleteEnumMapping(sa, im);

(1) funktioniert nicht. (2) und daher auch (3) schon. Worauf willst Du
nun hinaus?

Ciao,
Ingo
Ralf Ullrich
2005-11-24 14:54:25 UTC
Permalink
Post by Ingo R. Homann
Hast Du das jetzt im Nachhinein festgestellt, oder war es eine
Fangfrage? ;-)
Fangfrage. (Nur das Map<? extends Enum,?> statt Map<?,?> war eigentlich ein Versehen.)
Post by Ingo R. Homann
Post by Ralf Ullrich
Und warum soll folgender Code sich nicht kompilieren lassen? (was er
aufgrund deiner Def. nämlich nicht tut?)
String[] sa = null;
IRHMap<Object,Object> im = null;
...
isCompleteEnumMapping(sa, im);
Moment. Wir müssen da mal drei Fälle unterscheiden, gegeben sei
Worauf willst Du
nun hinaus?
Ach immer diese fehlerhaften Schnellschüsse von mir. Da "Object[] oa = new String[10];" zulässig
ist, geht das natürlich. Von mir gemeint war also eher das umgedrehte:

Object[] sa = new String[] { "eins", "zwei"};
IRHMap<String,Object> im = null;
...
isCompleteEnumMapping(sa, im);


Was ich ausdrücken will: Es gibt keinen Grund die beiden Argumente von isCompleteEnumMapping was
ihren Typ anbelangt voneinander abhängig zu machen.

Refactor'n wir mal:

1. Nehmen wir zunächst mal passendere Bezeichner:

public static <K> boolean containsAllKeys(K[] keys, Map<K, ?> map)

2. Aber warum sollen eigentlich der Array-Typ und der Map-Typ voneinander abhängig sein?

Nehmen wir mal zwei Interfaces IFA und IFB und eine Klasse CAnB wie folgt:

interface IFA {}
interface IFB {}
class CAnB implements IFA, IFB {}

Warum soll nun folgender Code einen CompileTime-Fehler erzeugen?

IFA[] ary = new IFA[] { new CAnB() };
Map<IFB,Object> map = null;
...
if (containsAllKeys(ary, map)) {
...
}

Es gibt keinen Grund dafür. Ergo: die Abhängigkeit zwischen den beiden Argumenten muss weg:

public static <K,L> boolean containsAllKeys(K[] keys, Map<L, ?> map)

3. K und L tauchen nur noch jeweils einmal in der Signatur auf, d.h. sie sind effektiv Wildcards:
(Aus dem illegalen ?[] wird dann natürlich Object[])

public static boolean containsAllKeys(Object[] keys, Map<?, ?> map)

So, nun haben wir also die korrekte (und möglichst einfache) Signatur für die von dir vorgeschlagene
neue Implementierung und wieder werden wir das Problem haben, dass diese mit IRHMap nicht zu
implementieren ist.


Und das ist genau der Grund warum contains*, get, remove, indexOf, etc. bei den Collections und bei
Map typ-neutral definiert wurden. Es gibt eben doch Fälle, in denen keine ausreichenden
Informationen über die beteiligten Typen vorhanden sind und dennoch korrekter Code geschrieben
werden kann, der aber bei der von dir favorisierten Map-Implementierung nicht mehr geschrieben
werden könnte.


Ist mir eigentlich egal was du mir jetzt noch antworten willst, weil mir geht langsam die Fantasie
aus, um es dir noch besser erklären zu können, warum es Map#get(Object) und nicht Map#get(K) heißen
muss. (Und darum geht ja eigentlich in diesem Thread.)

cu


BTW: die einfachste Implementierung von containsAllKeys(Object[] keys, Map<?, ?> map) wäre wohl:
"return map.keySet().containsAll(Arrays.asList(keys));" Und jetzt rate mal wie die Signatur von
Collection#containsAll(Collection) korrekt lautet?

a) Collection<T> { boolean containsAll(Collection<T> c); }
b) Collection<T> { boolean containsAll(Collection<? super T> c); }
c) Collection<T> { boolean containsAll(Collection<? extends T> c); }
oder
d) Collection<T> { boolean containsAll(Collection<?> c); }

???? ;-)
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 15:37:52 UTC
Permalink
Hi,
Post by Ralf Ullrich
Post by Ingo R. Homann
Hast Du das jetzt im Nachhinein festgestellt, oder war es eine
Fangfrage? ;-)
Fangfrage.
Kann im Nachhinein ja jeder sagen! ;-)
Post by Ralf Ullrich
Object[] sa = new String[] { "eins", "zwei"};
IRHMap<String,Object> im = null;
...
isCompleteEnumMapping(sa, im);
Das ist aber ein schwaches Argument, weil Du da einfach deinen Code
nicht weit genug parametrisiert hast. (Ich will ja gerade, dass das
nicht geht!)

Das ist quasi das gleiche, als wenn ich bemäkeln würde, dass folgendes
nicht geht:

Object[] sa = new String[] { "eins", "zwei"};
Map<String, Object> m = null;
m.put(sa[0], sa[1]); // compiler error

(Man beachte: Es ist eine Map, keine IRHMap!) Natürlich geht das nicht,
und das ist auch gut so!
Post by Ralf Ullrich
Was ich ausdrücken will: Es gibt keinen Grund die beiden Argumente von
isCompleteEnumMapping was ihren Typ anbelangt voneinander abhängig zu
machen.
public static <K> boolean containsAllKeys(K[] keys, Map<K, ?> map)
2. Aber warum sollen eigentlich der Array-Typ und der Map-Typ
voneinander abhängig sein?
interface IFA {}
interface IFB {}
class CAnB implements IFA, IFB {}
Warum soll nun folgender Code einen CompileTime-Fehler erzeugen?
IFA[] ary = new IFA[] { new CAnB() };
Map<IFB,Object> map = null;
...
if (containsAllKeys(ary, map)) {
...
}
Das letzte Beispiel gefiel mir besser, weil ich das einfach komplett per
Cut&Paste in Eclipse kopieren konnte. ;-)
Damit wir nicht aneinander vorbeireden; Du meinst also:


import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

interface IFA
{//
}

interface IFB
{//
}

class CAnB implements IFA, IFB
{//
}

public class GenericDemo
{

public interface IRHMap<K, V>
{

V put(K key, V value);

V get(K key);

}

public static <K> boolean containsAllKeys(K[] keys, IRHMap<K, ?> map)
{
throw new RuntimeException("Not implemented " + keys + " " + map);
}

public static void main(String[] args)
{
IFA[] ary = new IFA[] { new CAnB()};
Map<IFB, Object> map = null;
GenericDemo.<Object> containsAllKeys(ary, map); // compiler error
}

}

Das geht in der Tat nicht mit meinem Ansatz. Ich verstehe dein Argument,
aber die Frage ist nach wie vor, (a) ob das sinnvoll ist (also ob es
gehen sollte) oder nicht und (b) ob das ein Praxis-nahes Beispiel ist
oder nicht.

Ich fürchte, in beiden Fragen werden wir uns nicht einig...
Post by Ralf Ullrich
Ist mir eigentlich egal was du mir jetzt noch antworten willst, weil mir
geht langsam die Fantasie aus, um es dir noch besser erklären zu können,
...
Ich gebe zu, dass ich bezüglich generics bisher nicht in die tiefsten
Tiefen abgetaucht bin / abtauchen musste. Danke also für einige
Aufklärungen und Denkanstösse - auch wenn ich nicht wirklich überzeugt
von deiner Argumentation bin. Eine überanstrengte Fantasie kann nämlich
auch ein Zeichen dafür sein, dass die Beispiele etwas sehr "konstruiert"
sind...
Post by Ralf Ullrich
Und jetzt rate mal wie
die Signatur von Collection#containsAll(Collection) korrekt lautet?
a) Collection<T> { boolean containsAll(Collection<T> c); }
b) Collection<T> { boolean containsAll(Collection<? super T> c); }
c) Collection<T> { boolean containsAll(Collection<? extends T> c); }
oder
d) Collection<T> { boolean containsAll(Collection<?> c); }
"Lautet" oder "Lauten sollte"? Nein, lassen wir das! ;-)

In diesem Sinne: EOT?

Ciao,
Ingo
Ralf Ullrich
2005-11-24 16:03:24 UTC
Permalink
Post by Ingo R. Homann
Das ist quasi das gleiche, als wenn ich bemäkeln würde, dass folgendes
Object[] sa = new String[] { "eins", "zwei"};
Map<String, Object> m = null;
m.put(sa[0], sa[1]); // compiler error
(Man beachte: Es ist eine Map, keine IRHMap!) Natürlich geht das nicht,
und das ist auch gut so!
NEIN! Es ist was ganz verschiedenes!

put() verändert die Map und muss daher für Type-Safety in diesem Fall bemäkelt werden.
iCEM() liest die Map nur, d.h. Type-Safety wird nicht verletzt, es _MUSS_ nichts bemäkelt werden.
(Auch wenn du dir das noch so sehr _wünschst_.)
Post by Ingo R. Homann
"Lautet" oder "Lauten sollte"? Nein, lassen wir das! ;-)
"lautet".
Post by Ingo R. Homann
In diesem Sinne: EOT?
Gerne. EOT!
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 16:43:00 UTC
Permalink
Hi,
Post by Ralf Ullrich
Post by Ingo R. Homann
Das ist quasi das gleiche, als wenn ich bemäkeln würde, dass folgendes
Object[] sa = new String[] { "eins", "zwei"};
Map<String, Object> m = null;
m.put(sa[0], sa[1]); // compiler error
(Man beachte: Es ist eine Map, keine IRHMap!) Natürlich geht das
nicht, und das ist auch gut so!
NEIN! Es ist was ganz verschiedenes!
put() verändert die Map...
iCEM() liest die Map nur,...
Ich sehe einfach nicht, dass der Unterschied "lesen"/"schreiben" etwas
mit dem Unterschied "typsicher"/"nicht typsicher" zu tun haben soll. Und
ich fürchte, davon kannst Du mich auch nicht überzeugen.

Und (um dich mal zu zitieren): Neben mir sehen das scheinbar auch einige
andere so - dort ist der key getypt:

java.util.prefs.Preferences.get(String key, String def);
java.util.Properties.getProperty(String key);
javax.servlet.http.HttpSession.getAttribute(String name);

Aber wie gesagt, da haben wir einfach unterschiedliche Ansichten - es
ist müßig, darüber zu diskutieren.

Ciao,
Ingo
Ralf Ullrich
2005-11-24 17:27:47 UTC
Permalink
Post by Ingo R. Homann
java.util.prefs.Preferences.get(String key, String def);
java.util.Properties.getProperty(String key);
javax.servlet.http.HttpSession.getAttribute(String name);
Aber wie gesagt, da haben wir einfach unterschiedliche Ansichten - es
ist müßig, darüber zu diskutieren.
Properties, Preferences und Session Attributes sind aber keine allgemeinen Collections sondern
spezielle Fälle von etwas ähnlichem. Preferences und Session Attribute wind wohl auch gerade deshalb
nicht von Collection-Klassen abgeleitet. Und, dass es bei Properties eigentlich ein Design-Fehler
war, wusste man auch vor der Einführung von Generics. Ich zitiere mal aus JavaDoc zu Properties:

"Because Properties inherits from Hashtable, the put and putAll methods can be applied to a
Properties object. Their use is strongly discouraged as they allow the caller to insert entries
whose keys or values are not Strings. The setProperty method should be used instead. If the store or
save method is called on a "compromised" Properties object that contains a non-String key or value,
the call will fail."

Frage: Warum wurde beim Übergang auf Java 5 java.util.Properties nicht als Hashtable<String,String>
statt als Hashtable<Object, Object> parametrisiert? (Und das wäre nun wirklich wünschenswert
gewesen. Und es ist auch keine Fangfrage, weil ich in diesem Fall, auch die Antwort nicht weiß.)


Und ja, ich denke auch wir werden uns nicht einig werden. Aber nur weil:

Tonne<Müll> mülltonne = ....
DiamantRing schmuckstück = ....

wobei gilt

schmuckstück instanceof Müll == false

heißt es noch lange nicht, dass die Frage

mülltonne.contains(schmuckstück)

keinen Sinn macht.

Ich finde sogar es macht sehr viel Sinn auf folgendes zu achten:

if (mülltonne.contains(schmuckstück)) {
mülltonne.remove(schmuckstück);
schatulle.add(schmuckstück);
}
strassenrand.attach(mülltonne);
while (!mülltonne.empty())
wait();
strassenrand.detach(mülltonne);


Und, ja, das ist nun wirklich ein sehr konstruiertes (=fantasievolles) Beispiel. ;-)

cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Dirk Försterling
2005-11-24 21:10:16 UTC
Permalink
Post by Ralf Ullrich
put() verändert die Map und muss daher für Type-Safety in diesem Fall bemäkelt werden.
iCEM() liest die Map nur, d.h. Type-Safety wird nicht verletzt, es
_MUSS_ nichts bemäkelt werden. (Auch wenn du dir das noch so sehr
_wünschst_.)
Hey, das kommt mir jetzt wieder bekannt vor. Da bin ich ja beruhigt, daß
meine Argumentation am Anfang des Threads nicht völlig abwegig gewesen
zu sein scheint, sondern nur unvollständig.

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
veQDuj'oH DujlIj'e'
Wanja Gayk
2005-11-25 01:16:04 UTC
Permalink
Post by Ralf Ullrich
public static <K> boolean containsAllKeys(K[] keys, Map<K, ?> map)
2. Aber warum sollen eigentlich der Array-Typ und der Map-Typ voneinander abhängig sein?
Das Problem bei der Diskussion ist möglicherweise folgendes:

Du willst eine parametrisierte Map, in die du jeden Key reinstecken
kannst, ohne dass der Compiler meckert und dass null raus bekomst, wenn
der Typ nicht passt.
Ausgehend davon konstruierst du all deine Beispiele.

Die andere Fraktion erwartet aber, dass eine parametrisierte Map, die
auf einen bestimmten Typen als Key festgelegt ist, auch nur diesen
akzeptiert, bzw. dass der Compiler im Falle einer Verletzung meckert.

Das sind zwei unterschiedliche Konzepte, die passen nicht zusammen.
So werdet ihr euch auch nie einig.

Ich frage mich viel mehr: warum sollte man überhaupt einen Parameter für
den Key angeben können, wenn der Compiler für die Map ohnehin jedes
Objekt als Key akzeptiert? Rein intuitiv würde ich diese Semantik nicht
erwarten. Ebeso, wie ich bei List#contains erwartet hätte, dass man
daraus dort den Typ-Parameter mitnimmt.
Warum ich das erwarte?

Nunja, ich möchte es mal analog zu dem Mülltonnen-Beispiel formulieren:

Man kann ganz natürlich die Frage
Garage<Object>#contains(Fahrrad)
stellen, die ist berechtigt.

Oder es würde sich die Frage:
Garage<ThingsSmallerThanCars>#contains(Fahrrad)
stellen, wobei der Parameter ein Tagging Interface vom Fahrrad sein
könnte (kein schönes Design, ich weiß; aber möglich ist es). Auch das
wäre berechtigt.

Es würde sich auch die Frage:
Garage<Dimensioned>#contains(Farrad)
mit:
interface Dimensioned{
Dimension3D getDimensions();
}
und:
Fahrrad implements Dimensioned
berechtigt stellen lassen.

Aber die Frage
Garage<Car>#contains(FullSizedSkyscraperFromManhattan)
ist von vorn herein unsinnig, denn ein Wolkenkratzer passt nicht in eine
Autogarage.

Ich fand das Mülltonnenbeispiel ja ganz amüsant, was hier gebracht
wurde, aber es ist IMO fehlerhaft:

Du schriebst:

| Aber nur weil:
| Tonne<Müll> mülltonne = ....
| DiamantRing schmuckstück = ....
| wobei gilt
| schmuckstück instanceof Müll == false
| heißt es noch lange nicht, dass die Frage
| mülltonne.contains(schmuckstück)
| keinen Sinn macht.

Und ich denke analog zu dem oben geschriebenen, dass die Frage in der
Tat völlig unsinnig ist, denn bei:
Tonne<Müll>#add(Schmuckstück)
Sollte es schon vom Compiler eine Fehlermeldung geben.
Ergo ist die Frage:
Tonne<Müll>#contains(Schmuckstück)
unsinnig, denn:
!Tonne<Müll>#contains(Schmuckstück)
wäre ja per Definition eine Tautologie**.

Wenn ich also die Einschränkung:
Tonne<Müll> mache
dann kann ich auch getrost damit leben, dass meine Frage mit:
Tonne<Müll>#contains(Müll)
entsprechend eingeschränkt wird, denn alles andere zu fragen wäre
semantisch völlig sinnlos. Begründung: siehe oben.


Nun kam auch die Begründung "Wildcard" auf:

| Map<?,String> m = new HashMap<String,String>();
| String s = m.get("special");
| und das wiederum hieße, das generische Map-Interface wäre nicht länger
| in der Lage den gleichen
| API-Contract zu erfüllen, wie der Raw-Type und damit wäre die
| Rückwärtskompatiblität gebrochen.

Dazu fiel mir folgendes ein:
Für
List<?>#add(String)
gilt doch das Gleiche und das geht per Definition auch nicht.

Zum Map-Interface selbst steht in der API:

| Some map implementations have restrictions on the keys and values they
| may contain. For example, some implementations prohibit null keys and
| values, and some have restrictions on the types of their keys.
| Attempting to insert an ineligible key or value throws an unchecked
| exception,

Der API-contract erlaubt es also scheinbar diese Einschränkung zu
machen, wird also IMO nicht verletzt.

Gruß,
-Wanja-

**dieses Wort wollte ich schon lange mal wieder benutzen, deswegen
musste ich einfach ein ! davor schreiben. *SCNR* :-)
--
"Gewisse Schriftsteller sagen von ihren Werken immer: 'Mein Buch, mein
Kommentar, meine Geschichte'. [..] Es wäre besser, wenn sie sagten:
'unser Buch, unser Kommentar, unsere Geschichte'; wenn man bedenkt, dass
das Gute darin mehr von anderen ist als von ihnen." [Blaise Pascal]
Ralf Ullrich
2005-11-25 08:15:59 UTC
Permalink
Post by Wanja Gayk
Du willst eine parametrisierte Map, in die du jeden Key reinstecken
kannst, ohne dass der Compiler meckert und dass null raus bekomst, wenn
der Typ nicht passt.
Nicht reinstecken, aber abfragen!
put() mecktert falsche Keys an, denn sie würden Type-Safety verletzen. Aber get() braucht sie nicht
anzumeckern, weil kein Run-Time-Error entstehen kann, wenn man auf einen nicht-existierenden Key
abfragt, sondern es dafür ein definiertes Verhalten gibt.
Post by Wanja Gayk
Die andere Fraktion erwartet aber, dass eine parametrisierte Map, die
auf einen bestimmten Typen als Key festgelegt ist, auch nur diesen
akzeptiert, bzw. dass der Compiler im Falle einer Verletzung meckert.
Ja, die andere Fraktion kann dies auch gerne (ebenso wie ich) bei ihren eigenen Klassen fordern,
aber für die allg. Collection API ist nun mal seit Java 1.2 ein "tolerantes" Verhalten definiert.
Auch bisher traten nur dann Run-Time-Fehler auf, wenn man versucht hat "falsche" Keys/Values in eine
Collection zu legen. Nur traten sie vor Generics erst beim auslesen als ClassCastException auf.
Generics helfen nun diese CCEs zur Compile-Time als Type-Safety Warnungen und Fehler früher zu
entdecken, nämlich schon beim Versuch sie in die Collection zu legen.

Ich bin nun mal der Meinung, dass die Generics für so "alte" Klassen das bisherige Verhalten nur
dort einschränken dürfen, wo auch bisher Runtime-Fehler aufgetreten wären.


Letzten Endes geht es darum was man als "korrekten Code" ansieht. Ihr seid der Meinung, dass nur
formal korrekter Code auch korrekter Code ist. Ich dagegen bin zufrieden wenn mein Programm das
richtige Ergebnis ohne Fehler bringt. Das ist für mich korrekter Code und daher werde ich Generics
auch stets so einsetzen, dass ich nur das einschränke, was definitiv schadet. Nimm nochmal dieses
Beispiel von mir:

public static boolean isCompleteEnumMapping(
Class<? extends Enum> clz,
Map<?, ?> map) {

Warum betrachte ich es als Fehler, dass ich hier zunächst Map<? extends Enum, ?> stehen hatte?

Nun für die korrekte Funktion dieser Methode ist es eben nicht zwingend nötig vorauszusetzen, dass
die Map nur Enums als Keys enthält.

Wie würde nun die Signatur für putDefaultEnumMapping(Class enumClz, Map map) aussehen müssen?

Klar ist, enumClz muss eine Enum-Klasse sein, also Class<? extends Enum>. Die Map soll verändert
werden, also muss ich diesmal voraussetzen, dass die Map die Werte aufnehmen kann. Nehmen wir mal
als Default-Mapping enum -> enum.toString() an, dann haben wir schließlich:

<E extends Enum> void putDefaultEnumMapping(
Class<E> enumClz,
Map<? super E,? super String> map)


Anders ausgedrückt: Die Parametrisierung sollte nur das ausschließen, was zu CCE Run-Time-Fehlern
führen kann und alles andere erlauben.

mülltonne.contains(schmuckstück)

wird eben NIEMALS einen CCE Run-Time-Error werfen, und sollte daher auch nicht in diese Richtung
durch Generics eingeschränkt werden.
Post by Wanja Gayk
List<?>#add(String)
add != get
Post by Wanja Gayk
| Some map implementations have restrictions on the keys and values they
| may contain. For example, some implementations prohibit null keys and
| values, and some have restrictions on the types of their keys.
| Attempting to _insert_ an ineligible key or value throws an unchecked
| exception,
insert != contains-check


cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Dirk Försterling
2005-11-25 08:33:48 UTC
Permalink
Post by Wanja Gayk
Aber die Frage
Garage<Car>#contains(FullSizedSkyscraperFromManhattan)
ist von vorn herein unsinnig, denn ein Wolkenkratzer passt nicht in eine
Autogarage.
Du meinst hier also, daß FullSizedSkyscraperFromManhattan selbst von
Car abgeleitet sein müßte bzw. Car implementiert, damit es legitim ist?

Meinetwegen kannst Du das so haben. Ich will es nicht.
Das Killerbeispiel - das mir einleuchtet, auch wenn ich nicht gleich
dran gedacht habe - steht ja schon in <dm22i7$b79$***@online.de>.
Das ist Dir nur nicht praxisnah genug.
Post by Wanja Gayk
Ich fand das Mülltonnenbeispiel ja ganz amüsant, was hier gebracht
| Tonne<Müll> mülltonne = ....
| DiamantRing schmuckstück = ....
| wobei gilt
| schmuckstück instanceof Müll == false
| heißt es noch lange nicht, dass die Frage
| mülltonne.contains(schmuckstück)
| keinen Sinn macht.
Wie wäre es alternativ mit:

Tonne<Müll> mülltonne = ...
Kiste<Kram> kiste = ...
Garage<ThingsSmallerThanCars> garage = ...
Kästchen<Schmuck> geschmeide = ...

// z.B. aus Datei oder vom Netz, oder durch
// Benutzer-Interaktion entstanden
"UnbekannterTyp" gegenstand = ... // Object

Wenn ich wissen will in welchem Container gegenstand drin
ist, dann hat die Frage nach contains(gegenstand) bei allen
Containern einen Sinn, auch wenn gegenstand ein DiamantRing
oder FullSizedSkyscraperFromManhattan ist.

Möchtest Du das lieber mit Typecasts oder instanceof lösen?
Post by Wanja Gayk
Tonne<Müll> mache
Tonne<Müll>#contains(Müll)
entsprechend eingeschränkt wird, denn alles andere zu fragen wäre
semantisch völlig sinnlos. Begründung: siehe oben.
Nach allem anderen zu fragen ist im Allgemeinen nicht semantisch
sinnlos, sondern wird nur garantiert negativ beantwortet.
Im speziellen, also wenn man die Beispiele wörtlich nimmt, dann
erscheint Dir das genauso wie mir und allen anderen hier in der
Tat als sinnlos.

Aber um solche Situationen geht es doch gar nicht.

Beispiele hat es dazu ja schon genug gegeben, doch die scheinen
nur wenigen verständlich zu sein.
---8<----
Post by Wanja Gayk
List<?>#add(String)
gilt doch das Gleiche und das geht per Definition auch nicht.
add() verändert die Liste ja auch, würde also hier die Typsicherheit
verletzen, denn dann würde jemand, der aus derselben Liste mit get()
ein Objekt aus der Liste holt - sei es eine List<Integer> - plötzlich
einen String für ein Integer halten.

Bei den Nur-Lese-Methoden ist das einfach egal, da die Antwort
nach einem beliebigen Objekt in einer typisierten Liste schlimmstenfalls
negativ beantwortet wird, jedoch nie zu einer Typverwechslung führt.
Post by Wanja Gayk
| Some map implementations have restrictions on the keys and values they
| may contain. For example, some implementations prohibit null keys and
| values, and some have restrictions on the types of their keys.
| Attempting to insert an ineligible key or value throws an unchecked
| exception,
Der API-contract erlaubt es also scheinbar diese Einschränkung zu
machen, wird also IMO nicht verletzt.
Lies nochmal.
Es geht um die keys und values, die _eingefügt_ werden. Das ist meiner
Meinung nach auch gut so, denn eine einfache Nachfrage mit Nein zu beantworten
ist deutlich etwas anderes, als ein Objekt nach dem abholen aus dem Container
für etwas zu halten, was es nicht ist.

Wenn Du aus einem Container einen Wert holst wirst Du i.d.R. auch prüfen,
ob der Container mit dem Key überhaupt einen Wert geliefert hat - egal ob
die Zugriffsmethode nur bestimmte Keys akzeptiert. Woher soll der Compiler
wissen, ob das nicht so gewollt ist? (z.B. bei Operationen auf Maps, die
unabhängig vom Schlüsseltyp funktionieren)

-dirk
--
D i r k F "o r s t e r l i n g
***@zorbla.de http://r.zorbla.de/
-------------
veQDuj'oH DujlIj'e'
Paul Ebermann
2005-11-24 14:54:55 UTC
Permalink
Post by Ralf Ullrich
Post by Ingo R. Homann
Aber vielleicht übersehe ich was? Kann vielleicht jemand mal ein
Praxis-nahes Beispiel bringen?
Ja, du übersiehst etwas und das Beispiel checkForSpecialKeys war
im Prinzip bereits Praxis-nah, aber vielleicht brauchst du auch
IRHMap ist eine Variante des Map-Interfaces mit den von dir
gewünschten Definitionen für containsKey, containsValue und get.
isCompleteEnumMapping ist eine Methode, die überprüft, ob in
einer Map für jeden möglichen Wert eines Enum-Typs ein passendes
Mapping vorhanden ist. Wie du siehst, lässt sich diese Methode
mit der "herkömmlichen" Map einwandfrei implementieren. Aber
mit der IRHMap gibt es dann Probleme.
Viel Spaß beim durchdenken und experimentieren. (Wenn du es
schaffst eine funktionierende Implementation der Methode für
die unten aufgeführten Dummy-Aufrufe für IRHMap sollten sich
alle kompilieren lassen.)
[...]
Post by Ralf Ullrich
public static boolean isCompleteEnumMapping(Class<? extends Enum> clz,
IRHMap<? extends Enum, ?> map) {
for (Enum e : clz.getEnumConstants()) {
if (!map.containsKey(e)) // compile time error!!
return false;
}
return true;
}
Hmm, ich würde den Typ der Methode etwas ändern (nachdem ich
Ingos Antwort gelesen habe):

---
public static boolean <K extends Enum, E extends K>
isCompleteEnumMapping(Class<E> clz, IRHMap<K, ?> map)
{
for (E e : clz.getEnumConstants()) {
if (!map.containsKey(e)) // compile time error!!
return false;
}
return true;
}
---

Ich habe das jetzt nicht ausprobiert, aber ich denke,
das dürfte passen.

Eigentlich wollte ich <K, E extends Enum & K> nehmen,
aber nur der erste Bound darf eine Klasse oder eine
Type-Variable sein, der Rest nur Interfaces. :-(

Damit ist leider IRHMap<Object, String> (bzw.
IRHMap<Comparable, String> o.ä.) nicht erlaubt.

Hmm. Vielleicht ist folgendes besser:

---
public static boolean <E extends Enum>
isCompleteEnumMapping(Class<E> clz, IRHMap<? super E, ?> map)
{
for (E e : clz.getEnumConstants()) {
if (!map.containsKey(e)) // compile time error!!
return false;
}
return true;
}
---

Merke: Gelegentlich kommt man ohne Wildcards weiter.


Paul
--
Ich beantworte Java-Fragen per E-Mail nur gegen Bezahlung. Kostenpunkt
100 Euro pro angefangene Stunde. Bitte erwähne in der Anfrage, dass du
das akzeptierst, da ich sonst die E-Mail einfach ignoriere.
(In der Newsgroup fragen ist billiger und oft auch schneller.)
martin demberger
2005-11-28 12:51:12 UTC
Permalink
Post by Ralf Ullrich
Post by Ingo R. Homann
Aber vielleicht übersehe ich was? Kann vielleicht jemand mal ein
Praxis-nahes Beispiel bringen?
Ja, du übersiehst etwas und das Beispiel checkForSpecialKeys war im
Prinzip bereits Praxis-nah, aber vielleicht brauchst du auch einfach ein
IRHMap ist eine Variante des Map-Interfaces mit den von dir gewünschten
Definitionen für containsKey, containsValue und get.
isCompleteEnumMapping ist eine Methode, die überprüft, ob in einer Map
für jeden möglichen Wert eines Enum-Typs ein passendes Mapping vorhanden
ist. Wie du siehst, lässt sich diese Methode mit der "herkömmlichen" Map
einwandfrei implementieren. Aber mit der IRHMap gibt es dann Probleme.
Viel Spaß beim durchdenken und experimentieren. (Wenn du es schaffst
eine funktionierende Implementation der Methode für IRHMap vorzuweisen,
wäre ich sehr daran interessiert. Wichtig: die unten aufgeführten
Dummy-Aufrufe für IRHMap sollten sich alle kompilieren lassen.)
Hallo,

Warum versucht dus nicht so?

public static <K extends Enum> boolean isCompleteEnumMapping(
Class<? extends K> clz, IRHMap<? super K, ?> map) {
for (K e : clz.getEnumConstants()) {
if (!map.containsKey(e)) // compile time error!!
return false;
}
return true;
}
Der Trick dabei ist der Typ der Schleifenvariable.

Noch schöner wäre allerdings eine Lösung mit den Werten im enum. Die
Übergabe des Wertes im Konstruktor ist nur als Beispiel gedacht. Der
Wert kann natürlich auch aus der DB gelesen werden.

private enum NUMBERS {
ONE(1), TWO(2), THREE(3), FOUR(4);

private int asInt;

private NUMBERS(int asInt) {

this.asInt = asInt;
}

public int asInt() {
return asInt;
}
}

Schönen Gruß
Martin

Eike Preuss
2005-11-24 11:46:36 UTC
Permalink
Hallihallo,
Post by Ingo R. Homann
Hi,
Post by Ralf Ullrich
public static Integer SPECIAL_INTEGER = new Integer(1);
public static String SPECIAL_STRING = "eins";
boolean checkForSpecialKeys(Map m) {
return m.containsKey(SPECIAL_STRING) || m.containsKey(SPECIAL_INTEGER);
}
Kriegst du das mit Map<Object,Special> hin?
boolean checkForSpecialKeys(Map<Object,ObjectOderWhatever> m) {
return m.containsKey(SPECIAL_STRING) || m.containsKey(SPECIAL_INTEGER);
}
Da ich die Methode cfsk(Map m) auch mit einer Map<String, Irgendwas>
aufrufen kann, die Methode cfsk(Map<Object,ObjectOderWhatever> m) aber
nicht, kann man wohl nicht sagen, dass beide Methoden dasselbe realisieren.

Der Grund weshalb ich cfsk(Map<Object,ObjectOderWhatever> m) nicht
mit Map<String, Irgendwas>
aufrufen kann:
Wenn ich es könnte, und angenommen, m.containsKey(...) würde den
Parameter speichern (und es gäbe z.B. eine Methode
K m.getLastCheckedKey(), die den gespeicherten Wert zurückliefert), dann
würde der Aufruf meine Map<String, Irgendwas> in einen inkonsistenten
Zustand bringen, weil getLastCheckedKey() ja einen Integer zurückliefern
müsste -> runtime error.

Das heißt, der Compiler müsste zu Compilezeit garantieren können, dass
m.containsKey(...) das Objekt m nicht verändert. Damit wären wir bei
'const' Methoden, also doch wieder bei C++ (und hat C# das auch?)
angelangt und V containsKey(K aKey) const;

Da ist es mir lieber, dass containsKey ein Object entgegen nimmt, statt
diese IMO hirnrissige Komplexität und deren Implikationen auch noch
einzuführen!!!

++ Eike
Ingo R. Homann
2005-11-23 16:13:57 UTC
Permalink
Hi Ralf,

nebenbei...
...hoffentlich allseits bekannten 70kB PDF Datei generics-tutorial.pdf
beschrieben sind (siehe S. 5). (Stand 16:00 Uhr MEZ)
...was spielen eigentlich die Uhrzeit und die kB für eine Rolle?

Ciao,
Ingo
Ralf Ullrich
2005-11-23 16:31:19 UTC
Permalink
Post by Ingo R. Homann
Hi Ralf,
nebenbei...
...hoffentlich allseits bekannten 70kB PDF Datei generics-tutorial.pdf
beschrieben sind (siehe S. 5). (Stand 16:00 Uhr MEZ)
...was spielen eigentlich die Uhrzeit und die kB für eine Rolle?
Ciao,
Ingo
Die Uhrzeit war auf die Aussage "bisherige Antworten" bezogen, und die 70kB waren erwähnt, damit
jeder, der sich diesen Thread hier anschaut und die Antwort auf die Frage nicht von Anfang an
korrekt gewußt hat, daran erinnert fühlt, dass er/sie sich dies _läppischen_ 70kB nochmal ganz
gründlich durcharbeiten sollte, bevor er/sie das nächste Mal generischen Code schreibt.

cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Ingo R. Homann
2005-11-24 08:09:54 UTC
Permalink
Hi,
Post by Ralf Ullrich
Post by Ingo R. Homann
...was spielen eigentlich die Uhrzeit und die kB für eine Rolle?
Die Uhrzeit war auf die Aussage "bisherige Antworten" bezogen,
Achso.
Post by Ralf Ullrich
und die
70kB waren erwähnt, damit jeder, der sich diesen Thread hier anschaut
und die Antwort auf die Frage nicht von Anfang an korrekt gewußt hat,
daran erinnert fühlt, dass er/sie sich dies _läppischen_ 70kB nochmal
ganz gründlich durcharbeiten sollte...
Hmm, 70kb plain-text, das sind schätze ich mal, 30 Seiten. Abzüglich des
Overheads, den PDF hat, passen da immer noch - ich schätze mal ganz grob
- 23 Seiten rein. ;-) Wenn ich bedenke, wie sehr Leute in Newsgroups
teilweise mit Links um sich werfen (5 Links a 10 Seiten Text, in denen
die Antwort auf eine einfache Frage irgendwo im 4. Dokument auf Seite 9
Zeile 42 zu finden ist...), ist das ne ganze Menge.

OK, angesichts der Tatsache, dass der Link von dir kommt, und dass er
auf sun-Seiten verweist, ist die Wahrscheinlichkeit, dass er lesenswert
ist, tatsächlich ziemlich hoch! :-)

Ciao,
Ingo
Timo Stamm
2005-11-24 11:15:02 UTC
Permalink
Post by Ingo R. Homann
Wenn ich bedenke, wie sehr Leute in Newsgroups
teilweise mit Links um sich werfen (5 Links a 10 Seiten Text, in denen
die Antwort auf eine einfache Frage irgendwo im 4. Dokument auf Seite 9
Zeile 42 zu finden ist...), ist das ne ganze Menge.
OK, angesichts der Tatsache, dass der Link von dir kommt, und dass er
auf sun-Seiten verweist, ist die Wahrscheinlichkeit, dass er lesenswert
ist, tatsächlich ziemlich hoch! :-)
Ist tatsächlich ein gutes Tutorial, liegt bei mir schon seit Mitte des
Jahres auf der Platte, als ich auf 1.5 umgestiegen bin.

Allerdings ist es illusorisch, sich vor dem ersten Einsatz von Generics
mal eben die 23 Seiten durchzulesen. Davon bleibt doch zwangsweise nur
ein Teil hängen, wenn man sich nicht die Zeit nimmt das Ganze parallel
dazu in die Praxis umzusetzen.

Ich weiss nicht wie es bei euch aussieht, aber ich habe nicht die Zeit
so etwas bis ins Detail durchzuarbeiten wenn es nicht konkret für ein
Projekt gefragt ist. Ich beneide jeden der die Möglichkeit dazu hat.


Timo
Ingo R. Homann
2005-11-24 12:25:24 UTC
Permalink
Hi,
Post by Timo Stamm
Ist tatsächlich ein gutes Tutorial,
ACK,
Post by Timo Stamm
Allerdings ist es illusorisch, sich vor dem ersten Einsatz von Generics
mal eben die 23 Seiten durchzulesen. Davon bleibt doch zwangsweise nur
ein Teil hängen, wenn man sich nicht die Zeit nimmt das Ganze parallel
dazu in die Praxis umzusetzen.
ACK!

IMHO ist es immer eine Mischung aus mehreren Faktoren: Tutorial, Einsatz
in der Praxis, Erläuterung und Diskussion mit anderen (z.B. kompetenten
und geduldigen Newsgroup-Teilnehmern :-)

Allein aus der grauen Theorie des Tutorials heraus wird einem die
komplette Tragweite einiger Aspekte nicht bewusst.

In diesem Sinne werde ich mal über Ralfs Aufgabenstellung aus seinem
Posting von 12:01 Uhr sinnieren. ;-)

Ciao,
Ingo
Timo Stamm
2005-11-23 17:45:16 UTC
Permalink
Post by Ralf Ullrich
"generified"
Können wir nicht "parameterized", bzw. "parametrisiert" benutzen? Klingt
besser und wird auch von Sun genutzt.

Ansonsten guter Einwurf, an die Wildcards habe ich auch nicht gedacht.


Timo
Ralf Ullrich
2005-11-23 20:06:14 UTC
Permalink
Post by Timo Stamm
Post by Ralf Ullrich
"generified"
Können wir nicht "parameterized", bzw. "parametrisiert" benutzen? Klingt
besser und wird auch von Sun genutzt.
Gerne, aber ich bin mir noch nicht sicher welche "Sprachregelung" sich für Generics im Deutschen
etablieren wird.

Im Übrigen: IIRC wird "parametrisierter Typ" von Sun für die Unterscheidung zum "rohen Typ"
verwendet, während ja beides immer noch den "generischen Typ" (aber eben in unterschiedlicher
Ausprägung, nämlich bestimmt und unbestimmt) darstellt.


cu
--
We can live without religion and meditation,
but we cannot survive without human affection.
Dalai Lama
Michael Matzl
2005-11-23 19:09:24 UTC
Permalink
Post by Ralf Ullrich
Hi Michael,
Kompliment zu deiner (nur scheinbar) simplen Frage, denn alle
bisherigen Antworten darauf waren ziemlich falsch, obwohl doch die
Gründe in der hoffentlich allseits bekannten 70kB PDF Datei
generics-tutorial.pdf beschrieben sind (siehe S. 5). (Stand 16:00 Uhr MEZ)
Hallo Ralf!

Danke für deine wirklich kompetente Antwort.

Mir ist noch eingefallen, dass folgendes auch eintreten kann:
A,B,C seien bel. Klassen.

Map<A, C> m = new HashMap<A,C>();

m.put(a,c);

Nehmen wir an, wir haben ein Objekt b der Klasse B mit: a.equals(b) = true.
Dann funktioniert nämlich m.get(b) und liefert c.

Danke auch allen anderen.

lg
michl
Lesen Sie weiter auf narkive:
Loading...