Подтвердить что ты не робот

Почему компилятор не дает ошибку для этой операции добавления?

Я знаю, что компилятор делает неявное преобразование типов для целых литералов. Например:

byte b = 2; // implicit type conversion, same as byte b = (byte)2;

Компилятор дает мне ошибку, если диапазон переполняется:

byte b = 150; // error, it says cannot convert from int to byte

Компилятор дает ту же ошибку, когда переменной передается выражение:

byte a = 3;
byte b = 5;
byte c = 2 + 7; // compiles fine
byte d = 1 + b; // error, it says cannot convert from int to byte
byte e = a + b; // error, it says cannot convert from int to byte

Я пришел к выводу, что результат выражения, включающего переменные, не может быть гарантирован. Результирующее значение может находиться внутри или вне диапазона байтов, поэтому компилятор выдает ошибку.

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

byte a = 127;
byte b = 5;
byte z = (a+=b); // no error, why ?

Почему это не дает мне ошибку?

4b9b3361

Ответ 1

Ответ предоставлен JLS 15.26.2:

Например, следующий код верен:

short x = 3;

x += 4.6;

и приводит к тому, что x имеет значение 7, потому что оно эквивалентно:

short x = 3;

x = (short)(x + 4.6);

Итак, как вы можете видеть, последний случай действительно работает, потому что назначение добавления (как и любое другое назначение оператора) выполняет неявное преобразование в левый тип (а в вашем случае a - byte). Расширение, это эквивалентно byte e = (byte)(a + b);, который будет скомпилироваться с радостью.

Ответ 2

В то время как декомпиляция вашего кода объясняет, что делает Java, причина, по которой это делается, обычно может быть найдена в спецификации языка. Но прежде чем мы пойдем к этому, мы должны установить несколько важных понятий:

Итак, мы вернулись к этому сценарию: почему добавление двух байтов, которые явно больше, чем то, что может обрабатывать байт, не создает ошибку компиляции?

Он не будет создавать исключение во время выполнения из-за overflow.

Это сценарий, в котором два числа, добавленные вместе, неожиданно создают очень небольшое число. Из-за небольшого размера диапазона byte он чрезвычайно прост в переполнении; например, добавление от 1 до 127 сделает это, в результате чего -128.

Основная причина, по которой он собирается обернуться, объясняется тем, как Java обрабатывает примитивное преобразование значений; в этом случае мы говорим о сужающем преобразовании. То есть, хотя полученная сумма больше, чем byte, сужение преобразования приведет к отбрасыванию информации, чтобы данные могли вписываться в byte, поскольку это преобразование никогда не вызывает исключение во время выполнения.

Поэтапно разбивать ваш сценарий:

  • Java добавляет a = 127 и b = 5 вместе, чтобы создать 132.
  • Java понимает, что a и b имеют тип byte, поэтому результат должен также иметь тип byte.
  • Integer результат по-прежнему равен 132, но на данный момент Java выполнит приведение, чтобы сузить результат до байта - эффективно дает вам (byte)(a += b).
  • Теперь оба a и z содержат результат -124 из-за обертки.

Ответ 3

Я пришел к выводу, что результат выражения, включающего переменные, не может быть гарантирован. Результирующее значение может находиться внутри или вне диапазона байтов, поэтому компилятор выдает ошибку.

Нет, это не причина. Компиляторы статически типизированного языка работают следующим образом: Любая переменная должна быть объявлена ​​и напечатана, поэтому даже если ее значение неизвестно во время компиляции, ее тип известен. То же самое касается неявных констант. Исходя из этого, правила вычисления весов в основном таковы:

  • Любая переменная должна иметь тот же или более высокий масштаб, что и выражение с правой стороны.
  • Любое выражение имеет одинаковую шкалу максимального членства на нем.
  • Явные силы бросания, of corse, масштаб правого выражения.

(Это на самом деле упрощенный вид, на самом деле может быть немного сложнее).

Примените его к своим случаям:

byte d = 1 + b

Фактические масштабы:

byte = int + byte

... (потому что 1 рассматривается как неявная константа int). Итак, применяя первое правило, переменная должна иметь шкалу не менее int.

И в этом случае:

byte z = (a+=b);

Фактические масштабы:

byte = byte += byte

... это нормально.

Обновление

Затем, почему byte e = a + b создает ошибку времени компиляции?

Как я уже сказал, правила реального типа в java более сложны: хотя общие правила применяются ко всем типам, примитивные типы byte и short более ограничены: компилятор предполагает, что добавление/вычитание двух или более байты/шорты рискуют вызвать переполнение (как указано в @Makoto), поэтому он должен быть сохранен как следующий тип в шкале, который считается "более безопасным": a int.

Ответ 4

