Fallstricke von Java. Teil 1

Guten Tag. Ich möchte Sie auf einen kurzen Artikel aufmerksam machen. Dieser Artikel ist für Anfänger gedacht. Aber auch wenn Sie ein erfahrener Entwickler sind, sollten Sie keine Schlussfolgerungen ziehen.
Ich hoffe, dass diese Veröffentlichung nicht nur für Anfänger nützlich ist.

Der Zweck dieser Veröffentlichung:
Zeigen Sie die häufigsten Fehler Anfänger und einige Tricks, um sie zu beheben. Es ist klar, dass einige Fehler komplex sein und aus dem einen oder anderen Grund auftreten können. Der Zweck der Veröffentlichung besteht in gewissem Maße darin, sie zu analysieren und zu helfen, sie frühzeitig zu identifizieren. Ich hoffe, dass diese Veröffentlichung für Anfänger nützlich sein wird.

Fehlercheckliste:

1. Tippfehler. Ärgerliche Tippfehler, die nicht sofort auftauchen
2. Zuordnung im Zustand statt Vergleich
3. Logische Fehler in der Bedingung
4. Falscher Stringvergleich
5. Falsche Initialisierung von Variablen primitiver Typen
6. Unsachgemäße Verwendung von Double
7. Falscher Typ des Rückgabewerts im Konstruktor.
8. Division durch Null. POSITIVE_INFINITY
9. Ohne Berücksichtigung der Klasseninitialisierungsreihenfolge
10. Eine lokale Variable verbirgt eine Klassenvariable
11. Automatisches Casting in arithmetischen Ausdrücken ignorieren
12. Endlosschleife mit Byte, die schwer zu erkennen ist.
13. Der Name der Klasse unterscheidet sich vom Namen der Datei, in der sie gespeichert ist.
14. Objekte, die Elemente eines Arrays sind, werden nicht initialisiert.
15. Platzieren mehrerer Klassen gleichzeitig in einer Datei mit dem öffentlichen Modifikator

Java-Fallstricke


Alle Programmiersprachen haben ihre Vor- und Nachteile. Dies hat viele Gründe. Java ist keine Ausnahme. Ich habe versucht, einige offensichtliche und nicht offensichtliche Schwierigkeiten zu sammeln, auf die ein unerfahrener Java-Programmierer gestoßen ist. Ich bin sicher, dass erfahrene Programmierer in meinem Artikel auch etwas Nützliches finden werden. Übung, Aufmerksamkeit und gesammelte Programmiererfahrung helfen Ihnen, viele Fehler zu vermeiden. Einige Fehler und Schwierigkeiten sollten jedoch am besten im Voraus berücksichtigt werden. Ich werde einige Beispiele mit Code und Erklärungen geben. Viele Erklärungen werden Ihnen aus den Kommentaren zum Code klar. Übung gibt viel, da einige Regeln nicht so offensichtlich sind. Einige liegen an der Oberfläche, andere sind in den Sprachbibliotheken oder in der virtuellen Java-Maschine versteckt. Denken Sie daran, dass Java nicht nur eine Programmiersprache mit einer Reihe von Bibliotheken ist, sondern auch eine virtuelle Java-Maschine.

Für den Artikel habe ich speziell einen Arbeitscode mit detaillierten Kommentaren geschrieben. Um einen Artikel mit Codebeispielen zu schreiben, wurde Java 8 verwendet. Zum Testen wird Java-Code in separaten Paketen abgelegt.

Beispiel: "package underwaterRocks.simple;"

Welche Schwierigkeiten haben Anfänger?

Tippfehler


Es kommt vor, dass unerfahrene Programmierer Tippfehler machen, die auf einen Blick schwer zu erkennen sind.


Codebeispiel:

Datei: "Simple.java"

/*   ;     */ package underwaterRocks.simple; /** * * @author Ar20L80 */ public class Simple { public static void main(String[] args) { int ival = 10; if(ival>0); { System.out.println("     "); } } } 


Erläuterung : „Das Semikolon gibt das Ende der Anweisung an. In diesem Fall; Ist das Ende einer leeren Anweisung. Dies ist ein logischer Fehler. Ein solcher Fehler kann schwierig zu erkennen sein.

Der Compiler wird berücksichtigen, dass alles korrekt ist. Bedingung wenn (ival> 0); in diesem Fall macht keinen Sinn. Weil es bedeutet: Wenn ival größer als Null ist, tun Sie nichts und fahren Sie fort. “

Zuordnung im Zustand statt Vergleich


Die Bedingung ist die Variablenzuweisung.

Dies ist kein Fehler, aber die Verwendung einer solchen Technik sollte gerechtfertigt sein.

  boolean myBool = false; if(myBool = true) System.out.println(myBool); 

Wenn in diesem Code (myBool = true) bedeutet: „Setzen Sie die Variable myBool auf true,
Wenn der Ausdruck wahr ist, befolgen Sie die Bedingung in Klammern. “

In diesem Code ist die Bedingung immer wahr. Und System.out.println (myBool); wird immer ausgeführt, unabhängig von der Bedingung.

== ist ein Vergleich für Gleichheit.
= Ist eine Aufgabe, kann man sagen a = 10; wie: "aber einen Wert von 10 zuweisen".

Die Bedingung in Klammern gibt einen booleschen Wert zurück.
Es spielt keine Rolle, in welcher Reihenfolge Sie schreiben. Sie können wie folgt vergleichen: (0 == a) oder (5 == a)
Wenn Sie ein Gleichheitszeichen vergessen, z. B. (0 = a) oder (5 = a), benachrichtigt Sie der Compiler über einen Fehler. Sie weisen einen Wert zu, keinen Vergleich.
Sie können auch einige Intervalle in lesbarer Form schreiben.
Zum Beispiel: Sie müssen schreiben: a größer als 5 und kleiner als 10.
Sie schreiben wie folgt: (a> 4 && a <10), aber mit dem gleichen Erfolg können Sie schreiben: (4 <a && a <10),
Jetzt sehen Sie, dass a zwischen 4 und 10 liegt, ohne diese Werte. Das ist offensichtlicher. Es ist sofort ersichtlich, dass a ohne diese Werte im Intervall zwischen 4 und 10 liegt.

Codebeispiel (Intervall] 3.9 [):
if (3 <a && a <9) ausführen;

Logischer Fehler


if (Bedingung) {} if (Bedingung) {} else {} - else bezieht sich auf das nächstgelegene if.
Dies ist häufig die Ursache für Anfängerfehler.

Ungültiger Zeichenfolgenvergleich

Anfänger verwenden häufig == anstelle von .equals, um Zeichenfolgen zu vergleichen.

Variable Initialisierung


Erwägen Sie die Initialisierung von Variablen eines primitiven Typs.

Grundelemente (Byte, kurz, int, lang, char, float, double, boolean).

Anfangswerte.

 byte 0 short 0 int 0 long 0L float 0.0f double 0.0d char '\u0000' String (or any object) null boolean false (  jvm) 


Hinweis:

Lokale Variablen unterscheiden sich geringfügig.
Der Compiler weist einer nicht initialisierten lokalen Variablen niemals einen Standardwert zu.

Wenn Sie Ihre lokale Variable nicht dort initialisieren können, wo sie deklariert ist,
Denken Sie daran, ihm einen Wert zuzuweisen, bevor Sie versuchen, ihn zu verwenden.

Der Zugriff auf eine nicht initialisierte lokale Variable führt zu einem Fehler bei der Kompilierung.

Bestätigung dieses Hinweises im Code:

Datei: "MyInitLocal.java"

 /*         */ package underwaterRocks.myInit; /** * * @author Ar20L80 */ public class MyInitLocal { float classes_f; int classes_gi; public static void main(String[] args) { float f; int i; MyInitLocal myInit = new MyInitLocal(); /*         .*/ System.out.println("myInit.classes_f = " + myInit.classes_f); System.out.println("myInit.classes_gi = " + myInit.classes_gi); // System.out.println("f = " + f); // .     // System.out.println("f = " + i); // .     } } 


Wertebereiche:

byte ( , 1 , [-128, 127])
short ( , 2 , [-32768, 32767])
int ( , 4 , [-2147483648, 2147483647])
long ( , 8 , [-922372036854775808,922372036854775807])
float ( , 4 )
double ( , 8 )
char ( Unicode, 2 , 16 , [0, 65535])
boolean ( /, int, JVM)

char: Der char-Datentyp ist ein einzelnes 16-Bit-Unicode-Zeichen. Es hat einen Mindestwert von '\ u0000' (oder 0) und einen Höchstwert von '\ uffff' (oder 65.535 einschließlich).


Oracle-Dokumentation >>

Versuchen wir, eine Variable vom Typ long mit der Nummer 922372036854775807 zu initialisieren.
Für uns wird nichts klappen. Weil es ein ganzzahliges Literal vom Typ int ist.
Richtige Initialisierung mit einem langen Literal: 922372036854775807L;

Codebeispiel:

Datei: "MyInitLocalLong.java"

 /*    long  */ package underwaterRocks.myInit; /** * * @author Ar20L80 */ public class MyInitLocalLong { public static void main(String[] args) { // long al = 922372036854775807; // integer number too large long bl = 922372036854775807L; //   } } 


Worauf Sie beim Initialisieren einer Variablen achten müssen.

Der Wertebereich einer Variablen dieses Typs. Die Tatsache, dass die Variable mit einem Literal eines bestimmten Typs initialisiert wird. Für explizite und implizite Besetzungen. Auf Typkompatibilität.

Wenn Sie Shells vom Typ Integer verwenden, sollten Sie auf das automatische Packen und automatische Entpacken dieser Typen achten.

Unangemessene Verwendung von Doppel


Hier müssen Sie klären. Hier geht es nicht darum, den Doppeltyp zu missbrauchen.
Wir verwenden richtig. Nur das Ergebnis kann einen unerfahrenen Programmierer überraschen.
Datei: "MinusDouble.java"
 /*   */ package underwaterRocks.tstDouble; /** * * @author vvm */ public class MinusDouble { public static void main(String[] args) { double a = 4.64; double b = 2.64; System.out.println("ab = "+(ab)); } } /*   run: ab = 1.9999999999999996 */ 


Hinweis zum Doppeltyp. Mit einem Gleitkomma können Sie mit einem bestimmten relativen Fehler und einem großen Bereich zählen. Bei wissenschaftlichen Berechnungen wird häufig ein relativer Fehler benötigt.

Ungültiger Doppelvergleich


Betrachten Sie den Doppeltyp.

Codebeispiel:

Datei: "MyDouble.java"

 /*    double  - double. */ package underwaterRocks.myDouble; /** * * @author Ar20L80 */ public class MyDouble { public static void main(String[] args) { double dx = 1.4 - 0.1 - 0.1 - 0.1 - 0.1; System.out.println("dx = " + dx); // dx = 0.9999999999999997 System.out.print(" (dx == 1.0):"); System.out.println(dx == 1.0); // false,   1.0   0.9999999999999997 /*   double*/ final double EPSILON = 1E-14; double xx = 1.4 - 0.1 - 0.1 - 0.1 - 0.1; double xy = 1.0; /*  xx c xy */ if (Math.abs(xx - xy) < EPSILON) System.out.println(xx + "    " + xy + " EPSILON = " + EPSILON); } } 

Der Doppeltyp ist praktisch, wenn keine hohe Präzision erforderlich ist. Für Finanztransaktionen ist dieser Typ nicht geeignet. Obwohl einige, nicht sehr ehrliche Unternehmen, den Doppeltyp verwenden, um auf die Seite zu runden, die sie benötigen. Für Finanzoperationen wird die BigDecimal-Klasse in Finanzberechnungen verwendet, da echte primitive Typen aufgrund von Genauigkeitsverlusten und Fehlern bei Rundungsergebnissen für diesen Zweck nicht geeignet sind. Mit der BigInteger-Klasse werden jedoch genauere Ergebnisse erzielt.

Klassenkonstruktor


Der Klassenkonstruktor stimmt mit dem Klassennamen überein und gibt nichts zurück, nicht einmal void.

Codebeispiel:

Datei: "MyConstructor.java"

 /*      ,  void    void -    */ package underwaterRocks.myConstructor; /** * * @author Ar20L80 */ public class MyConstructor { public MyConstructor(){ System.out.println("   void"); } public void MyConstructor(){ System.out.println("  c void"); } public static void main(String[] args) { MyConstructor myconst = new MyConstructor(); myconst.MyConstructor(); //    } } 

Wie wir im Code sehen, zwei Methoden mit demselben Namen: MyConstructor () und MyConstructor (). Eine der Methoden gibt nichts zurück. Dies ist der Konstruktor unserer Klasse. Eine andere Methode mit void ist die reguläre Klassenmethode. Wenn Sie keinen Konstruktor oder Ihrer Meinung nach einen Klassenkonstruktor mit void erstellt haben, erstellt der Compiler einen Standardkonstruktor und Sie werden überrascht sein, warum Ihr Konstruktor nicht funktioniert.

Division durch Null


Was wird Ihrer Meinung nach das Ergebnis der Ausführung eines solchen Codes sein?

Datei: "DivisionByZero.java"

 /* */ package divisionByZero; import static java.lang.Double.POSITIVE_INFINITY; /** * * @author Ar20L80 */ public class DivisionByZero { public static void main(String[] args) { try{ float f = 12.2f; double d = 8098098.8790d; System.out.println(f/0); System.out.println(d/0); System.out.println(POSITIVE_INFINITY == f/0); System.out.println(POSITIVE_INFINITY == d/0); } catch (NumberFormatException ex) { System.out.println("NumberFormatException"); } catch (ArithmeticException ex) { System.out.println("ArithmeticException"); } } } 

Beim Ausführen des Codes wird Folgendes ausgegeben:

 Infinity Infinity true true 

Wenn Sie den Integer-Typ durch Null teilen, erhalten Sie eine ArithmeticException.

Die Klasse java.lang.Double definiert die Konstante POSITIVE_INFINITY;

 public static final float POSITIVE_INFINITY = 1.0d / 0.0d; 

Es wird in eine Zeichenfolge konvertiert, die Infinity entspricht.

Initialisierungsreihenfolge


Datei: "InitClass.java"

 /*     */ package myInitClass; /** * * @author Ar20L80 */ public class InitClass { InitClass(){ //   System.out.print(""); } { //   System.out.print("3 "); } public static void main(String[] args) { System.out.print("2"); new InitClass(); } static { //    System.out.print("1"); } } 

Zuerst werden alle statischen Blöcke ausgeführt, dann Initialisierungsblöcke und dann der Klassenkonstruktor.

Es wird angezeigt: "123 Konstruktor"

Eine lokale Variable verbirgt eine Klassenvariable
Obwohl moderne IDEs einen solchen Fehler leicht erkennen, möchte ich einen solchen Fehler genauer betrachten. Beginnen wir mit der klassischen Variablenzuweisung im Konstruktor. Das Beispiel ist richtig. Es gibt keinen Fehler.
 public class MyClass { private int val = 0; public MyClass(int val) { this.val = val; } } 

Was passiert jedoch, wenn Sie diese Technik in einer Methode und nicht in einem Klassenkonstruktor verwenden? Bei der üblichen Methode wird die Verwendung dieser Technik nicht empfohlen. Die Frage bezieht sich auf die richtige Gestaltung einer Klasse.

Eine einfache Erklärung: In einer Methode ist eine Variable mit demselben Namen wie eine Klassenvariable lokal für die Methode. Sie können mit this.val auf die Klassenvariable zugreifen. Ein solcher Aufruf der Methode führt jedoch nur zu Nebenwirkungen, wenn die Klasse nicht ordnungsgemäß entworfen wurde, und kann die Lesbarkeit des Codes beeinträchtigen.

Das Gießen vom arithmetischen Typ erfolgt automatisch

Dies kann zu störenden Fehlern führen.
 // byte a = 1; // byte b = 1; // byte  = a + b; //  // byte a = (byte) 1; // byte b = (byte) 1; // byte  = a + b; //  


 //     —     . byte a = 1; byte b = 1; byte c = (byte) (a + b); 


 //     —  final // final byte a = 1; // final byte b = 1; // byte c = a + b; //    ,  a  b final 


Eine mögliche Lösung beim Arbeiten mit einem String:
 byte bHundr = Byte.parseByte("100"); //      byte 


Ein weiterer Fehler wird im folgenden Code angegeben.
 for (byte i = 1; i <= 128; i++) { System.out.println(i); } 

In diesem Fall erhalten wir eine Endlosschleife.

Die Erklärung. Geben Sie Byte [-128, 127] ein. 128 liegt nicht mehr in diesem Bereich. Ein Überlauf tritt auf und der Zyklus wiederholt sich. Die Notwendigkeit, in diesem Fall Byte zu verwenden, ist zweifelhaft. Obwohl es in seltenen Fällen stattfindet. Es wird empfohlen, int anstelle von Byte zu verwenden. Eine weitere Empfehlung ist, keine Schleife in Ihrem Algorithmus zu verwenden.

Objekte, die Array-Elemente sind, werden nicht initialisiert
 int[] cats = new int[10]; for(int i=0; i<cats.length;i++){ System.out.println("cats " + i + " = " + cats[i]); } 


In diesem Beispiel haben wir ein Array primitiver Elemente. Und nichts Schlimmes passiert, wenn wir sie nicht initialisieren. Ihnen wird der Standardwert zugewiesen. In diesem Fall ist der Wert = 0.

Betrachten wir ein anderes Beispiel, nicht mit Grundelementen im Array, sondern mit Objekten im Array.
 public class ArrInitObj { public static void main(String[] args) { MyObj[] cats = new MyObj[10]; for(int i=0; i<cats.length;i++){ System.out.println("cats " + i + " = " + cats[i]); System.out.println("cats " + i + ".val = " + cats[i].val); //    java.lang.NullPointerException } } } class MyObj{ public int val; } 


Die Lösung für dieses Problem besteht darin, alle Objektvariablen vor ihrer Verwendung zu initialisieren. Die Initialisierung kann im Konstruktor der MyObj-Klasse erfolgen.

Der Klassenname unterscheidet sich vom Namen der Datei, in der er gespeichert ist
Die moderne IDE erkennt diese Art von Fehler leicht. Solche Fehler treten jedoch auf, wenn auch ziemlich selten. Dies hilft bei der Aufmerksamkeit, da Unterschiede in den Namen von Groß- und Kleinbuchstaben berücksichtigt werden.

Platzieren mehrerer Klassen in einer Datei gleichzeitig mit dem öffentlichen Modifikator
Der Fehler ist ziemlich selten. Die IDE gibt Ihnen sofort eine Warnung.
Der Dateiname muss mit dem Namen der öffentlichen Klasse übereinstimmen.

Schlussfolgerungen
Viele Fehler sind auf den ersten Blick nicht offensichtlich. Sogar erfahrene Programmierer tun dies, jedoch in geringerer Anzahl. Sorgfalt, praktische Erfahrung, die Verwendung eines Debuggers und das Lesen der Dokumentation helfen Ihnen, viele Fehler zu vermeiden.

Ich hoffe, Ihnen hat der Artikel gefallen und Sie fanden ihn hilfreich. Ich freue mich über Ihre Kommentare, Kommentare, Vorschläge, Wünsche. Fortsetzung folgt. Vielmehr folgt der Zusatz.

Referenzen
Richtlinien für das Design von Oracle Java-Code >>>

PS. Meine Freunde. Ich kann ohne Ihre Hilfe nicht weiter veröffentlichen. Das heißt, keine finanzielle Chance. Wenn die Veröffentlichung Ihnen wirklich geholfen hat und Sie fortfahren möchten, unterstützen Sie mich bitte. Irgendwo gibt es eine Schaltfläche: "Unterstütze den Autor."
Ich hoffe auf Ihr Verständnis. Ich danke Ihnen. Vielen Dank an Habr für die Gelegenheit zur Veröffentlichung.

Der Autor wird gezwungen sein, seine Veröffentlichungen zu löschen oder sie in Entwurfskopien zu verstecken, wenn keine Unterstützung vorliegt. Dies ist kein Ultimatum. Wenn Sie die Möglichkeit haben, hat es sich als nützlich erwiesen. Sie können helfen und dann auf die Schaltfläche für die Autorenunterstützung klicken.

Source: https://habr.com/ru/post/de439642/


All Articles