java - showinputdialog - joptionpane showoptiondialog




Können wir die Ergebnisse des Diamantoperators vom Rohkonstruktor unterscheiden? (4)

Sie haben möglicherweise Probleme mit dem Standardkonstruktor, wenn Ihre generischen Argumente begrenzt sind. Zum Beispiel ist hier schlampig und unvollständige Umsetzung der Liste der Zahlen, die die Gesamtsumme verfolgt:

public class NumberList<T extends Number> extends AbstractList<T> {
    List<T> list = new ArrayList<>();
    double sum = 0;

    @Override
    public void add(int index, T element) {
        list.add(index, element);
        sum += element.doubleValue();
    }

    @Override
    public T remove(int index) {
        T removed = list.remove(index);
        sum -= removed.doubleValue();
        return removed;
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }

    public double getSum() {
        return sum;
    }
}

Das Weglassen der generischen Argumente für den Standardkonstruktor kann zu ClassCastException in Runtime führen:

List<String> list = new NumberList(); // compiles with warning and runs normally
list.add("test"); // throws CCE

Das Hinzufügen des Rautenoperators führt jedoch zu einem Kompilierungsfehler:

List<String> list = new NumberList<>(); // error: incompatible types
list.add("test");

Ich habe einen Code, den ich schreiben würde

GenericClass<Foo> foos = new GenericClass<>();

Während ein Kollege es schreiben würde

GenericClass<Foo> foos = new GenericClass();

argumentierend, dass in diesem Fall der Diamantoperator nichts hinzufügt.

Ich bin mir bewusst, dass Konstruktoren , die tatsächlich Argumente verwenden, die sich auf den generischen Typ beziehen, einen Kompilierzeitfehler mit <> anstelle eines Laufzeitfehlers im Rohfall verursachen können. Und dass der Kompilierzeitfehler viel besser ist. (Wie in dieser Frage dargelegt)

Ich bin mir auch ziemlich bewusst, dass der Compiler (und IDE) Warnungen für die Zuordnung von Rohtypen zu Generics generieren kann.

Die Frage ist stattdessen für den Fall, in dem es keine Argumente oder keine Argumente gibt, die sich auf den generischen Typ beziehen. GenericClass<Foo> foos in diesem Fall eine Möglichkeit, wie sich das konstruierte Objekt GenericClass<Foo> foos unterscheiden kann, je nachdem, welcher Konstruktor verwendet wurde, oder garantiert Javas, dass sie identisch sind?


Die JLS ist in diesem Punkt ziemlich klar. http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2

Zuerst heißt es: "Eine generische Klassendeklaration definiert eine Menge parametrisierter Typen (§4.5), eine für jede mögliche Parametrisierung des Typparameter-Abschnitts nach Typargumenten. Alle diese parametrisierten Typen haben zur Laufzeit dieselbe Klasse."

Dann gibt es uns den Codeblock

Vector<String>  x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
boolean b = x.getClass() == y.getClass();

und sagt, dass es "dazu führen wird, dass die Variable b den Wert wahr hält ".

Der Test für die Gleichheit der Instanz ( == ) besagt, dass sowohl x als auch y genau dasselbe y teilen .

Jetzt mach es mit dem Diamant-Operator und ohne.

Vector<Integer> z = new Vector<>();
Vector<Integer> w = new Vector();
boolean c = z.getClass() == w.getClass();
boolean d = y.getClass() == z.getClass();

Auch hier ist c true und so auch d .

Wenn Sie also, wie ich verstehe, fragen, ob es zur Laufzeit oder im Bytecode einen Unterschied zwischen der Verwendung des Diamanten und nicht gibt, ist die Antwort einfach. Es gibt keinen Unterschied.

Ob es in diesem Fall besser ist, den Diamantenoperator zu verwenden, ist eine Frage des Stils und der Meinung.

PS Schiess nicht auf den Boten. Ich würde in diesem Fall immer den Diamantenoperator verwenden. Aber das ist nur, weil ich mag, was der Compiler für mich im Allgemeinen w / r / t Generika tut, und ich möchte nicht in irgendwelche schlechten Gewohnheiten fallen.

PPS Vergessen Sie nicht, dass dies ein vorübergehendes Phänomen sein kann. http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8 warnt uns, dass "die Verwendung von rohen Typen im Code nach der Einführung von Generika in die Java-Programmierung geschrieben wurde Von der Sprache wird dringend abgeraten. Es ist möglich, dass zukünftige Versionen der Java-Programmiersprache die Verwendung von Rohtypen verbieten. "


Dies ist keine vollständige Antwort - bietet aber ein paar weitere Details.

Während Sie Anrufe wie nicht unterscheiden können

GenericClass<T> x1 = new GenericClass<>();
GenericClass<T> x2 = new GenericClass<T>();
GenericClass<T> x3 = new GenericClass();

Es gibt Tools, mit denen Sie unterscheiden können

GenericClass<T> x4 = new GenericClass<T>() { };
GenericClass<T> x5 = new GenericClass() { };

Hinweis: Es sieht zwar so aus, als würden wir die new GenericClass<>() { } nicht finden, aber Java ist derzeit nicht gültig.

Der Schlüssel ist, dass Typinformationen über die generischen Parameter für anonyme Klassen gespeichert werden. Insbesondere können wir über Parameter zu den generischen Parametern gelangen

Type superclass = x.getClass().getGenericSuperclass();
Type tType = (superclass instanceof ParameterizedType) ?
             ((ParameterizedType) superclass).getActualTypeArguments()[0] : 
             null;
  • Für x1 , x2 und x3 tType eine Instanz von TypeVariableImpl (dieselbe Instanz in allen drei Fällen, was nicht überraschend ist, da getClass() für alle drei Fälle das gleiche Objekt zurückgibt.

  • Für x4 tType wird T.class

  • Für x5 getGenericSuperclass() keine Instanz von ParameterizedType , sondern eine Class (infact GenericClass.class )

Wir könnten dann verwenden, um zu bestimmen, ob unser Objekt über (x1, x2 oder x3) oder x4 oder x5 konstruiert wurde.


Für Instanzen von zwei ArrayList , eine mit dem Diamant-Operator am Ende und eine ohne ...

List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();

... der erzeugte Bytecode ist identisch.

LOCALVARIABLE fooList Ljava/util/List; L1 L4 1
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
LOCALVARIABLE barList Ljava/util/List; L2 L4 2
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>

Es würde also keinen Unterschied zwischen den beiden nach dem Bytecode geben.

Wenn Sie den zweiten Ansatz verwenden, generiert der Compiler jedoch eine ungeprüfte Warnung. Daher ist der zweite Ansatz wirklich wertlos; Alles, was Sie tun, erzeugt eine falsch positive ungeprüfte Warnung mit dem Compiler, die zu dem Rauschen des Projekts beiträgt.

Ich habe es geschafft, ein Szenario zu demonstrieren, in dem dies aktiv schädlich ist . Der formale Name dafür ist Haufenverschmutzung . Dies ist nicht etwas, das Sie in Ihrer Codebasis auftreten möchten , und jedes Mal, wenn Sie diese Art von Aufruf sehen, sollte es entfernt werden.

Betrachten Sie diese Klasse, die einige Funktionen von ArrayList .

class Echo<T extends Number> extends ArrayList<T> {
    public Echo() {

    }

    public Echo(Class<T> clazz)  {
        try {
            this.add(clazz.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            System.out.println("YOU WON'T SEE ME THROWN");
            System.exit(-127);
        }
    }
}

Scheint harmlos genug; Sie können eine Instanz von dem hinzufügen, was Ihr Typ gebunden ist.

Wenn wir jedoch mit rohen Typen herumspielen, kann es einige unglückliche Nebenwirkungen geben.

final Echo<? super Number> oops = new Echo(ArrayList.class);
oops.add(2);
oops.add(3);

System.out.println(oops);

Dies druckt [[], 2, 3] anstatt irgendeine Ausnahme auszugeben. Wenn wir eine Operation für alle Integer Operationen in dieser Liste ausführen ClassCastException , würden wir aufgrund dieses wunderbaren ClassCastException ArrayList.class auf eine ClassCastException .

Natürlich könnte all das vermieden werden, wenn der Diamantoperator hinzugefügt würde, was garantieren würde, dass wir ein solches Szenario nicht in den Händen halten würden.

Jetzt, da wir einen Rohtyp in den Mix eingeführt haben, kann Java keine Typprüfung nach JLS 4.12.2 durchführen:

Zum Beispiel der Code:

List l = new ArrayList<Number>();
List<String> ls = l;  // Unchecked warning

führt zu einer unkontrollierten Warnung zur Kompilierungszeit, da weder zur Kompilierzeit (innerhalb der Grenzen der Regeln zur Überprüfung der Kompilierzeit) noch zur Laufzeit festgestellt werden kann, ob sich die Variable l tatsächlich auf eine List<String> bezieht List<String>

Die obige Situation ist sehr ähnlich. Wenn wir uns das erste Beispiel ansehen, das wir verwendet haben, fügen wir keine zusätzliche Variable hinzu. Die Haufenverschmutzung tritt trotzdem auf.

List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;

Während also der Byte-Code identisch ist (wahrscheinlich aufgrund von Löschung), bleibt die Tatsache bestehen, dass ein abweichendes oder abweichendes Verhalten von einer solchen Deklaration herrühren kann.

Verwenden Sie keine rohen Typen , mmkay?







language-lawyer