JavaRush /Java блог /Random UA /Помилки початківців java-програмістів. Частина 1
articles
15 рівень

Помилки початківців java-програмістів. Частина 1

Стаття з групи Random UA

1. Ім'я класу відрізняється від імені файлу, у якому він зберігається

Всі використовувані мною середовища java, включаючи Javasoft JDKs, припускають, що вихідний код класу з модифікатором public зберігається у файлі з таким самим ім'ям, як і ім'я класу, і розширенням .java. Недотримання цієї угоди може спричинити багато проблем, які виявляться під час компіляції.
Помилки початківців java-програмістів.  Частина 1 - 1
Студенти-початківці (програмісти) часто забувають про цю угоду, і, наприклад, задають ім'я файлу відповідно до завдання: Lab6.java. Помилковий приклад: Ім'я файлуLab6.java
public class Airplane extends Vehicle
  Seat pilot;
  public Airplane() {
    pilot = new Seat();
  }
}
Виправлений приклад: Ім'я файлуAirplane.java
public class Airplane extends Vehicle
  Seat pilot;
  public Airplane() {
    pilot = new Seat();
  }
}
Зауважте:передбачається, що ім'я класу починається з великої літери. В операційних системах, які враховують регістр символів в іменах файлів, можуть виникнути додаткові проблеми, особливо у студентів, які вивчають Java під Unix, і звикли до систем іменування файлів в DOS. Клас MotorVehicleповинен зберігатися у файлі MotorVehicle.java, але не у файлі motorvehicle.java.

2. Порівняння за допомогою==

У рядку Java - це об'єкти класу java.lang.String. Оператор ==, що застосовується до об'єктів, перевіряє на рівність посилання на об'єкти! Іноді студенти не розуміють семантики оператора ==та намагаються застосувати його для порівняння рядків. Помилковий приклад:
// проверим, дорівнює ли первый аргумент "-a"
if (args[0] == "-a") {
    optionsAll = true;
}
Правильний спосіб порівняння 2х рядків на рівність - це використання методу equals()класу java.lang.String. Він повертає true, якщо рядки однакової довжини і складаються з тих самих символів. (Прим. перекл.: взагалі це не гарантує рівність. Насправді, equalsперевіряє, чи рівні посимвольно 2 рядки) Виправлений приклад:
//  проверим, дорівнює ли первый аргумент "-a"
if ("-a".equals(args[0])) {
    optionsAll = true;
}
Ця помилка - безглузда, тому що насправді Java код виходить синтаксично правильним, а в результаті працює не так як потрібно. Деякі студенти також намагаються застосовувати оператори порівняння >і <=замість методу compareTo()класу java.lang.String. Цю помилку знайти простіше, оскільки вона викликає помилки на етапі компіляції.

3. Забув проініціалізувати об'єкти, які є елементами масиву.

У Java масив об'єктів - це насправді масив посилань на об'єкти. Створення масиву - це просто створення набору посилань, які ні на що не вказують (тобто рівних null). Щоб насправді створити повноцінний масив об'єктів, необхідно проініціалізувати кожен елемент масиву. Багато студентів не розуміють цього; вони вважають, що створюючи масив об'єктів, вони автоматично створюють самі об'єкти. (У більшості випадків студенти приносять цю концепцію з C++, де створення масиву об'єктів призводить до створення самих об'єктів шляхом виклику їх конструктора за замовчуванням). У прикладі нижче студент хоче створити 3 об'єкти класу StringBuffer. Код буде відкомпільовано без помилок, але в останньому рядку відбудеться виняток NullPointerException, де відбувається звернення до об'єкта, що не існує. Помилковий приклад:
// Создаем массив из StringBuffer
StringBuffer [] myTempBuffers;
myTempBuffers = new StringBuffer[3];
myTempBuffers[0].add(data);
Щоб не допускати цієї помилки, необхідно не забувати проініціалізувати елементи масиву. Виправлений приклад:
// Создаем массив из StringBuffer и инициализируем элементы
StringBuffer [] myTempBuffers;
myTempBuffers = new StringBuffer[3];
for (int ix = 0; ix < myTempBuffers.length; ix++)
     myTempBuffers[ix] = new StringBuffer();

