Discussion:
Generics kann scheinbar nicht extends extends
(zu alt für eine Antwort)
m***@heinerkuecker.de
2013-01-16 16:30:45 UTC
Permalink
Hallo Javaner,

manchmal stellen mich die Generics vor Rätsel,
ich bin wahrscheinlich zu einfach gestrickt dafür.

Folgendes Beispiel:

import java.util.ArrayList;
import java.util.List;

public final class GenericsProblem
{
static class A {
}

static class AX
extends A {
}

public static void main( String[] args )
{
final List<A> aList = new ArrayList<A>();

final List<AX> axList = new ArrayList<AX>();

aList.addAll( axList );

final List<? extends A> aWildcardExtendsList = new ArrayList<A>();

//compiler error: The method addAll(Collection<? extends capture#1-of ? extends GenericsProblem.A>) in the type List<capture#1-of ? extends GenericsProblem.A> is not applicable for the arguments (List<GenericsProblem.AX>)
aWildcardExtendsList.addAll( axList );


final List<? super A> aWildcardSuperList = new ArrayList<A>();

aWildcardSuperList.addAll( axList );
}

}

Beim ersten addAll ist der Compiler zufrieden, obwohl ich die Liste ohne Wildcards, also invariant deklariert habe.

Also das dürfte nach meiner Meinung nicht kompilieren.

Beim zweiten addAll habe ich die Liste mit einem extends-Wildcard versehen,
also kovariant deklariert.

Da kompiliert es nicht, der angegebene Fehler taucht auf.

Eventuell liegt es ja an der addAll-Methode von List, denn diese setzt noch einen Wildcard drauf

