Unterschied mit und ohne Wildcard, IntelliJ

#1

Eclipse hat mich jahrelang in meiner Anfangszeit begleitet und mich immer dazu animiert bei jeder Klasse mit generischen Typen, den Typen auch zu definieren. Seit ein paar Jahren verwende ich jetzt aber IntelliJ, und das gibt keine Warnung mehr aus, und deshalb vergesse ich das auch häufig. (Allgemein ist IntelliJ sehr nervig in dieser Hinsicht, weil man immer manuell die Docs durchforsten muss, sehr ärgerlich wenn die Generic Ausdrücke komplizierter werden.)

Entsprechend habe ich mich früher mit dieser Frage nie beschäftigt: Was ist denn der Unterschied zwischen diesen Beiden Ausdrücken?

List<?> listW;
List listNW;

Ich dachte immer das wäre genau das gleiche, aber der Compiler wirft häufig Fehler wenn ich versuche die Variablen zu mischen wie hier:

public abstract class GenericExample<C extends String> {
    public abstract C get();
}

public abstract class ExampleExtended extends GenericExample<C extends AdvancedString> {}
ExampleExtended<?> example1;
ExampleExtended example2;

example1.get(); // gibt AdvancedString zurück
example2.get(); // gibt String zurück??

ist für mich nicht nachvollziehbar.

#2

Es ist nicht klar, mit welchen Detailgrad die Frage beantwortet werden sollte.

gibt es da verschiedene mögliche Abstufungen.

Aber etwas vereinfacht:

  • List<?> ist eine Liste mit Elementen, die einen festen Typ haben, den man aber nicht kennt. Man kann zu dieser Liste nichts hinzufügen (außer null), weil man den Typ nicht kennt.
  • List ist ein “raw type”, und das ist böse. Man weiß nicht, was da drin ist, und man “zwingt” den Compiler praktisch, sämtliche Typ-Checks (IIRC für die komplette Klasse) auszuhebeln, weil er sich einfach über nichts mehr sicher sein kann.

Also: Immer zumindest <?> (oder <Object>) dazuschreiben…


Der Code compiliert so erstmal nicht. Genau den passenden Code zu posten könnte Seiten-Diskussionen vermeiden. Compilierbar gemacht:

package de.javagl.procrastinator;

public class GenericType
{
    public static void main(String[] args)
    {
        ExampleExtended<?> example1 = null;
        ExampleExtended example2 = null;

        Integer result1 = example1.get();
        Number result2 = example2.get();
    }
}

abstract class GenericExample<C extends Number>
{
    public abstract C get();
}

abstract class ExampleExtended<C extends Integer> extends GenericExample<C>
{
}
#3

Danke dir, Integer und Number sind tausend mal besser geeignet als was auch immer ich mir da oben gedacht habe. Aber der Code trifft es genau auf den Punkt, warum gibt example2 nicht Integer zurück? Definiert ExampleExtended doch das C ein Untertyp von Integer sein muss und damit muss subsequent GenericExample.get() auch Integer zurück geben.

Wenn ich dich richtig verstehe hebt der raw-Type den Generic aus, aber selbst die Bounds? Und dann auch nur die Bounds der Kindklasse und nicht der Elternklasse?
Gibt es dafür einen sinnvollen Grund?

#4

ein oder der Grund für Vorhandensein von raw type mit diesem Verhalten ist wohl backward compatibility,

in der Annahme dass es vor Generics eine Version mit hardcodierten Typ gab,

public abstract String get();
oder auch
public abstract Number get();
statt C jeweils

bei return type ist es nicht ganz so relevant, wichtiger dagegen bei Parameter von Methoden:
wenn die Basisklasse den Basistyp akzeptiert kann nicht die Subklasse auf einmal den Typ einschränken, nicht bei raw type,

obwohl die Frage besteht ob es mit den alten Klassen vor Generics überhaupt so ein Szenario mal gab/ gibt…,
soll aber eben vielleicht auch mit neuen Klassen für bestimmtes Compilerlevel funktionieren,
wobei, wenn die Subklasse dann intern mit dieser Erwartung arbeitet…

#5

Wie gesagt, man könnte da vieeel dazu schreiben, je nachdem, was genau die Frage ist. Ein bißchen Plaudern, Nachfragen, unklare Sachen, textlastiges etc. ist ja OK für ein Forum - das ist quasi die Abgrenzung gegenüber einer Q/A-Seite! - Aber in bezug auf Java und sein Typsystem und auch noch speziell Generics gibt es viele “Richtungen”, in die man was erzählen könnte, ohne sicher sein zu können, dass es hilft.

Den ersten Teil kann man mit type erasure erklären:

Bei letzteren steht sowohl was da passiert, als auch (indirekt) ein Grund:

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.

Also ja, wenn dort ein <?> steht, dann setzt er praktisch den Typ ein, der durch die Bounds in der Deklaration vorgegeben ist - in diesem Fall das Integer von C extends Integer. Wenn dort nichts steht, also es ein raw type ist, muss er zurückfallen auf das “alles geht”.

Hier hat er sich offenbar zumindest noch den Bound der Elternklasse ziehen können. Tatsächlich hätte ich diese genaue Konstallation so auch nicht auf dem Schirm gehabt (d.h. ich hätte überlegen müssen, was die Rückgabetypen sind (Integer für <?>bzw.Number` für den raw type)).

Der Grund (Ausrede? Naah: Rechtfertigung :smiley: ) ist: Raw types verwendet man einfach nicht. Und auch bei IntelliJ sollte man die passenden Warnings einschalten können. (Schnelle Websuche liefert https://intellij-support.jetbrains.com/hc/en-us/community/posts/206163579-showing-warning-when-declaring-raw-types und mehr…)

#6

List<?> ist einfach die Abkürzung für List<? extends Object>. Wie schon gesagt, es ist ein fester Typ, den wir nur nicht kennen. Deshalb darf man auch nicht einfach irgendwelchen Krams in die Liste packen, weil man eben nicht weiß, ob der Typ passt. Holt man Objekte aus der Liste, ergibt sich aus der Bound, dass sie vom Typ Object sein müssen, auch nicht sehr hilfreich.

Ein Raw-Typ ist als prähistorischer (prägenerischer?) Typ “eigentlich” so etwas wie List<Object>, allerdings kann es krachen, wenn ein Typ generisch erzeugt wurde, und dann als Raw-Typ verwendet wird. Beispiel: Set set = EnumSet.allOf(MyEnum.class); set.add("Bumm!");

Übrigens Gratulation zu deiner Entscheidung, IntelliJ zu benutzen!

split this topic #7

3 Beiträge wurden in ein neues Thema verschoben: IntelliJ vs. Eclipse und Netbeans

#9

Auf Stackoverflow hat man mich darauf hingewiesen, dass wenn ExtendedExample die Methode get() überlädt, die Bounds von ExtendedExample in Kraft treten?! Völlig unverständlich, sagt doch die Java Language Specification 4.8 doch eben genau dass das nicht passieren soll:

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Apropo hat mich das daran erinnert warum ich Stackoverflow überhaupt nicht ausstehen kann. Die Frage wurde direkt als Duplicate markiert auf einen anderen Beitrag über Raw Types allgemein. :roll_eyes:

#10

Nach reiflichem einlesen ist dass hier wohl die richtige Antwort. raw types sind deprecated.