Вопрос-ответ

What does "possible lossy conversion" mean and how do I fix it?

Что означает "возможное преобразование с потерями" и как мне это исправить?

Начинающих Java-программистов часто сбивают с толку сообщения об ошибках компиляции, такие как:


"несовместимые типы: возможное преобразование с потерями из double в int"


для этой строки кода:

int squareRoot = Math.sqrt(i);

В общем, что означает сообщение об ошибке "возможное преобразование с потерями" и как это исправить?

Переведено автоматически
Ответ 1

Прежде всего, это ошибка компиляции. Если вы когда-либо видите это в сообщении об исключении во время выполнения, это потому, что вы запустили программу с ошибками компиляции1.

Общая форма сообщения такова:


"несовместимые типы: возможное преобразование с потерями из <type1> в <type2>"


где <type1> и <type2> оба являются примитивными числовыми типами; т.е. Один из byte, char, short, int long, float double, ,,,,,,,,,,,,,,,,,, или ,,,.

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

В примере в вопросе:

  int squareRoot = Math.sqrt(i);

sqrt метод выдает double, но преобразование из double в int потенциально приводит к потерям.

Что означает "потенциально с потерями"?

Что ж, давайте рассмотрим пару примеров.


  1. Преобразование a long в an int является преобразованием с потенциальными потерями, потому что существуют long значения, которые не имеют соответствующего int значения. Например, любое long значение, большее 2 ^31 - 1, слишком велико, чтобы быть представленным в виде int. Аналогично, любое число, меньшее -2 ^ 31, слишком мало.



  2. Преобразование int в long НЕ является преобразованием с потерями, потому что каждое int значение имеет соответствующее long значение.



  3. Преобразование a float в an long является преобразованием с потенциальными потерями, потому что есть float значения, которые находятся за пределами диапазона, который может быть представлен как long значения. Такие числа преобразуются (с потерями) в Long.MAX_VALUE or Long.MIN_VALUE, как и значения NaN и Inf.



  4. Преобразование long в float НЕ является преобразованием с потерями, потому что каждое long значение имеет соответствующее float значение. (Преобразованное значение может быть менее точным, но "с потерями" это не означает ... в данном контексте.)



Это все преобразования, которые потенциально приводят к потерям:


  • short to byte или char

  • char to byte или short

  • int в byte, short или char

  • long в byte, short, char или int

  • float в byte, short, char, int или long

  • double в byte, short, char int, long float или,,.

Как исправить ошибку?

Чтобы устранить ошибку компиляции, нужно добавить приведение типов. Например;

  int i = 47;
int squareRoot = Math.sqrt(i); // compilation error!

становится

  int i = 47;
int squareRoot = (int) Math.sqrt(i); // no compilation error

Но действительно ли это исправление? Учтите, что квадратный корень из 47 равен 6.8556546004 ... но squareRoot получит значение 6. (Преобразование будет усеченным, а не округленным.)

И что насчет этого?

  byte b = (int) 512;

Это приводит к b получению значения 0. Преобразование из большего типа int в меньший тип int выполняется путем маскирования битов старшего порядка, а 8 битов младшего порядка 512 равны нулю.

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

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


  • Это происходит из-за того, что вы допустили какую-то другую ошибку в своем коде?

  • Должен ли <type1> быть другого типа, чтобы преобразование с потерями здесь не требовалось?

  • Если преобразование необходимо, будет ли корректным тихое преобразование с потерями, выполняемое приведением типов?

  • Или ваш код должен округлять, а не усекать, или выполнять некоторые проверки диапазона и иметь дело с неправильными / неожиданными значениями, создавая исключение?

"Возможное преобразование с потерями" при подписке.

Первый пример:

for (double d = 0; d < 10.0; d += 1.0) {
System.out.println(array[d]); // <<-- possible lossy conversion
}

Проблема здесь в том, что значение индекса массива должно быть int. Поэтому d должно быть преобразовано из double в int. В общем случае использование значения с плавающей запятой в качестве индекса не имеет смысла. Либо у кого-то сложилось впечатление, что массивы Java работают как (скажем) словари Python, либо они упустили из виду тот факт, что арифметика с плавающей запятой часто неточна.

Решение состоит в том, чтобы переписать код, чтобы избежать использования значения с плавающей запятой в качестве индекса массива. (Добавление приведения типов, вероятно, является неправильным решением.)