myTempBuffers[0].add(data);

4. Поміщення в один файл відразу кількох класів із модифікаторомpublic

Файли з вихідним java-кодом певним чином пов'язані з класами, які у цих файлух. Зв'язок можна охарактеризувати так: Будь-який Java-клас зберігається лише в одному файлі. У будь-який файл з вихідним кодом можна помістити трохи більше 1-го класу з модифікатором public. Якщо у файлі з вихідним кодом є клас з модифікатором public, ім'я файлу та ім'я класу повинні бути строго однаковими (прим. перекл.: до регістру, див. п.1) Іноді студенти забувають про 2-е правило, що призводить до помилок на етапі компіляції. Повідомлення про помилку для 2-го та 3-го правила буде однаковим (у чому власне і полягає складність розпізнавання цієї помилки).

5. Підміна поля класу локальної змінної.

Java дозволяє оголошувати всередині методу змінні, ім'я якого збігається з полями класу. У такому разі перевага буде віддана локальним змінним, і вони використовуватимуться замість полів. Компілятор видасть помилку, якщо змінні з однаковими іменами будуть різними типами. Якщо ж вони однакові типи, помилки компіляції не буде, і будуть незрозумілі причини неправильної роботи програми. Помилковий приклад:
public class Point3 {
    int i = 0;
    int j = 0;
    int k = 0;

    public boolean hits(Point[] p2list) {
      for(int i = 0; i < p2list.length; i++) {
        Point p2 = p2list[i];
        if (p2.x == i && p2.y == j)
          return true;
      }
      return false;
    }
}
Існує кілька способів, як виправити цю помилку. Найпростіший - звертатися до полів класу за допомогою неявного покажчика this: this.ім'я_поля. Найкращий спосіб - це перейменувати поле класу або локальну змінну, тоді підміни не станеться. (прим. перекл.: другий спосіб - не наш спосіб. До того ж він не гарантує, що я коли-небудь випадково не підміню поле змінної. Ще більша проблема виникне при успадкування, коли я взагалі не бачу, які поля є у класу ) Виправлений приклад:
// One way to fix the problem
  int i = 0;
  int j = 0;
  int k = 0;

  public boolean hits(Point[] p2list) {
    for(int i = 0; i < p2list.length; i++) {
      Point p2 = p2list[i];
      if (p2.x == this.i && p2.y == this.j)
        return true;
    }
    return false;
  }

  // *****************************
  // Лучший способ
  int x = 0;
  int y = 0;
  int z = 0;

  public boolean hits(Point[] p2list) {
    for(int i = 0; i < p2list.length; i++) {
      Point p2 = p2list[i];
      if (p2.x == x && p2.y == y)
        return true;
    }
    return false;
  }
Ще одне можливе місце появи цієї помилки - завдання імені параметра методу так само, як і ім'я поля класу. Це добре виглядає у конструкторах, але для звичайних методів не підходить.

прим. перев.

трохи сумбурно, але сенс такий

public class Test {
   private int param = 0;

   public Test(int param) {
      this.param = param;
   }
}

тобто в конструкторі все виглядає красиво, але для звичайних методів застосовувати це не слід.

6. Забув викликати конструктор батька (суперкласу)

