Что означает "возможное преобразование с потерями" и как мне это исправить?
Начинающих 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
потенциально приводит к потерям.
Что означает "потенциально с потерями"?
Что ж, давайте рассмотрим пару примеров.
Преобразование a
long
в anint
является преобразованием с потенциальными потерями, потому что существуютlong
значения, которые не имеют соответствующегоint
значения. Например, любоеlong
значение, большее 2 ^31 - 1, слишком велико, чтобы быть представленным в видеint
. Аналогично, любое число, меньшее -2 ^ 31, слишком мало.Преобразование
int
вlong
НЕ является преобразованием с потерями, потому что каждоеint
значение имеет соответствующееlong
значение.Преобразование a
float
в anlong
является преобразованием с потенциальными потерями, потому что естьfloat
значения, которые находятся за пределами диапазона, который может быть представлен какlong
значения. Такие числа преобразуются (с потерями) вLong.MAX_VALUE
orLong.MIN_VALUE
, как и значения NaN и Inf.Преобразование
long
вfloat
НЕ является преобразованием с потерями, потому что каждоеlong
значение имеет соответствующееfloat
значение. (Преобразованное значение может быть менее точным, но "с потерями" это не означает ... в данном контексте.)
Это все преобразования, которые потенциально приводят к потерям:
short
tobyte
илиchar
char
tobyte
или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