Преобразование типов

Числовые преобразования

Расширяющее преобразование выполняется неявно, если целевой тип способен представить диапазон исходного типа по правилам языка:

int count = 42;
long total = count;
double average = total;

Основные направления:

byte  → short → int → long → float → double
          char → int → long → float → double

«Расширяющее» не означает «без потери точности». Например, большое значение long может округлиться при преобразовании к float, потому что у float меньше значащих битов.

Сужающее преобразование требует явного cast:

long source = 3_000_000_000L;
int narrowed = (int) source; // переполнение, исключения не будет

Cast берёт младшие биты для целых типов или округляет дробное число к нулю при переходе к целому. Java не проверяет потерю диапазона автоматически.

Продвижение в выражениях

byte, short и char в арифметике обычно продвигаются до int:

byte left = 10;
byte right = 20;
// byte sum = left + right; // результат int
int sum = left + right;

Составное присваивание включает неявное приведение:

byte value = 120;
value += 10; // эквивалентно value = (byte) (value + 10), получится -126

Это удобство может скрыть переполнение.

Ссылочные преобразования

Upcast от наследника к родителю безопасен и неявен:

Object value = "ready";

Downcast проверяется во время выполнения:

if (value instanceof String text) {
    System.out.println(text.length());
}

Явный cast не преобразует объект в другой класс, а лишь утверждает компилятору ожидаемый runtime-тип. Неверное утверждение приводит к ClassCastException.

Boxing, unboxing и generics

Integer boxed = 10;
int primitive = boxed;

Unboxing null выбрасывает NullPointerException. Кроме того, generics инвариантны: преобразовать List<Integer> в List<Number> нельзя, хотя отдельный Integer можно присвоить переменной Number.

Строки и числа

int count = Integer.parseInt("42");
long id = Long.parseLong("9000");
double ratio = Double.parseDouble("0.25");
String text = String.valueOf(count);

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

Точные преобразования

Когда переполнение недопустимо, используйте проверяющие методы:

long value = 2_000_000_000L;
int exact = Math.toIntExact(value);
int sum = Math.addExact(1_500_000_000, 1_000_000_000); // ArithmeticException

Они превращают тихое переполнение в явное исключение.

Ловушки

  • Считать любой widening conversion точным.
  • Ожидать исключение от обычного целочисленного переполнения.
  • Думать, что cast изменяет реальный класс объекта.
  • Не проверять null перед unboxing.
  • Путать (int) double с математическим округлением: cast отбрасывает дробную часть по направлению к нулю.
  • Не учитывать продвижение byte/short/char до int.

Вопросы на собеседовании

  1. Чем расширяющее преобразование отличается от сужающего?
  2. Может ли расширяющее преобразование потерять точность?
  3. Почему сумма двух byte имеет тип int?
  4. Что делает cast ссылочного типа?
  5. Чем (int) 2.9 отличается от Math.round(2.9)?
  6. Как обнаружить переполнение при преобразовании long в int?

Связанные заметки

Переменные и типы данных Java · Классы обертки Java · Работа с числовыми данными · Raw Type · Наследование · Работа с исключениями в Java