2016-02-22 3 views
29

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

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 ? 

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

+2

Ваш просто расширение этого дубликата ... Когда вы выполняете 'byte z = (a + = b);', вы просто назначаете один ** байт ** ('a') другому (' z '). – Codebender

+0

Голосование для повторного открытия, потому что дубликат отвечает на другой вопрос. Он спрашивает, почему 'i + = j' разрешен, когда' j' больше, чем 'i'. Этот вопрос не связан с этим. – ajb

+0

Я удивлен, что никто не упоминал об этом, но если вы определяете b final, byte d = 1 + b; будет компилироваться. Это должно дать вам намек. –

ответ

5

Ответ обеспечивается JLS 15.26.2:

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

short x = 3;

x += 4.6;

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

short x = 3;

x = (short)(x + 4.6);

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

+0

Гораздо понятнее и точнее, чем текущий «верхний» ответ – Marco13

+0

Хотя я принял это как ответ, я считаю, что фактический ответ - это комбинация всех других ответов. 1) 'byte z = (a + = b);' равен байту z = (байт) (a + b) '2) Переполнение vaue –

+0

@FlyingGambit, переполнение (строго говоря) не имеет ничего общего, поскольку литье. Еще один тест, который вы можете проверить, - это 'byte b = 125 + 5', который генерирует ошибку компиляции (' required byte, found int', потому что 130 не может соответствовать байту, поэтому компилятор не может выполнять автоматическое кастинг). С другой стороны, 'byte b = (byte) (125 + 5)' компилируется отлично, потому что вы вынуждаете компилятор обрабатывать результат как 'byte' (эффективное значение -' -126' FYI) – ThanksForAllTheFish

0
/* 
* 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) кодом.

+0

И как это объясняет, почему нет ошибки во время компиляции? Извините, это ответ на другой вопрос. – ajb

+0

Почему нет вызова функции super() в конструкторе? Я думал, что унаследовал java.lang.Object? –

+1

@FlyingGambit Вероятно, потому что компилятор знает, что конструктор 'Object' по умолчанию ничего не делает. В языковых правилах говорится, что 'super()' следует вызывать, но компилятору разрешено знать, что это пустая трата времени, когда он генерирует код. Языковые правила действительно говорят только о том, как должна себя вести себя программа - они не говорят нам точного кода, который должен быть сгенерирован. – ajb

4

Основная причина заключается в том, что компилятор ведет себя несколько иначе, когда задействованы константы. Все целочисленные литералы обрабатываются как константы int (если в конце они не имеют L или l). Обычно вы не можете назначить intbyte. Однако существует специальное правило, в котором задействованы константы; см. 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; хотя we может сказать, что значение будет больше 127, компилятор не будет отслеживать предыдущие значения.Насколько он знает, когда он видит a += b, он знает, что программа будет добавлять b в a, когда он будет запущен, и он не будет смотреть на предыдущие объявления, чтобы увидеть, какие значения будут. (Хороший оптимизирующий компилятор действительно может выполнять такую ​​работу, но мы говорим о том, что делает программу законной или нет, а правила законности не касаются оптимизации.)

+0

a + = 130 действителен, но когда вы пишете его как a = a + 130, он даст ошибку времени компиляции – Pooya

+0

Правда, но это не имеет никакого отношения к этому вопросу. – ajb

+0

вы говорите, что a = = b является действительным, поскольку оба они являются байтами, но даже если вы объявляете b как int, оно также будет истинным и не будет ошибки времени компиляции. По-моему, значение + = сильно отличается от a = a + b – Pooya

5

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

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

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

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

применить его к делам:

byte d = 1 + b 

Реальные масштабы:

byte = int + byte 

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

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

byte z = (a+=b); 

Реальные масштабы:

byte = byte += byte 

... который в порядке.

Update

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

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

+0

Тогда не следует 'byte e = a + b;' компилировать, поскольку это 'byte = byte + byte'? –

+0

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

+0

То же самое верно для двух поплавков? Можете ли вы предоставить ссылку для дальнейшего чтения? –

22

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

  • A literal numeral is always interepreted as an int.

    Целый литерал имеет тип долго, если он суффиксом с ASCII буквы L или L (флигель); в противном случае это тип int (п. 4.2.1).

  • A byte can only hold an integer value between -128 and 127, inclusive.

  • Попытка присвоить литерал, который больше, чем тип, который может содержать это приведет к ошибке компиляции. Это первый сценарий, с которым вы сталкиваетесь.

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

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

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

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

Чтобы сломать ваш шаг за шагом сценария:

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

это то, что мои примеры ясны, просто декомпилируя код. Вкратце это будет объясните, я думаю .. –

+1

@VikrantKashyap: Опять же, это объясняет, что и как, но не почему. – Makoto

+0

Я думаю, что ему не нужно ничего делать с помощью int литералов, переполнения или сужения конверсий. Ключевым моментом является то, что 'someByte + = whatever' всегда имеет тип' byte', как указано в [answer by ThanksForAllTheFish] (http://stackoverflow.com/a/35557111/3182664) – Marco13

4

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

в отличие от C/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.

0

Выражение 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? Можете ли вы понять, почему они могут получить какую-то другую ценность?

Смежные вопросы