Коли клас розширює інший клас, кожен конструктор підкласу повинен викликати якийсь конструктор суперкласу. Зазвичай це досягається викликом конструктора суперкласу методом super(x), вміщеним у першому рядку конструктора. Якщо в першому рядку конструктора немає виклику super(x), компілятор самостійно вставляє цей виклик, але без параметрів: super(). (прим. перекл.: х...се, а я і не знав) Іноді студенти забувають про цю вимогу. Зазвичай це не є проблемою: виклик конструктора суперкласу вставляється компілятором і працює відмінно. Однак якщо суперклас не має конструктора за умовчанням (прим. перекл.: тобто конструктора без параметрів), то компілятор видасть помилку. У прикладі нижче всі конструктори суперкласу java.io.Fileмають 1 або 2 параметри: Помилковий приклад:
public class JavaClassFile extends File {
    String classname;
    public JavaClassFile(String cl) {
        classname = cl;
    }
}
Вирішенням проблеми є вставка явного виклику правильного конструктора суперкласу .
public class JavaClassFile extends File {
    String classname;
    public JavaClassFile(String cl) {
        super(cl + ".class");
        classname = cl;
    }
}
Неприємніша ситуація виникає, коли у суперкласу є конструктор за замовчуванням, але він не повністю ініціалізує об'єкт. У такому разі код скомпілюється, але результат роботи програми може бути неправильним або може виникнути виняток.

7. Неправильне перехоплення винятків

Система обробки винятків Java досить потужна, але важка для розуміння новачками. Студенти, які добре володіють C++ або Ada, зазвичай не мають труднощів, на відміну від програмістів C і Fortran. Нижче наведено приклади деяких поширених помилок. У цьому прикладі для виключення не вказано ім'я. На цю помилку вкаже компілятор на етапі компіляції, тому її легко виправити самостійно. Помилковий приклад:
try {
    stream1 = new FileInputStream("data.txt");
} catch (IOException) {
    message("Could not open data.txt");
}
Виправлений приклад:
try {
   stream1 = new FileInputStream("data.txt");
} catch (IOException ie) {
   message("Could not open data.txt: " + ie);
}
Порядок розташування блоків catchвизначає черговість перехоплення винятків. Потрібно враховувати, що кожен такий блок перехопить усі винятки зазначеного класу чи будь-якого його підкласу. Якщо це не врахувати, можна отримати недосяжний блок catch, потім вкаже компілятор. У прикладі нижче SocketExceptionє підклас IOException. Помилковий приклад:
try {
    serviceSocket.setSoTimeout(1000);
    newsock = serviceSocket.accept();
} catch (IOException ie) {
    message("Error accepting connection.");
} catch (SocketException se) {
    message("Error setting time-out.");
}
Виправлений приклад:
try {
    serviceSocket.setSoTimeout(1000);
    newsock = serviceSocket.accept();
} catch (SocketException se) {
    message("Error setting time-out.");
} catch (IOException ie) {
    message("Error accepting connection.");
}
Якщо коді можлива поява виключення, яке перехоплюється жодним блоком try-catch, це виняток слід оголосити в заголовку методу. (Для винятків – підкласів класу RuntimeExceptionце не обов'язково). Студенти іноді забувають про те, що виклик методу може породжувати виняток. Найпростіше це виправити, поміщаючи виклик методу в блок try-catch. Помилковий приклад:
public void waitFor(int sec) {
    Thread.sleep(sec * 1000);
}
Виправлений приклад:
public void waitFor(int sec) throws InterruptedException {
    Thread.sleep(sec * 1000);
}

8. Метод доступу має типvoid

Це дуже проста помилка. Студент створює метод для доступу до змінної, але вказує, що метод нічого не повертає (поміщає модифікатор voidу загогловок методу). Щоб виправити цю помилку, потрібно вказати правильний тип значення, що повертається. Помилковий приклад:
public class Line {
    private Point start, end;
    public void getStart() {
      return start;
    }
}
Виправлений приклад:
public class Line {
    private Point start, end;
    public Point getStart() {
      return start;
    }
}
Вказівка ​​неправильного типу, що повертається, породжує цілий клас помилок. Зазвичай компілятор розпізнає ці помилки та повідомляє про них, тому студенти можуть самостійно їх виправити. Автор: А.Грасоff™ Читати продовження Посилання на першоджерело: Помилки java-програмістів-початківців
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