public interface List<E> extends Collection<E> {

boolean addAll(Collection<? extends E> c);


Die dritte addAll-Variante ist wiederum erlaubt.

Könnt Ihr mir das erklären?
Danke
Heiner
Michael Paap
2013-01-16 17:04:24 UTC
Permalink
Post by m***@heinerkuecker.de
manchmal stellen mich die Generics vor Rätsel,
ich bin wahrscheinlich zu einfach gestrickt dafür.
[...]
Post by m***@heinerkuecker.de
final List<? extends A> aWildcardExtendsList = new ArrayList<A>();
aWildcardExtendsList.addAll( axList );
Mach dir klar, was der Typ

List<? extends A>

eigentlich bedeutet: Das sind Listen, über deren Parameter du nichts
weißt, außer, dass er Subtyp von A ist. Deswegen kannst du über einen
Ausdruck, der diesen Typ hat, der Liste NICHTS hinzufügen, eben, weil
der Parametertyp beliebig speziell sein kann.

Ich muss das immer bei Studientagen erklären, deshalb habe ich ein
bisschen Beispielcode mit Erläuterungen parat... ich kopiere dir das mal
hierher. Ich denke, das sollte helfen.

Code:

------------------------------------------------------------------------
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 {}
------------------------------------------------------------------------

Erläuterung:

Dieses Beispiel zeigt, was die Deklaration einer Variablen mit einem
Wildcard-Typ für die Menge der potentiell von dieser Variablen
referenzierten Objekte aussagt, also, welche Eigenschaften der Compiler
bzgl. dieser Objekte als sicher annehmen kann und welche nicht, welche
Operationen er also demzufolge auf der Variablen zulässt:

Nur zur Sicherheit sei angemerkt, dass die Tatsache, dass ich den
Variablen liste1, liste2 und liste3 den Wert null zuweise, absolut
nichts mit den Fehlermeldungen des Compilers zu tun hat! Der Wert null
(die leere Referenz) ist gültiger Wert jeden Typs und in den jeweils auf
die Zuweisung folgenden Zeilen interessiert es den Compiler nicht im
mindesten, was die Variable tatsächlich zur Laufzeit referenzieren wird,
er kennt lediglich den Deklarationstyp und trifft seine Entscheidungen
ausschließlich aufgrund dieses Typs.

Durch die Beschränkung der Wildcard bei liste2 und liste3 wird letztlich
eingeschränkt, welche Typparameter jemand bei der Instantiierung einer
konkreten Liste verwenden darf, die einer dieser Variablen zugewiesen
werden kann. Und daraus resultiert für den Compiler ein Wissen über die
Objekte, welche in einer solchen Liste vorhanden sein können.

Der Variablen liste1 können Listen mit beliebigem Parametertyp
zugewiesen werden, d.h. dass der Compiler über diesen Typ nichts weiß.
Es könnte sich also z.B. um eine ArrayList<Tier> handeln, aber
auch um eine ArrayList<Object>. Also kann der Compiler uns nicht
erlauben, über die Variable liste1 irgendetwas in die Liste zu füllen.
Und bzgl. Eventuell aus der List zurückgegebener Objekte weiß er auch
nicht mehr, als dass sie mit Sicherheit den Typ Object haben werden,
weshalb er auch nur eine Zuweisung von aus der Liste geholten Objekten
an eine Variable dieses Typs zulässt.

Der Variablen liste2 können Listen mit allen Parametertypen zugewiesen,
die Subtyp von Tier sind (wie immer schließt das Tier selbst ein). Der
Compiler kann uns nach wie vor nicht erlauben, irgendetwas in die Liste
zu füllen, auch kein Tier, denn die tatsächlich von liste2 referenzierte
Liste könnte auch z.B. eine ArrayList<Vogel> sein, und in die kann ich
ja nicht jedes Tier packen. Über eventuell aus der Liste zurückgegebene
Objekte weiß der Compiler allerdings, dass ihr Typ mit Sicherheit Subtyp
von Tier sein wird, weswegen er eine Zuweisung von aus der Liste
geholten Objekten an die Variablen zulässt die ei-
nen Supertyp von Tier als Deklarationstyp haben.

Der Variablen liste3 schließlich können Listen mit allen Parametertypen
zugewiesen, die Supertyp von Vogel sind (wie immer schließt das Vogel
selbst ein). Der Compiler kann uns nun erlauben, Objekte aller
Subtypen von Vogel in die Liste zu füllen. Hingegen kann er über aus der
Liste zurückgegebene Objekte mit Sicherheit nichts mehr sagen, außer,
dass sie vom Typ Object sein werden: Im Extremfall könnte es
sich tatsächlich z.B. um eine ArrayList<Object> handeln. Deswegen lässt
er nur eine Zuweisung von aus der Liste geholten Objekten an Variablen
des Deklarationstyps Object zu.
------------------------------------------------------------------------

Gruß,
Michael Paap
m***@heinerkuecker.de
2013-01-16 20:56:58 UTC
Permalink
Post by Michael Paap
Durch die Beschränkung der Wildcard bei liste2 und liste3 wird letztlich
eingeschränkt, welche Typparameter jemand bei der Instantiierung einer
konkreten Liste verwenden darf, die einer dieser Variablen zugewiesen
werden kann. Und daraus resultiert für den Compiler ein Wissen über die
Objekte, welche in einer solchen Liste vorhanden sein können.
Aha, der Wildcard bezieht sich auf einen anderen Typ-Parameter, nicht auf die Elemente

List<? super Vogel> liste3 = new ArrayList<Tier>();
liste3.add(new Object()); // das kompiliert nicht
liste3.add(new Tier()); // das kompiliert nicht
liste3.add(new Vogel()); // das kompiliert, ist klar
liste3.add(new Amsel()); // das kompiliert, voll strange, aber verständlich, wenn man weiss, dass sich das Wildcard nicht auf das Element bezieht, sondern auf einen anderen Typ-Parameter
Post by Michael Paap
Gruß,
Michael Paap
Danke
Heiner
Michael Paap
2013-01-16 21:29:41 UTC
Permalink
Post by m***@heinerkuecker.de
Aha, der Wildcard bezieht sich auf einen anderen Typ-Parameter, nicht auf die Elemente
Exakt. Vielleicht noch ein par Worte zum Hintergrund der Wildcards:

Als Javas Typsystem entworfen wurde - da war von Generics noch lange
keine Rede - stand man vor der Frage, ob sich eine bestehende
Subtypbezioehung zwischen zwei Typen auf die entsprechenden Arraytypen
übertragen soll, ob also aus

Vogel SUB--> Tier

folgen soll:

Vogel[] SUB--> Tier[]

Auf den ersten Blick erscheint das sinnvoll, denn ein Array, dessen
Elemente Vögel sind, IST ja ein Array, dessen Elemente Tiere sind. Und
so hat man es auch gemacht. Damit hat man sich allerdings ein hübsches
Loch in der statischen Typprüfung eingehandelt, denn...

Vogel[] vögel = new Vogel[42];
Tier[] tiere = vögel;
tiere[3] = new Elefant();

compiliert einwandfrei, fliegt einem aber zur Laufzeit mit einer
ArrayStoreException um die Ohren.

Als man bei der Einführung generischer Typen vor der analogen Frage
stand, nämlich der, ob aus

Vogel SUB--> Tier

folgen soll:

List<Vogel> SUB--> List<Tier>

hat man sich diesmal /dagegen/ entschieden. Damit vermeidet man das zur
ArrayStoreException analoge Problem, das man sonst gehabt hätte.

Allerdings schränkt diese Entscheidung die Möglichkeiten des
Programmierers massiv ein. Z.B. hätte man nun keine Möglichkeit mehr,
einen Typ wie "Liste aller Listen, deren Typparameter Subtyp von Tier
ist" überhaupt auszudrücken. Und deswegen gibt es die Wildcards. Denn

List<? extends Tier>

leistet eben dies. Und

List<?>

ist Supertyp aller Listen mit beliebigem Typparameter.

Damit das funktioniert musste man allerdings die von mir im Beispielcode
beschriebenen Einschränkungen treffen. Dennoch ist das Zeug nützlich,
denn die betreffenden Listen kann man ja über einen Alias, der einen Typ
/ohne/ Wildcard hat, durchaus befüllen bzw. auslesen. Und man kann mit
Hilfe der Wildcards zum Beispiel in typsicherer Weise solche netten
Scherzchen bauen wie:

--------------------------------------------------------------------------
import java.util.List;

/**
* An abstract collection with import/export methods.
*
* @param <T>
* the type parameter of this list
*/
public abstract class ImportExportListLoesung<T> implements List<T> {

/**
* Imports all elements of a source list into this list.
*
* @param sourceList
* the list whose elements are imported
*/
void importFrom(List<? extends T> sourceList) {
for (T elem : sourceList) {
this.add(elem);
}
}

/**
* Exports all element of this list to a target list.
*
* @param targetList
* the list to which the elements are exported
*/
void exportTo(List<? super T> targetList) {
for (T elem : this) {
targetList.add(elem);
}
}
}
--------------------------------------------------------------------------

Gruß,
Michael

Patrick Roemer
2013-01-16 17:22:01 UTC
Permalink
Responding to ***@heinerkuecker.de:

(Ich habe mal etwas umformatiert...)
static class A {}
static class AX extends A {}
[...]
final List<A> aList = new ArrayList<A>();
final List<AX> axList = new ArrayList<AX>();
aList.addAll( axList );
Beim ersten addAll ist der Compiler zufrieden, obwohl ich die Liste
ohne Wildcards, also invariant deklariert habe.
#addAll() erwartet Collection<? extends A>, und das trifft auf List<AX> zu.
Beim zweiten addAll habe ich die Liste mit einem extends-Wildcard versehen,
also kovariant deklariert.
Da kompiliert es nicht, der angegebene Fehler taucht auf.
final List<? extends A> aWildcardExtendsList = new ArrayList<A>();
//compiler error: The method addAll(Collection<? extends
//capture#1-of ? extends GenericsProblem.A>) in the type
//List<capture#1-of ? extends GenericsProblem.A> is not applicable for
//the arguments (List<GenericsProblem.AX>)
aWildcardExtendsList.addAll( axList );
#addAll() erwartet hier eine Collection<?2 extends ?1 extends A>. D.h.
es gibt einen(!) bestimmten(!) nicht naeher spezifizierten Typ ?1
extends A, und es wird derselbe Typ ?1 oder ein (wiederum nicht genauer
spezifizierter) Subtyp ?2 extends ?1 erwartet. Es kann aber nicht
bewiesen werden, dass AX extends ?1.

Wenn das gueltig waere, ginge ja auch sowas:

static class AY extends A {}
final List<AY> ayList = ...
final List<? extends A> anotherWildcardExtendsList = ayList;
anotherWildcardExtendsList.addAll(axList);
Die dritte addAll-Variante ist wiederum erlaubt.
final List<? super A> aWildcardSuperList = new ArrayList<A>();
aWildcardSuperList.addAll( axList );
Hier wird ein(!) bestimmter(!) Supertyp von A verlangt - und fuer jeden
solchen muss gelten, dass AX zu dessen Subtypen zaehlt.

Viele Gruesse,
Patrick
m***@heinerkuecker.de
2013-01-16 21:24:02 UTC
Permalink
Post by Patrick Roemer
#addAll() erwartet hier eine Collection<?2 extends ?1 extends A>. D.h.
es gibt einen(!) bestimmten(!) nicht naeher spezifizierten Typ ?1
extends A, und es wird derselbe Typ ?1 oder ein (wiederum nicht genauer
spezifizierter) Subtyp ?2 extends ?1 erwartet. Es kann aber nicht
bewiesen werden, dass AX extends ?1.
Aha, der notierte ist also ein neuer, ganz eigener Typ, der nicht einmal kompatibel zu einem anderen, genauso notierten Typ ist.
Post by Patrick Roemer
static class AY extends A {}
final List<AY> ayList = ...
final List<? extends A> anotherWildcardExtendsList = ayList;
anotherWildcardExtendsList.addAll(axList);
Aha, es geht also einmal die Referenz, die auch an anderer Code-Stelle schreibend benutzt werden könnte

final List<? extends String> strList = new ArrayList<String>();

(jemand schreibt eventuell später auf strList)

und im anderen Fall um die Elemente, welche nur für diese eine Operation den Typ erfüllen müssen

final List<? extends String> strList = new ArrayList<String>();

//strListRef: Liste von Strings, welcher eine Referenz zugewiesen wird
// und die demzufolge den Typ(mit Parameter) einzuhalten hat
final List<String> strListRef = strList; // kompiliert nicht


//strListAddee: Liste von Strings zu welcher Elemente addiert werden
List<String> strListAddee = null;
strListAddee.addAll( strList ); // kompiliert
Post by Patrick Roemer
Viele Gruesse,
Patrick
Danke
Heiner
Florian Weimer
2013-01-16 17:35:13 UTC
Permalink
Post by m***@heinerkuecker.de
final List<A> aList = new ArrayList<A>();
final List<AX> axList = new ArrayList<AX>();
aList.addAll( axList );
final List<? extends A> aWildcardExtendsList = new ArrayList<A>();
//compiler error: The method addAll(Collection<? extends capture#1-of ? extends GenericsProblem.A>) in the type List<capture#1-of ? extends GenericsProblem.A> is not applicable for the arguments (List<GenericsProblem.AX>)
aWildcardExtendsList.addAll( axList );
OpenJDK 7 liefert übrigens bessere Fehlermeldungen für solche Fälle:

GenericsProblem.java:24: error: no suitable method found for addAll(List<AX>)
aWildcardExtendsList.addAll( axList );
^
method List.addAll(int,Collection<? extends CAP#1>) is not applicable
(actual and formal argument lists differ in length)
method List.addAll(Collection<? extends CAP#1>) is not applicable
(actual argument List<AX> cannot be converted to Collection<? extends CAP#1> by method invocation conversion)
where CAP#1 is a fresh type-variable:
CAP#1 extends A from capture of ? extends A
Lesen Sie weiter auf narkive:
Suchergebnisse für 'Generics kann scheinbar nicht extends extends' (Newsgroups und Mailinglisten)
58
Antworten
[DISCUSSION] Safe queries - Sichere Datenbankabfragen in Java
gestartet 2005-03-27 15:31:17 UTC
de.comp.lang.java
8
Antworten
ClassLoader Problem (knifflig?)
gestartet 2004-07-14 18:33:49 UTC
de.comp.lang.java
40
Antworten
Konzept von polymorphen Datentypen missverstanden?
gestartet 2004-06-18 07:03:55 UTC
de.comp.lang.java
92
Antworten
philosophisch? jede klasse ein objekt?
gestartet 2005-03-12 22:03:20 UTC
de.comp.lang.java
30
Antworten
[VM] Annotations und 1.4
gestartet 2005-09-17 19:57:37 UTC
de.comp.lang.java
Suchergebnisse für 'Generics kann scheinbar nicht extends extends' (Fragen und Antworten)
5
Antworten
Wie kann ich die Steckdosen nach der Installation eines Backsplash erweitern?
gestartet 2013-05-30 05:18:42 UTC
heimwerken
6
Antworten
Ist es wichtig, dass die Canon 1DX nicht für den Autofokus mit Objektiven ausgelegt ist, die langsamer als 1: 5,6 sind?
gestartet 2011-11-05 21:03:08 UTC
foto
3
Antworten
Ist es eine gute Idee, ein 70-200 mm Canon-Objektiv durch ein 135 mm und einen Extender zu ersetzen?
gestartet 2012-05-08 19:29:25 UTC
foto
2
Antworten
Was sind die Unterschiede zwischen den Canon Extendern?
gestartet 2012-09-25 00:18:04 UTC
foto
4
Antworten
Warum extrudiert mein 3D-Drucker nicht, wenn ich die Durchflussrate sehr niedrig eingestellt habe?
gestartet 2017-12-19 01:54:47 UTC
3d drucken
Nicht verwandte, aber interessante Themen
5
Antworten
Klassische Gitarrenstücke auf einer Steel-String-Gitarre spielen
gestartet 2013-10-08 03:03:49 UTC
5
Antworten
Wie weit weg kann ein Klavier unisono sein und immer noch als „gestimmt“ betrachtet werden?
gestartet 2011-06-01 22:06:48 UTC
5
Antworten
Verwenden Sie weiße Punkte (Markierungen) auf dem Griffbrett, während Sie sich Notizen merken
gestartet 2011-11-30 22:47:18 UTC
5
Antworten
Wie lange hältst du eine Fermate?
gestartet 2015-08-23 22:07:32 UTC
6
Antworten
Was sind die praktischen Gründe, um immer noch transponierende Instrumente zu haben?
gestartet 2012-09-18 12:03:39 UTC
Loading...