Хабрахабр

[Из песочницы] Подводные камни Java

Здравствуйте. Хочу представить вашему вниманию небольшую статью.

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

Подводные камни Java

Все языки программирования имеют свои достоинства и недостатки. Это обусловлено многими причинами. Язык Java не исключение. Я попытался собрать некоторые очевидные и не очевидные трудности, с которыми сталкивается начинающий программист Java. Уверен, что опытные программисты тоже найдут в моей статье что-то полезного. Практика, внимательность и полученный опыт программирования, помогут избавить вас от многих ошибок. Но некоторые ошибки и трудности лучше рассмотреть заранее. Я приведу несколько примеров с кодом и объяснениями. Многие объяснения вам станут понятны из комментариев к коду. Практика дает очень многое, так как некоторые правила не столь очевидны. Некоторые лежат на поверхности, некоторые скрыты в библиотеках языка или в виртуальной машине java. Помните, что java это не только язык программирования с набором библиотек, это еще и виртуальная машина java.

Для написания статьи использовалась java 8. Для статьи я специально написал работающий код с подробными комментариями. Для тестирования код java помещен в отдельные пакеты.

Пример: «package underwaterRocks.simple;»

С какими трудностями сталкиваются начинающие?

Опечатки

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

Пример кода:

Файл: «Simple.java»

/*
; после условия и блок */
package underwaterRocks.simple; /** * * @author Ar20L80 */
public class Simple }
}

Объяснение: «Точка с запятой означает конец оператора. В данном случае; — это конец пустого оператора. Это логическая ошибка. Такую ошибку бывает трудно обнаружить.

Условие if(ival>0); в данном случае не имеет смысла. Компилятор сочтет, что всё правильно. Потому как означает: если ival больше нуля, ничего не делать и продолжить.»

Присвоение в условии вместо сравнения

В условии присвоение переменной.

Это не ошибка, но использование такого приема должно быть оправдано.

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

В данном коде if(myBool = true) означает :«Присвоить переменной myBool значение true,
если выражение истинно, выполнить условие следующее за скобками.»

И System.out.println(myBool); будет выполнено всегда, независимо от условия. В данном коде условие будет всегда истинно.

== — это сравнение на равенство.
= — это присвоение, вы можете проговорить a = 10; как: «а присвоить значение 10».

Вы можете сравнить так: (0 == a) или (5 == a)
Если вы забудете один знак равенства, например так (0 = a) или (5 = a), то компилятор сообщит вам об ошибке. Условие в скобках возвращает логическое значение.
Не важно в каком порядке вы запишите. Это более наглядно. Вы присваиваете значение, а не сравниваете.
Вы также можете записать в удобочитаемой форме какой-то интервал.
Например: вам нужно написать: a больше 5 и меньше 10.
Вы пишите так: (a>4 && a<10), но с таким же успехом вы можете написать: (4<a && a<10),
теперь вы видите, что a находится в интервале между 4 и 10, исключая эти значения. Сразу видно, что а находится в интервале между 4 и 10, исключая эти значения.

Пример в коде(интервал ]3,9[ ):
if(3<a&&a<9) выполнить;

Логическая ошибка

if(условие){} if(условие){} else{} — else относится к ближайшему if.

Часто это бывает причиной ошибок начинающих.

Инициализация переменных

Рассмотрим инициализацию переменных примитивного типа.

Примитивы (byte, short, int, long, char, float, double, boolean).

Начальные значения.

byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char '\u0000'
String (or any object) null
boolean false (зависит от jvm)

Примечание:

Локальные переменные немного отличаются;
Компилятор никогда не присваивает значение по умолчанию неинициализированной локальной переменной.

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

Доступ к неинициализированной локальной переменной приведет к ошибке времени компиляции.

Подтверждение этого примечания в коде:

Файл: «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); // ошибка. Локальная переменная не инициализирована }
}

Диапазоны значений:

byte (целые числа, 1 байт, [-128, 127])
short (целые числа, 2 байта, [-32768, 32767])
int (целые числа, 4 байта, [-2147483648, 2147483647])
long (целые числа, 8 байт, [-922372036854775808,922372036854775807])
float (вещественные числа, 4 байта)
double (вещественные числа, 8 байт)
char (символ Unicode, 2 байта, [0, 65536])
boolean (значение истина/ложь, используется int, зависит от JVM)

Документация Oracle >>

Потому как, это целочисленный литерал типа int.
Правильная инициализация литералом типа long: 922372036854775807L; Попытаемся инициализировать переменную типа long числом: 922372036854775807.
У нас ничего не выйдет.

Пример кода:

Файл: «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; // так правильно }
}

На что следует обращать внимание при инициализации переменной.

На то, что переменная инициализируется литералом определенного типа. На диапазон значений переменной данного типа. На совместимость типов. На явное и неявное приведение типов.

При использовании оболочек типа Integer, следует обратить внимание на авто упаковку и авто распаковку данных типов.

Неправильное сравнение double

Рассмотрим тип double.

Пример кода:

Файл: «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); } }

Тип double удобен там, где не нужна высокая точность. Для финансовых операций этот тип не годится. Хотя некоторые, не очень честные компании, использую тип double, для округления в нужную им сторону. Для финансовых операций используется класс BigDecimal в финансовых расчётах, так как вещественные примитивные типы не годятся для этой цели по причинам потери точности и ошибках результатах округления. Однако, более точные результаты дает использование класса BigInteger.

Конструктор класса

Конструктор класса совпадает с именем класса и ничего не возвращает, даже void.

Пример кода:

Файл: «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(); // вызов обычного метода }
}

Как мы видим в коде два метода с одинаковыми именами: MyConstructor() и MyConstructor(). Один из методов ничего не возвращает. Это и есть конструктор нашего класса. Другой метод с void — это обычный метод класса. В случае, когда вы не создали конструктор или создали, по вашему мнению конструктор класса с void, то компилятор создаст конструктор по умолчанию и вы будете удивлены, почему ваш конструктор не работает.

Деление на ноль

Как вы думаете, какой будет результат выполнения такого кода.

Файл: «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"); } } }

Выполнение кода выведет:

Infinity
Infinity
true
true

Деление целого типа на ноль даст ArithmeticException.

Double определена константа POSITIVE_INFINITY; В классе java.lang.

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

Она преобразуется в строку равную Infinity.

Порядок инициализации

Файл: «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"); } }

Вначале выполняются все статические блоки, затем блоки инициализации, затем конструктор класса.

Выведется: «123 Конструктор»

Выводы

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

Буду рад вашим комментариям, замечаниям, предложениям, пожеланиям. Надеюсь, статья вам понравилась и оказалась полезной. Уже вижу, что публикация не полная. Постараюсь дописать. Вернее дополнение следует. Продолжение следует.

Статья распространяется на условиях GNU FDL. Лицензия
«Я разрешаю читать эту статью, разрешаю использовать эту статью, позволяю улучшать эту статью».

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть