Восемь самых частых ошибок начинающих Java-программистов: сравнение объектов через == вместо equals(); обращение к нестатическим полям из статических методов; путаница между передачей по ссылке и по значению; игнорирование соглашений об именовании; непонимание работы String и String Pool; неправильная обработка исключений; непонимание приоритета операторов; пропуск break в switch. Ниже разбираем каждую с примерами. Привет! Сегодня мы рассмотрим список из 8 распространенных ошибок начинающих (да и не только) Java-разработчиков. В Сети ты найдешь немало таких подборок: многие из них похожи друг на друга.

Кратко

  • Сравнивать объекты через == вместо equals(): == сравнивает ссылки, а не содержимое.
  • Обращаться к нестатическим полям из статических методов: так нельзя, будет ошибка компиляции.
  • Путать передачу по ссылке (объекты) и по значению (примитивы).
  • Игнорировать соглашения об именовании и правила читаемости кода.
  • Не понимать String Pool: new String() и строковый литерал это разные объекты.
  • Неправильно обрабатывать исключения (антипаттерны).
  • Не учитывать приоритет операторов (например, == выше, чем ++).
  • Забывать break в switch: тогда выполняются все case подряд.
Когда мы составляли этот список, ориентировались на один критерий: допускали ли эти ошибки сами во время учебы и работы :) Они расставлены не по приоритету и одинаково важны для понимания и запоминания. Все восемь ошибок и способы их избежать удобно свести в таблицу:
ОшибкаПочему это ошибкаКак правильно
Сравнение объектов через ==== сравнивает ссылки, а не содержимоеиспользовать equals() и переопределить его
Нестатические поля в статическом методеу статического метода нет объекта, а поля принадлежат объектусделать поле статическим или обращаться через объект
Путаница по ссылке / по значениюобъекты передаются по ссылке, примитивы по значениюпомнить: изменения объекта в методе видны снаружи, примитива нет
Игнорирование соглашений об именованиикод тяжело читать и поддерживатьследовать стандартам именования Java
Непонимание String Poolnew String() создает новый объект, литерал берется из пуласравнивать строки через equals(), а не ==
Неправильная обработка исключенийантипаттерны маскируют настоящую причину ошибкиобрабатывать осмысленно, не «глотать» исключения
Игнорирование приоритета оператороврезультат вычисляется не так, как ожидаешьпомнить приоритет (== выше ++ и т.д.)
Пропуск break в switchбез break выполняются все следующие caseставить break в конце каждого case

Разбор каждой ошибки

  1. Сравнение объектов с помощью ==.

    Оператор == сравнивает ссылки объектов.

    Ссылки указывают на адреса в памяти, и если они находятся по разным адресам, сравнение через == будет возвращать false.

    
    public class Car {
    
       String model;
       int maxSpeed;
       int yearOfManufacture;
    
       public Car(String model, int maxSpeed, int yearOfManufacture) {
           this.model = model;
           this.maxSpeed = maxSpeed;
           this.yearOfManufacture = yearOfManufacture;
       }
    
       public static void main(String[] args) {
           Car ferrari = new Car("Ferrari 360 Spider", 280, 1996);
           Car ferrariTwin = new Car("Ferrari 360 Spider", 280, 1996);
           System.out.println(ferrari == ferrariTwin);
       }
    }
    

    Для сравнения объектов в классе Object есть специальный метод, equals(). Его реализация по умолчанию, прямо скажем, так себе:

    
    public boolean equals(Object obj) {
       return (this == obj);
    }
    

    В самом классе Object логика метода equals() реализована как сравнение двух ссылок. В свою очередь, для корректного сравнения объектов тебе необходимо переопределить этот метод в соответствии с теми критериями, которые важны в конкретной программе для конкретных объектов. Критерии равенства определяешь ты сам.

    Единственное, о чем не стоит забывать, это список требований для правильного переопределения equals(). Ты их легко найдешь в Интернете, но наши крутые ученики уже сделали статью на эту тему.

  2. Использование нестатических переменных в статических методах (и наоборот).

    Если ты хоть раз видел надпись «Non-static variable x cannot be referenced from a static context», добро пожаловать в клуб :)

    Статические методы не имеют доступа к нестатическим переменным класса.

    Это логично: ведь статический метод можно вызвать, не создавая объект класса, а все переменные-поля принадлежат конкретным объектам. В этом и заключается противоречие, приводящее к ошибке.

    Наоборот, кстати, можно: использовать статические переменные в нестатических методах допустимо:

    
    public class Main {
    
       public int x = 10;
    
       public static int staticX = 100;
    
       public static void main(String[] args) {
    
           System.out.println(x);//ошибка компиляции, так нельзя!
       }
    
       public void printX() {
    
           System.out.println(staticX);//а так можно!
       }
    }
    
  3. Непонимание того, как передаются параметры в методы: по ссылке или по значению.

    Объекты и примитивы передаются в методы в качестве параметров по-разному: первые по ссылке, вторые по значению.

    Новичкам часто бывает сложно понять эту концепцию, в результате их код ведет себя неожиданно:

    
    public class Main {
    
       public static void main(String[] args) {
    
           int x = 7;
           incrementNumber(x);
           System.out.println(x);
    
           Cat cat = new Cat(7);
           catLevelUp(cat);
           System.out.println(cat.getAge());
    
       }
    
       public static void catLevelUp(Cat cat) {
    
           cat.setAge(cat.getAge()+1);
       }
    
       public static void incrementNumber(int x) {
           x++;
       }
    }
    

    Если в этом примере ты точно не знаешь, какое число увеличится, а какое нет (обычное число или возраст кота), тогда перечитай еще раз нашу лекцию на эту тему.

  4. Игнорирование правил написания кода.

    И это касается не только соблюдения тех или иных «технических» принципов, но и банальных соглашений об именовании.

    Все эти правила «как надо называть переменные», «как надо писать названия методов» были придуманы не просто так. Это и правда сильно влияет на читаемость кода.

    Ведь код не всегда будет только твоим. Ты можешь перевестись на другой проект в компании, и он перейдет по наследству к твоим коллегам, которые будут явно не в восторге, получив в работу что-то вроде этого:

    
    public class Cat {
    
       private int S_O_M_E_T_H_I_N_G = 7;
       public String striiiiiiiiiiiiiing;
       protected double X3_4TO_ET0_TAK0E = 3.14;
       boolean random = Math.random() > 0.5;
    
    }
    

    Твой код может быть гениальным с точки зрения производительности, но если его невозможно прочитать и понять как он, собственно, работает, цена его, увы, невелика.

    Придерживайся стандартов написания, и твой код, даже далекий от идеала, хотя бы прочтут более опытные товарищи и подскажут, что с технической точки зрения в нем можно улучшить :)

  5. Непонимание работы класса String

    
    public class Main {
    
       public static void main(String[] args) {
    
           String s1 = "Я изучаю Java";
           String s2 = new String("Я изучаю Java");
    
           System.out.println(s1 == s2);
       }
    }
    

    Если ты не знаешь, почему этот код выводит false, знания явно нужно подтянуть:)

    Новички часто не знают что такое String Pool и как он работает.

    Как следствие, не до конца ясно, как правильно сравнивать строки в своем коде. В одной из наших лекций мы подробно рассматривали эту тему

  6. Неправильная работа с исключениями.

    Это свойственно не только новичкам, но и опытным разработчикам. Причин несколько.

    Во-первых, универсального рецепта не существует. Ошибки в программе бывают разные, сценарии их обработки, соответственно, тоже. Во-вторых, не все понимают структуру stackTrace, а антипаттернов обработки ошибок очень много, и каждый из них «неправилен» по-своему. Так что вариантов сделать неправильно здесь намного больше, чем где бы то ни было.

    Распространенные антипаттерны приведены здесь:

  7. Неполное понимание работы операторов (арифметических, логических и других).

    Непонимание приоритета операторов приводит к неожиданным результатам

    Простой пример. Сможешь сходу сказать, что выведет этот код?

    
    public class Main {
    
       public static void main(String[] args) {
    
           int i = 6;
           System.out.println(7 == i++);
       }
    }
    

    Если ты ответил неправильно или наугад, значит, в этой области пока есть пробелы:)

    Код выведет false, потому что приоритет у оператора сравнения == выше, чем у постфиксного инкремента ++. Поэтому сначала будет выполнено сравнение 7 == i, и только потом операция i++.

    По этой теме, кстати, у нас тоже была подробная лекция. Вот ссылка, если пропустил.

  8. Пропуск слова break в операторе switch.

    Эту ошибку, вероятно, допускали многие из читателей!)

    
    public class Main {
    
       public static void main(String[] args) {
    
           int i = 1;
    
           switch (i) {
    
               case 1: {
                   System.out.println("Число равно 1");
               }
               case 2: {
                   System.out.println("Число равно 2");
               }
               case 3: {
                   System.out.println("Число равно 3");
               }
           }
       }
    }
    

    В результате на них обрушивается водопад из всех возможных вариантов:

    Вывод:

    
    Число равно 1
    Число равно 2
    Число равно 3
    

    Оператор break прерывает работу оператора switch в момент, когда отработал один из вариантов. Не стоит о нем забывать, иначе результат может быть неожиданным :) Подробнее про switch можно почитать в документации Oracle (The switch Statement).

Читайте также

Вопросы и ответы

Почему нельзя сравнивать объекты через == в Java?

Оператор == сравнивает ссылки (адреса в памяти), а не содержимое объектов. Два разных объекта с одинаковыми полями лежат по разным адресам, поэтому == вернет false. Для сравнения по содержимому используют equals().

Почему нельзя использовать нестатическое поле в статическом методе?

Статический метод можно вызвать без создания объекта, а нестатические поля принадлежат конкретному объекту. Поэтому обратиться к ним из статического метода нельзя, будет ошибка «Non-static variable cannot be referenced from a static context».

Как в Java передаются параметры: по ссылке или по значению?

Примитивы передаются по значению (копия), а объекты по ссылке. Поэтому изменение объекта внутри метода видно снаружи, а изменение примитива нет.

Почему s1 == s2 возвращает false для строк?

Потому что new String() создает новый объект в памяти, а строковый литерал берется из String Pool. Это разные объекты, и == (сравнение ссылок) дает false. Сравнивать строки нужно через equals().

Что будет, если забыть break в switch?

Выполнится подходящий case и все case после него, пока не встретится break или не закончится switch. Это называется проваливанием (fall-through). Чтобы этого избежать, ставят break в конце каждого case.

Что выведет 7 == i++ при i = 6?

false. Приоритет оператора сравнения == выше, чем у постфиксного инкремента, поэтому сначала сравнивается 7 == 6 (false), а увеличение i происходит после.