© 2008 Наталия Македа
Все материалы блога защищены авторским правом. Любая перепечатка или использование материалов этого блога в коммерческих целях возможна лишь с письменного согласия автора. При некоммерческом использовании ссылка на блог обязательна.

суббота, 3 мая 2008 г.

Сложение и вычитание, NaN (Выпуск 6)

Сложение и вычитание (+ и -)

За сложение и вычитание отвечают соответсвенно операторы + и -. Они применимы для операндов любых численных типов. Помимо этого, операция + применяется и для операндов типа String. Когда один из операндов типа String, то результат будет объектом типа String.

Особенности оператора +

В Java не допускается определять перегрузку операторов (то есть перепрограммирование операторов в зависимости от типа), как в С/С++. Но сам язык Java перегружает операторы автоматически. И это в общем-то не ново, поскольку многие языки программирования, которые поддерживают множественные арифметические типы, определяют автоматическую перегрузку арифметических операторов для примитивных типов. Java, помимо этого, имеет перегрузку оператора + для строкового типа (String). И результат действия оператора + в данном случае - это конкатенация, сцепление строк. Если один из операндов не строковый, то к нему будет предварительно применено приведение типа к строке.

Перегрузка (overloading) - это термин, упоминаемый в том случае, когда одна и та же операция (оператор) используется для операндов (аргументов) различного типа. При этом поведение операции (оператора) определяется типом операндов, к котором она должна быть применена. Например, метод println() может применяться как для операндов строкового, так и целочисленного типа. Эти два использования, на самом деле, относятся к совершенно различным методам. Просто было использовано одно и то же имя операции. Аналогично сложение (+) используется, как для целочисленных, так и для дробных типов. Но код, реализующий это сложение совершенно различный.

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

Если переполнение происходит при арифметическом сложении или вычитании, никаких сообщений об ошибках или исключений мы не получаем. Дело обстоит совсем по-другому, если мы конкатенируем строки. Если оба оператора строки, то результат - это "скленная" строка. Если один из операторов строка, а второй - число, то число будет приведено к типу String.

Приведение численных типов к строке

Для объектных типов приобразование к типу String происходит посредством вызова метода toString(). Этот метод определён в классе java.lang.Object, который является прародителем всех остальных классов. Поэтому все объекты наследуют от Object метод toString(). Однако, этот метод выдаёт некое зашифрованное значение объекта. Например, если мы рассмотрим такой код:

1. public class Test {
2. private static class MyClass {
3. private String name;
4. private int age;
5. public MyClass() {
6. name = "Natalia Macheda";
7. age = 28;
8. }
9.
10. }
11. public static void main (String[] args) {
12. MyClass myObj = new MyClass();
13. System.out.println(myObj.toString());
14. }
15.}

То результат, выводимый 13-й строкой будет такого вида: Test$MyClass@c17164. Здесь мы видим имена классов объекта (разделённые символом $) и какой-то идентификатор после символа @. Идентификатор - это, как правило, ссылка на объект.

Это не очень удобно для понимания, но может быть использовано, например, для отладки программы. С другой строны, для предания читаемости результату операции toString() необходимо перегрузить эту операцию. Например, если в 9-й строке вставить код:

9.       public String toString(){
9'. return (name + " " + age);
9''. }

то результат будет Natalia Macheda 28. Заметьте, что втрой аргумент - целочисленного типа. Как же произошло его преобразование в строку?

Преобразование чесленных аргументов к строчному типу неявно использует метод toString() классов-оболочек (wrapper). Например, значение типа int конвертируется вызовом статической функции Integer.toString().

Резюмируем сведения о сложении. Сложение двух числовых значений примтивных типов даёт результат:

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

Если один из операндов - НЕпримитивного типа:

  • второй операнд должен быть строкового типа. Иначе операция нелегальна;
  • операнд НЕстрокового типа приводится к типу String и результат - сцепление двух строк.

Приведение операнда объектного типа к строке производится путём вызова метода toString().

Приведение операнда примитивного типа к строке производится неявно путём вызова статичского метода класса-оболочки.

Если вы хотите держать под контролем форматирование результата приведения типа, обратитесь к коду пакета java.text.

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

Арифметические ошибки

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

  • Деление на ноль (в том числе и для операции %) выдаёт ArithmeticException;
  • Никаких других исключений для арифметических операций не выдаётся, но результат может быть арифметически неправильным ввиду переполнения.
  • Дробные типы представляют абстрактные значения при помощи следующих значений: IEEE 754 бесконечности, минус бесконечности и Не-числа NaN (Not a Number). Именные константы, представляющие эти значения, объявлены в классах Float и Double.

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

Сравнение с НЕ-числом

Два НЕ-числа NaN определены в пакете java.lang. Это Float.NaN и Double.NaN. Следующие сравнения всегда дают false, даже если x равен NaN:

x < Float.NaN
x <= Float.NaN
x == Float.NaN
x > Float.NaN
x >= Float.NaN

В тестах Float.NaN != Float.NaN и Doube.NaN != Double.NaN результат будет true.

Если же вы хотите проверить, что число (не) является NaN, то нужно использовать статические методы Float.isNaN(float) или Double.isNaN(double), определённые в пакете java.lang.

Комментариев нет: