JavaRush /Blog Java /Random-PL /Błędy początkujących programistów Java. Część 2
articles
Poziom 15

Błędy początkujących programistów Java. Część 2

Opublikowano w grupie Random-PL
Błędy początkujących programistów Java. Część 1

9. Wywoływanie metod klas niestatycznych z metody main().

Punktem wejścia dowolnego programu Java powinna być metoda statyczna main:
Błędy początkujących programistów Java.  Część 2 - 1
public static void main(String[] args) {
  ...
}
Ponieważ ta metoda jest statyczna, nie można z niej wywoływać metod klas niestatycznych. Studenci często o tym zapominają i próbują wywoływać metody bez tworzenia instancji klasy. Ten błąd popełniany jest zazwyczaj już na samym początku szkolenia, kiedy uczniowie piszą małe programy. Zły przykład:
public class DivTest {
    boolean divisible(int x, int y) {
        return (x % y == 0);
    }

    public static void main(String[] args) {
        int v1 = 14;
        int v2 = 9;

        // на следующие строки компилятор выдаст ошибку
        if (divisible(v1, v2)) {
            System.out.println(v1 + " is a multiple of " + v2);
        } else {
            System.out.println(v2 + " does not divide " + v1);
        }
    }
}
Istnieją 2 sposoby naprawienia błędu: ustaw żądaną metodę jako statyczną lub utwórz instancję klasy. Aby wybrać właściwą metodę, zadaj sobie pytanie, czy metoda wykorzystuje pole, czy inne metody klasowe. Jeśli tak, powinieneś utworzyć instancję klasy i wywołać na niej metodę, w przeciwnym razie powinieneś ustawić metodę statyczną. Poprawiony przykład 1:
public class DivTest {
    int modulus;

    public DivTest(int m) {
      modulus = m;
    }

    boolean divisible(int x) {
        return (x % modulus == 0);
    }

    public static void main(String[] args) {
        int v1 = 14;
        int v2 = 9;

        DivTest tester = new DivTest(v2);

        if (tester.divisible(v1) {
            System.out.println(v1 + " is a multiple of " + v2);
        } else {
            System.out.println(v2 + " does not divide " + v1);
        }
    }
}
Poprawiony przykład 2:
public class DivTest {
    static boolean divisible(int x, int y) {
        return (x % y == 0);
    }

