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

avatar
Stephen C
1 августа 2018 в 11:30
14784
1
18

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

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

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

int squareRoot = Math.sqrt(i);

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

Источник

Ответы (1)

avatar
Stephen C
1 августа 2018 в 11:30
31

Во-первых, это ошибка компиляции. Если вы когда-либо видели это в сообщении об исключении во время выполнения, это потому, что вы запустили программу с ошибками компиляции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 может привести к потерям.

Что означает "с возможными потерями"?

Давайте рассмотрим пару примеров.

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

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

  3. Преобразование float в long является преобразованием с потенциальными потерями, поскольку значения float слишком велики или слишком малы для представления в виде значений long.

  4. Преобразование 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, в котором метод с ошибкой вызовет непроверенное исключение, если он будет вызван. В сообщении об исключении будет упомянуто сообщение об ошибке компиляции.

Stephen C
12 августа 2018 в 03:11
1

Любые предложения для более полезных "конкретных случаев"? Я ищу примеры, где основной причиной является какое-то явно идентифицируемое неправильное представление о языке Java... которое еще не рассмотрено.

Lino
21 сентября 2020 в 10:13
0

Вариант использования, о котором вы не упомянули, касается инициализации массива. Например. int[] a = {23L}; или long l = 23; int[] a = {l};