== вместо equals(); обращение к нестатическим полям из статических методов; путаница между передачей по ссылке и по значению; игнорирование соглашений об именовании; непонимание работы String и String Pool; неправильная обработка исключений; непонимание приоритета операторов; пропуск break в switch. Ниже разбираем каждую с примерами.
Привет! Сегодня мы рассмотрим список из 8 распространенных ошибок начинающих (да и не только) Java-разработчиков. В Сети ты найдешь немало таких подборок: многие из них похожи друг на друга.Кратко
- Сравнивать объекты через
==вместоequals():==сравнивает ссылки, а не содержимое. - Обращаться к нестатическим полям из статических методов: так нельзя, будет ошибка компиляции.
- Путать передачу по ссылке (объекты) и по значению (примитивы).
- Игнорировать соглашения об именовании и правила читаемости кода.
- Не понимать String Pool:
new String()и строковый литерал это разные объекты. - Неправильно обрабатывать исключения (антипаттерны).
- Не учитывать приоритет операторов (например,
==выше, чем++). - Забывать
breakвswitch: тогда выполняются все case подряд.
| Ошибка | Почему это ошибка | Как правильно |
|---|---|---|
Сравнение объектов через == | == сравнивает ссылки, а не содержимое | использовать equals() и переопределить его |
| Нестатические поля в статическом методе | у статического метода нет объекта, а поля принадлежат объекту | сделать поле статическим или обращаться через объект |
| Путаница по ссылке / по значению | объекты передаются по ссылке, примитивы по значению | помнить: изменения объекта в методе видны снаружи, примитива нет |
| Игнорирование соглашений об именовании | код тяжело читать и поддерживать | следовать стандартам именования Java |
| Непонимание String Pool | new String() создает новый объект, литерал берется из пула | сравнивать строки через equals(), а не == |
| Неправильная обработка исключений | антипаттерны маскируют настоящую причину ошибки | обрабатывать осмысленно, не «глотать» исключения |
| Игнорирование приоритета операторов | результат вычисляется не так, как ожидаешь | помнить приоритет (== выше ++ и т.д.) |
Пропуск break в switch | без break выполняются все следующие case | ставить break в конце каждого case |
Разбор каждой ошибки
Сравнение объектов с помощью
==.Оператор
==сравнивает ссылки объектов.Ссылки указывают на адреса в памяти, и если они находятся по разным адресам, сравнение через
==будет возвращать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(). Ты их легко найдешь в Интернете, но наши крутые ученики уже сделали статью на эту тему.Использование нестатических переменных в статических методах (и наоборот).
Если ты хоть раз видел надпись «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);//а так можно! } }Непонимание того, как передаются параметры в методы: по ссылке или по значению.
Объекты и примитивы передаются в методы в качестве параметров по-разному: первые по ссылке, вторые по значению.
Новичкам часто бывает сложно понять эту концепцию, в результате их код ведет себя неожиданно:
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++; } }Если в этом примере ты точно не знаешь, какое число увеличится, а какое нет (обычное число или возраст кота), тогда перечитай еще раз нашу лекцию на эту тему.
Игнорирование правил написания кода.
И это касается не только соблюдения тех или иных «технических» принципов, но и банальных соглашений об именовании.
Все эти правила «как надо называть переменные», «как надо писать названия методов» были придуманы не просто так. Это и правда сильно влияет на читаемость кода.
Ведь код не всегда будет только твоим. Ты можешь перевестись на другой проект в компании, и он перейдет по наследству к твоим коллегам, которые будут явно не в восторге, получив в работу что-то вроде этого:
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; }Твой код может быть гениальным с точки зрения производительности, но если его невозможно прочитать и понять как он, собственно, работает, цена его, увы, невелика.
Придерживайся стандартов написания, и твой код, даже далекий от идеала, хотя бы прочтут более опытные товарищи и подскажут, что с технической точки зрения в нем можно улучшить :)
Непонимание работы класса
Stringpublic class Main { public static void main(String[] args) { String s1 = "Я изучаю Java"; String s2 = new String("Я изучаю Java"); System.out.println(s1 == s2); } }Если ты не знаешь, почему этот код выводит
false, знания явно нужно подтянуть:)Новички часто не знают что такое
String Poolи как он работает.Как следствие, не до конца ясно, как правильно сравнивать строки в своем коде. В одной из наших лекций мы подробно рассматривали эту тему
Неправильная работа с исключениями.
Это свойственно не только новичкам, но и опытным разработчикам. Причин несколько.
Во-первых, универсального рецепта не существует. Ошибки в программе бывают разные, сценарии их обработки, соответственно, тоже. Во-вторых, не все понимают структуру
stackTrace, а антипаттернов обработки ошибок очень много, и каждый из них «неправилен» по-своему. Так что вариантов сделать неправильно здесь намного больше, чем где бы то ни было.Распространенные антипаттерны приведены здесь:
Неполное понимание работы операторов (арифметических, логических и других).
Простой пример. Сможешь сходу сказать, что выведет этот код?
public class Main { public static void main(String[] args) { int i = 6; System.out.println(7 == i++); } }Если ты ответил неправильно или наугад, значит, в этой области пока есть пробелы:)
Код выведет
false, потому что приоритет у оператора сравнения==выше, чем у постфиксного инкремента++. Поэтому сначала будет выполнено сравнение7 == i, и только потом операцияi++.По этой теме, кстати, у нас тоже была подробная лекция. Вот ссылка, если пропустил.
Пропуск слова
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).
Читайте также
- Stack Trace: как читать трассировку стека при исключениях (ошибка №6).
- Побитовые операции и приоритет: таблица приоритета операторов (ошибка №7).
Вопросы и ответы
Почему нельзя сравнивать объекты через == в 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 происходит после.