Во-первых, это ошибка компиляции. Если вы когда-либо видели это в сообщении об исключении во время выполнения, это потому, что вы запустили программу с ошибками компиляции1.
Общая форма сообщения такова:
"несовместимые типы: возможно преобразование с потерями из <type1>
в <type2>
"<
1
где <type1>
и <type2>
являются примитивными числовыми типами; то есть один из byte
, char
, short
, int
, long
, float
или double
.<800286814>.
Эта ошибка происходит, когда ваши попытки кода, чтобы сделать неявное преобразование из <type1>
к <type2>
, но преобразование может быть с потерями .
В примере в вопросе:
int squareRoot = Math.sqrt(i);
метод sqrt
дает double
, но преобразование из double
в int
может привести к потерям.
Что означает "с возможными потерями"?
Давайте рассмотрим пару примеров.
-
Преобразование long
в int
является преобразованием с потенциальными потерями, поскольку существуют значения long
, которым не соответствует значение int
. Например, любое значение long
, превышающее 2^31 - 1, слишком велико для представления в виде int
. Точно так же любое число меньше -2^31 слишком мало.
-
Преобразование int
в long
НЕ является преобразованием с потерями, поскольку каждое значение int
имеет соответствующее значение long
.
-
Преобразование float
в long
является преобразованием с потенциальными потерями, поскольку значения float
слишком велики или слишком малы для представления в виде значений long
.
-
Преобразование long
в float
НЕ является преобразованием с потерями, поскольку каждое значение long
имеет соответствующее значение float
. (Преобразованное значение может быть менее точным, но "с потерями" не означает, что... в данном контексте.)
Все конверсии, которые потенциально могут привести к потерям:
-
short
до byte
или char
-
char
до 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
в short
выполняется с потерями.
"Возможное преобразование с потерями" в операторе возврата.
Пример:
public int compute() {
long result = 42L;
return result; // <<-- possible lossy conversion
}
A return
(со значением/выражением) можно рассматривать как "присвоение возвращаемого значения". Но как бы вы об этом ни думали, необходимо преобразовать переданное значение в фактический возвращаемый тип метода. Возможными решениями являются добавление приведения типа (которое говорит: "Я признаю потерю качества") или изменение типа возвращаемого значения метода.
"Возможно преобразование с потерями" при назначении литералов
Подумайте об этом:
int a = 21;
byte b1 = a; // <<-- possible lossy conversion
byte b2 = 21; // OK
Что происходит? Почему одна версия разрешена, а другая нет? (Ведь они "делают" одно и то же!)
Во-первых, в JLS указано, что 21
— это числовой литерал, тип которого — int
. (Нет литералов byte
или short
.) Поэтому в обоих случаях мы присваиваем int
byte
.
.
В первом случае причина ошибки в том, что не все значения int
поместятся в byte
.
Во втором случае компилятор знает, что 21
— это значение, которое всегда будет соответствовать byte
.
.
Техническое объяснение в том, что в контексте назначения <
4> допустимо выполнить A примитивное сужение преобразования на byte
или short
Если следующее все верно:
- Значение является результатом времени компиляции константного выражения (которое включает литералы).
- Тип выражения:
byte
, short
, char
или int
.
- Присваиваемое постоянное значение представимо (без потерь) в домене типа "цель".
Обратите внимание, что это применимо только к операторам присваивания или, более технически, в контекстах присваивания. Таким образом:
Byte b4 = new Byte(21); // incorrect
выдает ошибку компиляции.
1. Например, в Eclipse IDE есть опция, которая позволяет игнорировать ошибки компиляции и выполнять код в любом случае. Если вы выберете это, компилятор IDE создаст файл .class
, в котором метод с ошибкой вызовет непроверенное исключение, если он будет вызван. В сообщении об исключении будет упомянуто сообщение об ошибке компиляции.
Любые предложения для более полезных "конкретных случаев"? Я ищу примеры, где основной причиной является какое-то явно идентифицируемое неправильное представление о языке Java... которое еще не рассмотрено.
Вариант использования, о котором вы не упомянули, касается инициализации массива. Например.
int[] a = {23L};
илиlong l = 23; int[] a = {l};