Второй пример:

for (long l = 0; l < 10; l++) {
System.out.println(array[l]); // <<-- possible lossy conversion
}

Это вариант предыдущей проблемы, и решение такое же. Разница в том, что основная причина заключается в том, что массивы Java ограничены 32-битными индексами. Если вы хотите "массивоподобную" структуру данных, содержащую более 231 - 1 элементов, вам нужно определить или найти класс для этого.

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

Учтите это:

public class User {
String name;
short age;
int height;

public User(String name, short age, int height) {
this.name = name;
this.age = age;
this.height = height;
}

public static void main(String[] args) {
User user1 = new User("Dan", 20, 190);
}
}

Компиляция вышеупомянутого с Java 11 дает следующее:

$ javac -Xdiags:verbose User.java 
User.java:20: error: constructor User in class User cannot be applied to given types;
User user1 = new User("Dan", 20, 190);
^
required: String,short,int
found: String,int,int
reason: argument mismatch; possible lossy conversion from int to short
1 error

Проблема в том, что литерал 20 является int , а соответствующий параметр в конструкторе объявлен как short. Преобразование int в a short приводит к потерям.

"Возможное преобразование с потерями" в операторе return .

Пример:

public int compute() {
long result = 42L;
return result; // <<-- possible lossy conversion
}

A return (со значением / выражением) можно рассматривать как "присвоение возвращаемому значению". Но независимо от того, как вы думаете об этом, необходимо преобразовать предоставленное значение в фактический возвращаемый тип метода. Возможные решения - добавление приведения типов (с надписью "Я подтверждаю отсутствие потерь") или изменение возвращаемого типа метода.

"Возможное преобразование с потерями" из-за повышения в выражениях

Учтите это:

byte b1 = 0x01;
byte mask = 0x0f;
byte result = b1 & mask; // <<-- possible lossy conversion

Это подскажет вам, что существует "возможное преобразование с потерями из int в byte". На самом деле это вариация первого примера. Потенциально запутанная вещь заключается в понимании того, откуда берется int.

Ответ на этот вопрос исходит от & оператора. Фактически все арифметические и побитовые операторы для целых типов будут выдавать int or long, в зависимости от операндов. Итак, в приведенном выше примере b1 & mask фактически создает int, но мы пытаемся присвоить это a byte.

Чтобы исправить этот пример, мы должны преобразовать результат выражения обратно в byte перед его присвоением.

byte result = (byte) (b1 & mask); 

"Возможное преобразование с потерями" при назначении литералов

Учтите это:

int a = 21;
byte b1 = a; // <<-- possible lossy conversion
byte b2 = 21; // OK

Что происходит? Почему одна версия разрешена, а другая нет? (В конце концов, они "делают" одно и то же!)

Прежде всего, в JLS указано, что 21 это числовой литерал, тип которого int. (Литералов byte or short нет.) Итак, в обоих случаях мы присваиваем int a byte.

В первом случае причина ошибки в том, что не все int значения поместятся в byte.

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

Техническое объяснение заключается в том, что в контексте присваивания допустимо выполнять примитивное сужающее преобразование в byte, char или short, если все нижеперечисленное верно:


  • Значение является результатом постоянного выражения во время компиляции (которое включает литералы).

  • Тип выражения - byte, short, char или int.

  • Присваиваемое постоянное значение является представимым (без потерь) в домене типа "target".

Обратите внимание, что это применимо только к операторам присваивания или, более технически, к контекстам присваивания. Таким образом:

Byte b4 = new Byte(21);  // incorrect

выдает ошибку компиляции.


1 - Например, в Eclipse IDE есть опция, которая позволяет игнорировать ошибки компиляции и запускать код в любом случае. Если вы выберете это, компилятор IDE создаст .class файл, в котором метод с ошибкой выдаст непроверенное исключение, если он будет вызван. В сообщении об исключении будет указано сообщение об ошибке компиляции.

Ответ 2

Сравните BigDecimal .valueOf (Math .sqrt(2)) .intValueExact () и .intValue (). Используйте intValueExact, если аргумент должен быть целым числом. Используйте последнее, если усечение для вас нормально или если это доказуемо целое число a (как в Math .sqrt (Math .multiplyExact (i, i))) — в последнем случае вы можете захотеть assert r * r == i (r означает your squareRoot).

К сожалению, нет .charValueExact.

Смотрите также JDK-8279986

java