    public static void main(String[] args) {
        int v1 = 14;
        int v2 = 9;

        if (divisible(v1, v2)) {
            System.out.println(v1 + " is a multiple of " + v2);
        } else {
            System.out.println(v2 + " does not divide " + v1);
        }
    }
}

10. Wykorzystanie obiektów klasy String jako parametrów metody.

W Javie klasa java.lang.Stringprzechowuje dane w postaci ciągów znaków. Jednak ciągi znaków w Javie
  1. mają charakter trwały (tzn. nie podlegają zmianie),
  2. są przedmiotami.
Dlatego nie można ich traktować jedynie jako buforu znaków; są to obiekty niezmienne. Czasami uczniowie przekazują ciągi znaków w błędnym oczekiwaniu, że obiekt ciągu zostanie przekazany jako tablica znaków przez referencję (jak w C lub C++). Kompilator zwykle nie uważa tego za błąd. Zły przykład.
public static void main(String args[]) {
   String test1 = "Today is ";
   appendTodaysDate(test1);
   System.out.println(test1);
}

/* прим. редактора: закомментированный метод должен иметь модификатор
    static (здесь автором допущена ошибка №9)
public void appendTodaysDate(String line) {
    line = line + (new Date()).toString();
}
*/

public static void appendTodaysDate(String line) {
    line = line + (new Date()).toString();
}
W powyższym przykładzie uczeń chce zmienić wartość zmiennej lokalnej, test1przypisując nową wartość parametrowi linew metodzie appendTodaysDate. Naturalnie to nie zadziała. Znaczenie linesię zmieni, ale znaczenie test1pozostanie takie samo. Ten błąd występuje z powodu nieporozumienia, że ​​(1) obiekty Java są zawsze przekazywane przez referencję i (2) ciągi znaków w Javie są niezmienne. Musisz zrozumieć, że obiekty łańcuchowe nigdy nie zmieniają swojej wartości, a wszystkie operacje na ciągach znaków tworzą nowy obiekt. Aby naprawić błąd z powyższego przykładu, musisz albo zwrócić ciąg znaków z metody, albo przekazać obiekt StringBufferjako parametr do metody zamiast String. Poprawiony przykład 1:
public static void main(String args[]) {
   String test1 = "Today is ";
   test1 = appendTodaysDate(test1);
   System.out.println(test1);
}

public static String appendTodaysDate(String line) {
    return (line + (new Date()).toString());
}
Poprawiony przykład 2:
public static void main(String args[]) {
   StringBuffer test1 = new StringBuffer("Today is ");
   appendTodaysDate(test1);
   System.out.println(test1.toString());
}

public static void appendTodaysDate(StringBuffer line) {
    line.append((new Date()).toString());
}

około. tłumaczenie
W rzeczywistości nie jest łatwo zrozumieć, na czym polega błąd. Ponieważ obiekty są przekazywane przez referencję, oznacza to, lineże odnoszą się do tego samego miejsca co test1. Oznacza to, że tworząc nowy line, tworzymy nowy.W test1złym przykładzie wszystko wygląda tak, jakby przelew Stringodbywał się wartościowo, a nie referencyjnie.

11. Deklaracja konstruktora jako metody

Konstruktory obiektów w Javie wyglądem przypominają zwykłe metody. Jedyną różnicą jest to, że konstruktor nie określa typu zwracanej wartości, a nazwa jest taka sama jak nazwa klasy. Niestety Java pozwala, aby nazwa metody była taka sama jak nazwa klasy. W poniższym przykładzie uczeń chce zainicjować pole klasy Vector listpodczas tworzenia klasy. Tak się nie stanie, ponieważ metoda 'IntList'nie jest konstruktorem. Zły przykład.
public class IntList {
    Vector list;

    // Выглядит Jak конструктор, но на самом деле это метод
    public void IntList() {
        list = new Vector();
    }

    public append(int n) {
        list.addElement(new Integer(n));
    }
}
Kod zgłosi wyjątek NullPointerExceptionprzy pierwszym dostępie do pola list. Błąd można łatwo naprawić: wystarczy usunąć wartość zwracaną z nagłówka metody. Poprawiony przykład:
public class IntList {
    Vector list;

    // Это конструктор
    public IntList() {
        list = new Vector();
    }

    public append(int n) {
        list.addElement(new Integer(n));
    }
}

12. Zapomniałem rzucić obiekt na wymagany typ

Podobnie jak wszystkie inne języki obiektowe, w Javie obiekt można nazwać jego nadklasą. Nazywa się to 'upcasting'i odbywa się automatycznie w Javie. Jeśli jednak zmienna, pole klasy lub wartość zwracana przez metodę zostanie zadeklarowana jako nadklasa, pola i metody podklasy będą niewidoczne. Odnosząc się do nadklasy jako podklasy 'downcasting', należy ją samodzielnie zarejestrować (czyli przenieść obiekt do żądanej podklasy). Studenci często zapominają o podklasie obiektu. Dzieje się tak najczęściej podczas używania tablic obiektów i kolekcji z pakietu java.util(co oznacza Collection Framework ). Poniższy przykład Stringumieszcza obiekt w tablicy, a następnie usuwa go z tablicy, aby porównać go z innym ciągiem znaków. Kompilator wykryje błąd i nie skompiluje kodu, dopóki nie zostanie jawnie określone rzutowanie typu. Zły przykład.
Object arr[] = new Object[10];
arr[0] = "m";
arr[1] = new Character('m');

String arg = args[0];
if (arr[0].compareTo(arg) < 0) {
    System.out.println(arg + " comes before " + arr[0]);
}
Znaczenie rzutowania typów jest dla niektórych trudne. Szczególnie często trudności sprawiają metody dynamiczne. equalsW powyższym przykładzie, gdyby zamiast metody została użyta metoda compareTo, kompilator nie zgłosiłby błędu, a kod działałby poprawnie, ponieważ zostałaby wywołana metoda equalsklasy String. Musisz zrozumieć, że łączenie dynamiczne różni się od łączenia downcasting. Poprawiony przykład:
Object arr[] = new Object[10];
arr[0] = "m";
arr[1] = new Character('m');

String arg = args[0];
if ( ((String) arr[0]).compareTo(arg) < 0) {
    System.out.println(arg + " comes before " + arr[0]);
}

13. Korzystanie z interfejsów.

Dla wielu uczniów różnica pomiędzy klasami i interfejsami nie jest do końca jasna. Dlatego niektórzy uczniowie próbują implementować interfejsy, używającObserver słowa kluczowego RunnableExtends zamiast implements . Aby poprawić błąd, wystarczy poprawić słowo kluczowe na właściwe. Zły przykład:
public class SharkSim extends Runnable {
    float length;
    ...
}
Poprawiony przykład:
public class SharkSim implements Runnable {
    float length;
    ...
}
Powiązany błąd: Niepoprawna kolejność rozszerzania i implementowania bloków . Zgodnie ze specyfikacją Java deklaracje rozszerzeń klas muszą występować przed deklaracjami implementacji interfejsu. Ponadto w przypadku interfejsów słowo kluczowe implements należy zapisać tylko raz; wiele interfejsów oddziela się przecinkami. Kilka bardziej błędnych przykładów:
// Неправильный порядок
public class SharkSim implements Swimmer extends Animal {
    float length;
    ...
}

// ключевое слово implements встречается несколько раз
public class DiverSim implements Swimmer implements Runnable {
    int airLeft;
    ...
}
Poprawione przykłady:
// Правильный порядок
public class SharkSim extends Animal implements Swimmer {
    float length;
    ...
}

// Несколько интерфейсов разделяются запятыми
public class DiverSim implements Swimmer, Runnable {
    int airLeft;
    ...
}

14. Zapomniałem użyć wartości zwracanej przez metodę nadklasy

Java umożliwia wywołanie podobnej metody nadklasy z podklasy za pomocą słowa kluczowego. Czasami uczniowie muszą wywoływać metody nadklasy, ale często zapominają o użyciu wartości zwracanej. Dzieje się tak szczególnie często wśród tych uczniów, którzy nie zrozumieli jeszcze metod i wartości zwracanych przez nie. W poniższym przykładzie uczeń chce wstawić wynik metody toString()nadklasy do wyniku toString()metody podklasy. Nie wykorzystuje jednak wartości zwracanej przez metodę nadklasy. Zły przykład:
public class GraphicalRectangle extends Rectangle {
      Color fillColor;
      boolean beveled;
      ...
      public String toString() {
          super();
          return("color=" + fillColor + ", beveled=" + beveled);
      }
}
Aby skorygować błąd, zwykle wystarczy przypisać zwracaną wartość do zmiennej lokalnej, a następnie wykorzystać tę zmienną przy obliczaniu wyniku metody podklasy. Poprawiony przykład:
public class GraphicalRectangle extends Rectangle {
      Color fillColor;
      boolean beveled;
      ...
      public String toString() {
          String rectStr = super();
          return(rectStr + " - " +
         "color=" + fillColor + ", beveled=" + beveled);
      }
}

15. Zapomniałem dodać komponenty AWT

AWT wykorzystuje prosty model projektowania GUI: każdy komponent interfejsu musi najpierw zostać utworzony przy użyciu własnego konstruktora, a następnie umieszczony w oknie aplikacji przy użyciu add()metody komponentu nadrzędnego. W ten sposób interfejs AWT otrzymuje strukturę hierarchiczną. Uczniowie czasami zapominają o tych 2 krokach. Tworzą komponent, ale zapominają o umieszczeniu go w oknie powiększenia. Nie spowoduje to błędów na etapie kompilacji, komponent po prostu nie pojawi się w oknie aplikacji. Zły przykład.
public class TestFrame extends Frame implements ActionListener {
    public Button exit;

    public TestFrame() {
        super("Test Frame");
        exit = new Button("Quit");
    }
}
Aby naprawić ten błąd, wystarczy dodać komponenty do ich rodziców. Poniższy przykład pokazuje, jak to zrobić. Należy zauważyć, że często student, który zapomni dodać komponent do okna aplikacji, zapomina również o przypisaniu detektorów zdarzeń do tego komponentu. Poprawiony przykład:
public class TestFrame extends Frame implements ActionListener {
    public Button exit;

    public TestFrame() {
        super("Test Frame");

        exit = new Button("Quit");

        Panel controlPanel = new Panel();
        controlPanel.add(exit);

        add("Center", controlPanel);

        exit.addActionListener(this);
    }

    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
}

17. Zapomniałem rozpocząć transmisję

Wielowątkowość w Javie jest implementowana przy użyciu platformy java.lang.Thread. Cykl życia wątku składa się z 4 etapów: zainicjowania, uruchomienia, zablokowania i zatrzymania. Nowo utworzony wątek jest w stanie zainicjowanym. Aby wprowadzić go w stan działania, musisz wywołać jego funkcję start(). Czasami uczniowie tworzą wątki, ale zapominają je rozpocząć. Zwykle błąd pojawia się, gdy student nie ma wystarczającej wiedzy na temat programowania równoległego i wielowątkowości. (w przybliżeniu tłumaczę: nie widzę połączenia) Aby naprawić błąd, wystarczy rozpocząć wątek. W poniższym przykładzie uczeń chce stworzyć animację obrazka za pomocą interfejsu Runnable, ale zapomniał rozpocząć wątek. Zły przykład
public class AnimCanvas extends Canvas implements Runnable {
        protected Thread myThread;
        public AnimCanvas() {
                myThread = new Thread(this);
        }

        // метод run() не будет вызван,
        // потому что поток не запущен.
        public void run() {
                for(int n = 0; n < 10000; n++) {
                   try {
                     Thread.sleep(100);
                   } catch (InterruptedException e) { }

                   animateStep(n);
                }
        }
        ...
}
Poprawiony przykład:
public class AnimCanvas extends Canvas implements Runnable {
        static final int LIMIT = 10000;
        protected Thread myThread;

        public AnimCanvas() {
                myThread = new Thread(this);
                myThread.start();
        }

        public void run() {
                for(int n = 0; n < LIMIT; n++) {
                        try {
                          Thread.sleep(100);
                        } catch (InterruptedException e) { }

                        animateStep(n);
                }
        }
        ...
}
Cykl życia wątku oraz relacje między wątkami i klasami implementującymi interfejs Runnableto bardzo ważna część programowania w Javie i warto się na tym skupić.

18. Użycie zabronionej metody readLine() klasy java.io.DataInputStream

W Javie w wersji 1.0 do odczytania ciągu tekstowego konieczne było użycie metody readLine()klasowej java.io.DataInputStream. Wersja Java 1.1 dodała cały zestaw klas we/wy, aby zapewnić operacje we/wy dla tekstu: Readeri Writer. Zatem od wersji 1.1 do odczytania linijki tekstu należy zastosować metodę readLine()klasową java.io.BufferedReader. Uczniowie mogą nie być świadomi tej zmiany, zwłaszcza jeśli uczyli się na podstawie starszych książek. (w przybliżeniu tłumaczenie: właściwie już nieaktualne. Jest mało prawdopodobne, że ktokolwiek będzie teraz uczyć się z książek, które miały 10 lat). Stara metoda readLine()pozostaje w JDK, ale została uznana za nielegalną, co często dezorientuje uczniów. Musisz zrozumieć, że używanie metody readLine()klasowej java.io.DataInputStreamnie jest złe, jest po prostu przestarzałe. Musisz użyć klasy BufferedReader. Zły przykład:
public class LineReader {
    private DataInputStream dis;

    public LineReader(InputStream is) {
        dis = new DataInputStream(is);
    }

    public String getLine() {
        String ret = null;

        try {
          ret = dis.readLine();  // Неправильно! Запрещено.
        } catch (IOException ie) { }

        return ret;
    }
}
Poprawiony przykład:
public class LineReader {
    private BufferedReader br;

    public LineReader(InputStream is) {
        br = new BufferedReader(new InputStreamReader(is));
    }

    public String getLine() {
        String ret = null;

        try {
          ret = br.readLine();
        } catch (IOException ie) { }

        return ret;
    }
}
Istnieją inne zabronione metody w wersjach późniejszych niż 1.0, ale ta jest najczęstsza.

19. Używanie double jako float

Podobnie jak większość innych języków, Java obsługuje operacje na liczbach zmiennoprzecinkowych (liczbach ułamkowych). Java ma 2 typy pierwotne dla liczb zmiennoprzecinkowych: doubledla liczb z 64-bitową precyzją zgodnie ze standardem IEEE i floatdla liczb z 32-bitową precyzją zgodnie ze standardem IEEE. Trudność polega na tym, że przy użyciu liczb dziesiętnych, takich jak 1,75, 12,9e17 lub -0,00003, kompilator przypisuje je do typu double. Java nie wykonuje rzutowania typów w operacjach, w których może wystąpić utrata precyzji. To rzutowanie typu musi być wykonane przez programistę. Na przykład Java nie pozwoli na przypisanie wartości typu do intzmiennej typu bytebez rzutowania typu, jak pokazano w poniższym przykładzie.
byte byteValue1 = 17; /* неправильно! */
byte byteValue2 = (byte)19; /* правильно */
Ponieważ liczby ułamkowe są reprezentowane przez typ double, a przypisanie doubledo zmiennej typu floatmoże spowodować utratę precyzji, kompilator będzie narzekał na każdą próbę użycia liczb ułamkowych jako float. Zatem użycie poniższych przypisań zapobiegnie kompilacji klasy.
float realValue1 = -1.7;          /* неправильно! */
float realValue2 = (float)(-1.9); /* правильно */
To przypisanie działałoby w C lub C++, ale w Javie jest znacznie bardziej rygorystyczne. Istnieją 3 sposoby na pozbycie się tego błędu. Możesz użyć typu doublezamiast float. To najprostsze rozwiązanie. Tak naprawdę nie ma sensu używać arytmetyki 32-bitowej zamiast 64-bitowej; różnica w szybkości i tak jest zjadana przez JVM (poza tym w nowoczesnych procesorach wszystkie liczby ułamkowe są konwertowane do formatu procesora 80-bitowego zarejestrować się przed jakąkolwiek operacją). Jedyną zaletą ich stosowania floatjest to, że zajmują mniej pamięci, co jest przydatne podczas pracy z dużą liczbą zmiennych ułamkowych. Możesz użyć modyfikatora typu liczbowego, aby poinformować kompilator, jak przechowywać liczbę. Modyfikator typu float - 'f'. Zatem kompilator przypisze typ 1.75 do double, i 1.75f - float. Na przykład:
float realValue1 = 1.7;    /* неправильно! */
float realValue2 = 1.9f;   /* правильно */
Możesz użyć jawnego rzutowania typu. Jest to najmniej elegancki sposób, ale jest przydatny podczas konwertowania zmiennej typu doublena typ float. Przykład:
float realValue1 = 1.7f;
double realValue2 = 1.9;
realValue1 = (float)realValue2;
Więcej o liczbach zmiennoprzecinkowych możesz przeczytać tutaj i tutaj.

-- komentarz tłumacza --
To wszystko.
W przykładzie 10 faktycznie został popełniony błąd 9. Zauważyłem to od razu, ale zapomniałem napisać notatkę. ale nie poprawiał go, żeby nie było rozbieżności z oryginałem.

Autor: A.Grasoff™ Link do źródła: Błędy początkujących programistów Java
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION