Generischer Rückgabewert

Ich habe folgendes Interface über das ich Manager verwalte, die jeder für sich eine bestimme Entität (bspw. User) verwalten.

  <E> Manager<E> getManagerOf(Class<E> type); //Generischer Zugriff auf Manager
  UserManager getUserManager();//Konkreter Zugriff auf UserManager
}

Und folgende Klassen:

  //... 
}

public class ManagementImpl implements Management{//Implementierung Management
  
  private Map<Class<?>,Manager<?>> managers = new HashMap<>();

  public ManagementImpl(){
    managers.put(User.class, new UserManager());
  }

  @Override
  public <E> Manager<E> getManagerOf(Class<E> type){
    return (Manager<E>) managers.get(type);
  }

  @Override
  public UserManager getUserManager(){
    return (UserManager) getManagerOf(User.class);
  }
}```

Nun würde ich die Methode `getManagerOf(Class<E> type)` gerne so schreiben, dass ich mir den cast in `getUserManager` sparen kann. Mein Ansatz:
```public <E, M extends Manager<E>> M getManagerOf(Class<E> type){
  return (M) managers.get(type);
}```Allerdings erkennt der Compiler das nicht als gültige Implementierung von `<E> Manager<E> getManagerOf(Class<E> type)` an (Es kommt die Fehlermeldung, dass ich getManagerOf noch implementieren muss).

Warum funktioniert das nicht?

Das würde genauso funktionieren, hast du dein Interface entsprechend der neuen Signatur angepasst?

public interface Management
{
	<E, M extends Manager<E>> M getManagerOf(Class<E> type); // Generischer Zugriff auf Manager

	UserManager getUserManager();// Konkreter Zugriff auf UserManager
}
public class ManagementImpl implements Management // Implementierung Management
{

	private Map<Class<?>, Manager<?>> managers = new HashMap<>();

	public ManagementImpl()
	{
		managers.put(User.class, new UserManager());
	}

	@Override
	public <E, M extends Manager<E>> M getManagerOf(Class<E> type)
	{
		return (M) managers.get(type);
	}

	@Override
	public UserManager getUserManager()
	{
		return getManagerOf(User.class);
	}
}

EDIT:
Wobei ich sagen muss, dass der Cast in der ManagementImpl nicht schlimm ist (solange du das einfügen in die Map durch geeignete Methoden kapselst). Die Lösung würde ich vielleicht sogar bevorzugen, da dadurch der “Genericwust” etwas aufgelöst wird.

[QUOTE=EikeB]Wobei ich sagen muss, dass der Cast in der ManagementImpl nicht schlimm ist (solange du das einfügen in die Map durch geeignete Methoden kapselst). Die Lösung würde ich vielleicht sogar bevorzugen, da dadurch der “Genericwust” etwas aufgelöst wird.[/QUOTE]Hm… auch ein Argument. Allerings könnte ich mir sonst sowohl im Interface als auch in der Implementierung alle expliziten getter sparen, da ich dann gleich UserManager um = management.getManagerOf(User.class) schreiben könnte.

Interessantes Phänomen bei <E, M extends Manager<E>> M getManagerOf(Class<E> type):

final Date d = management.getManagerOf(Date.class);```Das kompiliert! :eek: Kann mir das wer erklären!? Hätte erwartet, dass er mir nen Fehler wirft, da Date kein Manager<?> ist.

Vielleicht bleibe ich dann doch lieber bei `<E> Manager<E> getManagerOf(Class<E> type)` :D

bei mir Fehler…

Bound mismatch: The generic method getManagerOf(Class) of type Management2 is not applicable for the arguments (Class).
The inferred type Date&Manager2 is not a valid substitute for the bounded parameter <M extends Manager2>
Test2.java /Test/src/test line 15 Java Problem

hier vollständiges Testprogramm zum Kopieren

public class Test2{
    public static void main(String[] args)
    {
        Management2 management = null;
        final Date d = management.getManagerOf(Date.class);
    }
}


interface Management2 {
    <E, M extends Manager2<E>>M getManagerOf(Class<E> type);
}


class Management2Impl     implements Management2 {
    private Map<Class<?>, Manager2<?>> managers = new HashMap<Class<?>, Manager2<?>>();

    public <E, M extends Manager2<E>>M getManagerOf(Class<E> type)
    {
        return (M)managers.get(type);
    }
}


