Wann benutzt man Primitive vs Class in Java?

54

Ich sehe, dass Java Boolean (Klasse) vs Boolean (primitiv) hat. Ebenso gibt es eine Ganzzahl (Klasse) vs int (Primitiv). Was ist die beste Vorgehensweise bei der Verwendung der primitiven Version gegenüber der Klasse? Sollte ich grundsätzlich immer die Klassenversion verwenden, es sei denn, ich habe einen bestimmten (Performance-?) Grund, dies nicht zu tun? Was ist die gebräuchlichste, akzeptierte Art, die einzelnen zu verwenden?

Casey Patton
quelle
Für mich sind das keine Klassen, sondern Kisten. Sie sind nur dort, damit Sie Grundelemente dort verwenden können, wo Objekte benötigt werden, dh in Sammlungen. Sie können keine zwei Ganzzahlen hinzufügen (Sie können vorgeben, dass Java die Werte für Sie automatisch boxt | entboxt).
Stonemetal

Antworten:

47

In Punkt 5 von Effective Java sagt Joshua Bloch

Die Lektion ist klar: Ziehen Sie Primitiven Boxprimitiven vor und achten Sie auf unbeabsichtigtes Autoboxen .

Eine gute Verwendung für Klassen ist die Verwendung als generische Typen (einschließlich Auflistungsklassen wie Listen und Maps) oder die Umwandlung in einen anderen Typ ohne implizites Casting (z. B. Integerclass has methods doubleValue()oder byteValue().

Edit: Joshua Blochs Grund ist:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Dieses Programm erhält die richtige Antwort, ist aber aufgrund eines einstelligen Tippfehlers viel langsamer als es sein sollte. Die Variable sumwird als a Longanstelle von a deklariert long, was bedeutet, dass das Programm ungefähr 2 ^ 31 unnötige LongInstanzen erstellt (ungefähr eine für jedes Mal, wenn die long izu der hinzugefügten hinzugefügt wird Long sum). Durch Ändern der Summendeklaration von Longin longwird die Laufzeit auf meinem Computer von 43 Sekunden auf 6,8 Sekunden verringert.

m3th0dman
quelle
2
Es wäre hilfreich, wenn Sie die Gründe für Bloch aufführen würden, anstatt nur seine Schlussfolgerung zu zitieren!
Vaughandroid
@Baqueta Ich habe den Beitrag bearbeitet. Der Grund ist die Leistung.
m3th0dman
Das ist ein bisschen klarer, danke. Ich fühle mich berechtigt, jetzt meine eigene Antwort zu veröffentlichen. :)
Vaughandroid
Vielleicht finden Sie die lmax- Architektur interessant: "Es hat ein bisschen mehr Cleverness gekostet , um eine andere Größenordnung zu erreichen. Das LMAX-Team fand einige Dinge hilfreich, um dorthin zu gelangen. Eine bestand darin, benutzerdefinierte Implementierungen der entworfenen Java-Sammlungen zu schreiben seine Cache-freundlich und vorsichtig mit Müll. ein Beispiel hierfür ist die Verwendung primitive Java - long - Positionen als hashmap Schlüssel mit einer speziell geschrieben Array gesichert Map Implementierung .“
Es sieht so aus, als sollte JIT damit umgehen
deFreitas
28

Die Standardpraxis besteht darin, mit den Primitiven zu arbeiten, es sei denn, Sie haben es mit Generika zu tun (achten Sie darauf, dass Sie auf Autoboxing und Unboxing achten !).

Es gibt eine Reihe von guten Gründen, der Konvention zu folgen:

1. Sie vermeiden einfache Fehler:

Es gibt einige subtile, nicht intuitive Fälle, die Anfänger häufig herausfordern. Sogar erfahrene Programmierer geraten aus dem Ruder und machen manchmal diese Fehler (hoffentlich wird dies von einem Fluch gefolgt, wenn sie den Code debuggen und den Fehler finden!).