Основная причина заключается в том, что компилятор ведет себя несколько иначе, когда задействованы константы. Все целые литералы рассматриваются как константы int (если в конце они не имеют L или L). Обычно вы не можете назначить int для byte. Однако существует специальное правило, в котором задействованы константы; см. JLS 5.2. В основном, в объявлении типа byte b = 5;, 5 является int, но законно делать "сужающее" преобразование в byte , потому что 5 является константой и потому что он вписывается в диапазон byte. Вот почему byte b = 5 разрешен и byte b = 130 нет.

Однако byte z = (a += b); - это другой случай. a += b просто добавляет b в a и возвращает новое значение a; это значение присваивается a. Так как a является байтом, то нет никакого сужающего преобразования - вы назначаете байт байту. (Если a были int, программа всегда была бы незаконной.)

И правила говорят, что a + b (и поэтому a = a + b, или a += b) не будет переполняться. Если результат во время выполнения слишком велик для байта, верхние биты просто теряются - значение обтекает. Кроме того, компилятор не будет "оценивать", чтобы заметить, что a + b будет больше 127; хотя мы можем сказать, что значение будет больше 127, компилятор не будет отслеживать предыдущие значения. Насколько ему известно, когда он видит a += b, он знает только, что программа будет добавлять b в a при запуске, и она не смотрит на предыдущие объявления, чтобы увидеть, какие значения будут. (Хороший оптимизирующий компилятор действительно может выполнять такую ​​работу. Но мы говорим о том, что делает программу законной или нет, а правила о законности не касаются оптимизации.)

Ответ 5

Я столкнулся с этим раньше в одном проекте, и это то, что я узнал:

В отличие от c/С++, Java всегда использует подписанные примитивы. Один байт от -128 до +127, поэтому, если вы назначаете что-либо за этим диапазоном, оно даст вам ошибку компиляции.

Если вы явно конвертируете в байт, например (byte) 150, вы все равно не получите то, что хотите (вы можете проверить с помощью отладчика и увидеть, что он преобразуется в другое).

Когда вы используете переменные типа x = a + b, потому что компилятор не знает значения во время выполнения и не может вычислить, будет ли -128 <= a+b <= +127 выдаваться ошибка.

Что касается вашего вопроса о том, почему компилятор не дает ошибку на чем-то вроде a+=b:

Я копаю в java-компилятор, доступный из openjdk в

http://hg.openjdk.java.net/jdk9/jdk9/langtools.

Я проследил обработку дерева операндов и пришел к интересному выражению в одном из файлов компилятора Lower.java, который частично отвечает за перемещение дерева компилятора. вот часть кода, которая была бы интересна (Assignop для всех операндов, таких как + = - =/=...)

public void visitAssignop(final JCAssignOp tree) {
                        ...
                        Symbol newOperator = operators.resolveBinary(tree,
                                                                      newTag,
                                                                      tree.type,
                                                                      tree.rhs.type);
                        JCExpression expr = lhs;
                        //Interesting part:
                        if (expr.type != tree.type)
                            expr = make.TypeCast(tree.type, expr);
                        JCBinary opResult = make.Binary(newTag, expr, tree.rhs);
                        opResult.operator = newOperator;:

                        ....

как вы можете видеть, имеет ли тип rhs другой тип, чем lhs, тип cast будет иметь место, даже если вы объявите float или double с правой стороны (a+=2.55), вы получите нет ошибки из-за типа cast.

Ответ 6

/*
 * Decompiled Result with CFR 0_110.
 */
class Test {
    Test() {
    }

    public static /* varargs */ void main(String ... arrstring) {
        int n = 127;
        int n2 = 5;
        byte by = (byte)(n + n2);
        n = by;
        byte by2 = by;
    }
}

После декомпиляции кода

class Test{
public static void main(String... args){
byte a = 127;
byte b = 5;
byte z = (a+=b); // no error, why ?
}
}

Внутри Java заменил ваш оператор a+=b кодом (byte)(n+n2).

Ответ 7

Выражение byte1+byte2 эквивалентно (int)byte1+(int)byte2 и имеет тип int. Хотя выражение x+=y; обычно эквивалентно var1=var1+var2;, такая интерпретация делает невозможным использование += со значениями меньше int, поэтому компилятор будет обрабатывать byte1+=byte2 как byte1=(byte)(byte1+byte2);.

Обратите внимание, что система типов Java была разработана в первую очередь для простоты, и ее правила были выбраны так, чтобы иметь смысл во многих случаях, но поскольку упрощение правил было более важным, чем обеспечение их последовательного разумного, существует множество случаев, когда правила системы типов приводят к бессмысленному поведению. Один из наиболее интересных иллюстрируется с помощью:

long l1 = Math.round(16777217L)
long l2 = Math.round(10000000000L)

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

long distInTicks = Math.round(getDistance() * 2.54);

были изменены, чтобы исключить масштабный коэффициент [и getDistance() возвращено долго]. Какие значения вы ожидаете от l1 и l2? Можете ли вы понять, почему они могут получить другое значение?