class Manager2<T> { }

Machst du in deinem Beispiel Manager2 zu einem Interface kompiliert auch dein Code. Ich weiß allerdings nicht warum.

daran hatte ich auch kurz gedacht, aber Ausprobieren eingespart,
mal wieder etwas was mich an Generics überrascht, dazu fällt mir bislang nix ein,
außer einfacheres Beispiel:

public class Test2 {
    public static void main(String[] args)  {
        Date d = getListOf(); // kompiliert, später ClassCastException
        System.out.println("d: " + d);
    }

    public static <M extends List>M getListOf()  {
        return (M)new ArrayList();
    }
}

mit extends ArrayList funktionierend, extends List ohne Wirkung…

List ist ein interface. ArrayList nicht. Mehr fällt mir dazu auch nicht ein. Aber ich hätte bei public static <M extends List>M getListOf() schon erwartet, dass das nach dem Type-Erasure so aussieht: List getListOf(). Sehr mysteriös…

Würd ich sagen, dass das ein Bug ist. Sollte vl jemand mal bei Oracle melden

ich habe gestern recht viele Links angeschaut, Suchbegriffe wie return type, WildCard, Bounds,
Generics erklären viele, aber ich habe noch keine Seite gefunden die exakt diesen Fall betrachtet,

vielleicht wirklich noch unbekannt, so unwahrscheinlich das auch klingt :wink:

Habs nochmal in Java 8 getestet (Eclipse Kepler):

Date d = getListOf();//Ausgangscode

Kompiliert bei dieser Methoden-Signatur:

        return (M) new ArrayList<E>();
    }```

Bei dieser jedoch nicht (wie erwartet: cannot convert from List<Object> to Date)
```public static <E, M extends List<E>> M getListOf() {
        return (M) new ArrayList<E>();
    }```

Also Eclipse zeigt mir hier (Java7) bei beiden nur eine Warnung.

Allgemein kann man natürlich sagen: Casten kann ALLES kaputtmachen.

Aber in diesem konkreten Fall ist zumindest (einigermaßen) klar, warum es mit “List” funktioniert, aber mit “ArrayList” nicht: Wenn man “List” angibt, wird der Typ zu Date & List inferiert. Und so einen Typen kann es ja geben: class SpecialDate extends Date implements List. Wenn man “ArrayList” angibt, wäre der inferierte Typ Date & ArrayList, aber class SpecialDate extends Date, ArrayList geht eben nicht (allgemein darf eine Typvariable nur upper bounds haben, und nur eine Klasse als upper bound - siehe http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4 ).

Für eine Erklärung des Verhaltens abhängig vom Wildcard (und warum das jetzt bei Java 8 anders zu sein scheint) müßte man wohl genauer suchen.

innerhalb der Methode ist ja klar, auch beim Aufruf erkannt?,
List l = getListOf(); durchgelassen aber vor String s = getListOf(); (anders/ deutlicher) gewarnt?
wobei Eclipse-Warning immer noch Zusatz zu Compiler wäre

letzteres ürigens eine finale Klasse, nichts mit Erweiterung, aber soweit kann natürlich nicht dynamisch geprüft werden

hilfreich ist so ein Verhalten sowieso nicht, es muss hier so naheliegend wie bei Generics eben üblich mit einer ClassCastException zur Laufzeit gerechnet werden,
vom Compiler sehenden Auges hingenommen

bei

        List<? extends List> list = new ArrayList();
        Date d = list.get(4);

gibts ‚cannot convert from capture#1-of ? extends List to Date‘ statt stiller Hoffnung auf SpecialDate

Beim letzten steht auch kein cast dabei. Mit

List<? extends List> list = new ArrayList();
Date d = (SpecialDate)list.get(4);

würde das auch gehen. Es geht ja um die Frage, was bekannt sein kann. Aber Typinferenz ist eben so eine Sache, komplizierter als man denkt (AUCH wenn man diese Tatsache berücksichtigt…) :wink:

ganz schön weit hergeholt, anderseits funktioniert

         ArrayList d = list.get(4);

genausowenig wie mit Date, also eh kein gutes Vergleichsbeispiel,
dieses Konstrukt versteift sich auf List oder was immer als Basis angegeben ist, die Methode getListOf() nicht