Der häufigste Fehler ist die Verwendung von a == banstelle von a.equals(b). Die Leute sind es gewohnt, a == bmit Primitiven umzugehen, so dass dies mit den Objekt-Wrappern problemlos möglich ist.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Lesbarkeit:

Betrachten Sie die folgenden zwei Beispiele. Die meisten Leute würden sagen, die zweite ist besser lesbar.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Leistung:

Tatsache ist , es ist langsamer , den Object - Wrapper für Primitive zu verwenden , als nur die Grundelemente verwenden. Sie addieren die Kosten für Objektinstanziierung, Methodenaufrufe usw. zu den Dingen, die Sie überall verwenden .

Knuths Zitat "... sagt in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels" trifft hier nicht wirklich zu. Er sprach von Optimierungen, die den Code (oder das System) komplizierter machen - wenn Sie Punkt 2 zustimmen, ist dies eine Optimierung, die den Code weniger kompliziert macht!

4. Es ist die Konvention:

Wenn Sie für 99% der anderen Java-Programmierer eine andere Stilauswahl treffen, gibt es zwei Nachteile:

  • Sie werden feststellen, dass der Code anderer Leute schwerer zu lesen ist. 99% der Beispiele / Tutorials / etc verwenden Primitive. Wann immer Sie eines lesen, haben Sie den zusätzlichen kognitiven Aufwand, darüber nachzudenken, wie es im gewohnten Stil aussehen würde.
  • Andere Leute finden Ihren Code schwerer zu lesen. Wann immer Sie Fragen zu Stack Overflow stellen, müssen Sie Antworten / Kommentare durchsuchen, in denen Sie gefragt werden, warum Sie keine Grundelemente verwenden. Wenn Sie mir nicht glauben, schauen Sie sich einfach die Kämpfe an, die die Leute über Dinge wie die Platzierung von Klammern führen, die sich nicht einmal auf den generierten Code auswirken!

Normalerweise würde ich einige Kontrapunkte auflisten, aber ich kann mir ehrlich gesagt keine guten Gründe vorstellen, hier nicht zur Convention zu gehen!

vaughandroid
quelle
2
Empfehlen Sie keine Leute, mit denen Sie Objekte vergleichen möchten ==. Objekte sollten mit verglichen werden equals().
Tulains Córdova
2
@ user61852 Ich habe das nicht als eine Aufgabe vorgeschlagen, sondern als einen häufigen Fehler, der begangen wird! Sollte ich das klarer machen?
Vaughandroid
Ja, Sie erwähnen nicht, dass Objekte mit equals()... verglichen werden sollen . Sie umgehen dies, damit der Vergleich von Objekten mit zu ==den erwarteten Ergebnissen führt.
Tulains Córdova
Guter Punkt. Änderte es.
Vaughandroid
Ich habe equals()zum zweiten Code-Snippet hinzugefügt und meine Stimme geändert.
Tulains Córdova
12

Normalerweise gehe ich mit den Primitiven. Eine Besonderheit bei der Verwendung von Klassen wie Integerund Booleanist jedoch die Möglichkeit, nulldiese Variablen zuzuweisen. Dies bedeutet natürlich, dass Sie die nullganze Zeit Prüfungen durchführen müssen, aber immer noch besser, um eine NullPointerException zu erhalten, als logische Fehler zu haben, weil einige intoder booleanVariablen verwendet werden, die nicht korrekt initialisiert wurden.

Natürlich, da 8 Java können Sie (und wahrscheinlich sollte) noch einen Schritt weiter gehen und statt zB Integerkönnten Sie Optional<Integer>für Variablen , die keinen Wert haben könnten oder könnten.

Außerdem wird die Möglichkeit eingeführt null, diesen Variablen einen " unbekannten " oder " Platzhalter " -Wert zuzuweisen . Dies kann in bestimmten Situationen nützlich sein, beispielsweise in Ternary Logic . Oder Sie möchten möglicherweise überprüfen, ob ein bestimmtes Objekt mit einer Vorlage übereinstimmt. In diesem Fall können Sie nullVariablen in der Vorlage verwenden, die einen beliebigen Wert im Objekt haben können.

tobias_k
quelle
2
(War nicht meine Ablehnung, aber ...) Java erkennt bereits nicht initialisierte Variablen und lässt Sie keine Variable lesen, bis jedem Codepfad, der zu ihrer Verwendung führt, definitiv ein Wert zugewiesen wurde. Sie profitieren also nicht von der Möglichkeit, nullStandardeinstellungen zuzuweisen . Im Gegenteil: Es ist besser, wenn Sie die Variable überhaupt nicht "initialisieren". Durch das Festlegen eines Standardwerts wird nullder Compiler heruntergefahren, es wird jedoch auch verhindert, dass die fehlende sinnvolle Zuordnung auf allen Codepfaden erkannt wird. Somit wird ein Fehler, den der Compiler möglicherweise abgefangen hat, zur Laufzeit weitergeleitet.
CHAO
@cHao Aber was ist, wenn es keinen sinnvollen Standardwert gibt, mit dem die Variable initialisiert werden kann? Sie könnten es auf 0.0, oder -1oder Integer.MAX_VALUEoder setzen False, aber am Ende wissen Sie nicht, ob dies der Standardwert oder ein tatsächlicher Wert ist, der dieser Variablen zugewiesen wurde. In Fällen, in denen dies wichtig ist, ist der nullWert möglicherweise klarer.
tobias_k
Es ist nicht klarer, es ist nur einfacher, dem Compiler mitzuteilen, dass er Sie nicht vor unklarer Logik warnen soll, bis sich ein Fehler bereits verbreitet hat. : P In Fällen, in denen es keine vernünftigen Standardeinstellungen gibt, initialisieren Sie die Variable nicht. Stellen Sie es erst ein, wenn Sie einen vernünftigen Wert haben. Auf diese Weise stoppt Java Sie beim Kompilieren, wenn Ihr Wert möglicherweise nicht richtig eingestellt ist.
CHAO
@cHao Ich meinte, es könnte Fälle geben, in denen Sie die Variable nicht initialisieren können und sich zur Laufzeit damit befassen müssen. In solchen Fällen ist ein "Standard" wie "Null", der eindeutig als "Standard" oder "nicht aktiviert" identifiziert werden kann, möglicherweise besser als eine Initialisierung zur Kompilierungszeit, die auch ein gültiger Wert sein kann.
Tobias_k
Haben Sie einen solchen Fall im Sinn? Die Fälle, an die ich denken kann, befinden sich an Schnittstellengrenzen (z. B. als Parameter- oder Rückgabetypen) ... aber selbst dort wären sie weitaus besser, wenn sie umbrochen würden. (Naked nulls haben eine ganze Reihe von Problemen, einschließlich Null-Paranoia.) Innerhalb einer Funktion weist eine Variable, die zum Zeitpunkt der Verwendung möglicherweise tatsächlich nicht initialisiert wurde, in der Regel auf nicht abgedeckte Fälle hin. (Bestimmte Zuweisungsanalysen sind vereinfachend, so dass Fehlalarme möglich sind. Sie können sie jedoch häufig beheben, indem Sie die Logik vereinfachen.)
cHao
2

Mit den Worten des Laien:

Sie verwenden die Wrapper, wenn Sie Dinge zu Sammlungen hinzufügen müssen.

Sammlungen können keine Grundelemente enthalten.

Tulains Córdova
quelle
0

Java bietet Autoboxing, wie m3th0dman hervorhob. Wenn Sie auf der niedrigstmöglichen Ebene nachdenken, werden Sie feststellen, dass das automatische Aktivieren oder Deaktivieren eines primitiven Werts Taktzyklen impliziert, die für einige Aufgaben erforderlich sind, die Sie nicht benötigen, wenn Sie mit systemeigenen Datentypen in Ihrer Anwendung arbeiten.

In der Regel sollten Sie nach Möglichkeit versuchen, native Datentypen zu verwenden.

Silvarion
quelle