34

I know that the compiler does implicit type conversion for integer literals. For example:

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

The compiler gives me an error if the range overflows:

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

The compiler gives the same error when the variable is passed an expression:

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

I came to the conclusion that the result of an expression that involves variables cannot be guaranteed. The resulting value can be within or outside the byte range so compiler throws off an error.

What puzzles me is that the compiler does not throw an error when I put it like this:

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

Why does it not give me an error?

Celeritas
  • 14,489
  • 36
  • 113
  • 194
Flying Gambit
  • 1,238
  • 1
  • 15
  • 32
  • 2
    Yours is just an extension of the above duplicate... When you do `byte z = (a+=b);`, you are just assigning one **byte** (`a`) to another (`z`). – Codebender Feb 22 '16 at 06:11
  • Voting to reopen because the duplicate answers a different question. It asks why `i += j` is allowed when `j` is a larger type than `i`. This question doesn't involve that. – ajb Feb 22 '16 at 06:28
  • I'm surprised no one have mentionned this, but if you define b final, byte d = 1 + b; will compile. That should give you an hint. – Jean-François Savard Feb 22 '16 at 15:50
  • Isn't this a partial example of `autoboxing` ? – Shark Feb 22 '16 at 16:09
  • 3
    @Shark As far as I know, autoboxing and unboxing happens between primitives and wrapper classes – Flying Gambit Feb 22 '16 at 16:33
  • @FlyingGambit right, it's usually between stuff like `Integer` and `int`, so instead of saying `partial example` i should've used `kinda reminds of autoboxing`. – Shark Feb 23 '16 at 08:41
  • @Jean-FrançoisSavard Why does `final byte b; byte d = 1 + b;` work ? I am unable to find any clues in google. – Flying Gambit Feb 24 '16 at 03:35
  • 2
    @FlyingGambit, it is defined in the JLS: http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28. In particular: `A constant expression is an expression denoting a value of primitive type[...]composed using only the following[...]The unary operators +, -, ~, and ! (but not ++ or --)`: so, basically, by declaring a `primitive` final, the optimizer is able to create a constant. A constant is substituted by its value at compile time, thus `final byte b = 5; byte d = 1 + b;` "compiles" to `byte d = 1 + 5`. Omitting the `final` keyword forbids the optimizer to create a constant – ThanksForAllTheFish Feb 24 '16 at 10:38
  • note to my previous comment: optimizer is a made-up word, I do not know if it is a separate tool or a compilation step – ThanksForAllTheFish Feb 24 '16 at 10:39

7 Answers7

24

While decompiling your code will explain what Java is doing, the reason why it's doing it can be generally found in the language specification. But before we go into that, we have to establish a few important concepts:

So we're back to this scenario: why would adding two bytes that are clearly more than what a byte can handle not produce a compilation error?

It won't raise a run-time exception because of overflow.

This is the scenario in which two numbers added together suddenly produce a very small number. Due to the small size of byte's range, it's extremely easy to overflow; for example, adding 1 to 127 would do it, resulting in -128.

The chief reason it's going to wrap around is due to the way Java handles primitive value conversion; in this case, we're talking about a narrowing conversion. That is to say, even though the sum produced is larger than byte, the narrowing conversion will cause information to be discarded to allow the data to fit into a byte, as this conversion never causes a run-time exception.

To break down your scenario step by step:

  • Java adds a = 127 and b = 5 together to produce 132.
  • Java understands that a and b are of type byte, so the result must also be of type byte.
  • The integer result of this is still 132, but at this point, Java will perform a cast to narrow the result to within a byte - effectively giving you (byte)(a += b).
  • Now, both a and z contain the result -124 due to the wrap-around.
Community
  • 1
  • 1
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • thats what my examples clear just by de-compiling the code.. In Short it will explain i think.. – Vikrant Kashyap Feb 22 '16 at 06:59
  • 1
    @VikrantKashyap: Again, that explains what and how, but not why. – Makoto Feb 22 '16 at 07:00
  • I think it does not have to do anything with int literals, overflows or narrowing conversions. The key point is that `someByte+=whatever` is always of type `byte`, as pointed out in the [answer by ThanksForAllTheFish](http://stackoverflow.com/a/35557111/3182664) – Marco13 Feb 22 '16 at 22:18
  • @Marco13: It has *a lot* to do with overflow considering that you're adding two numbers that exceed the `byte` positive value range... – Makoto Feb 22 '16 at 22:39
  • Well, I don't want to argue too much about this. But when you do `byte z = (someByte+=someOtherByte)`, it does not matter whether `someByte+someOtherByte` overflows or not. The result is casted to `byte`, because that's what the `+=` operator does, regardless of the possible overflow. – Marco13 Feb 23 '16 at 09:44
6

The answer is provided by JLS 15.26.2:

For example, the following code is correct:

short x = 3;

x += 4.6;

and results in x having the value 7 because it is equivalent to:

short x = 3;

x = (short)(x + 4.6);

So, as you can see, the latest case actually work because the addition assignment (as any other operator assignment) performs an implicit cast to the left hand type (and in your case a is a byte). Extending, it is equivalent to byte e = (byte)(a + b);, which will compile happily.

Community
  • 1
  • 1
ThanksForAllTheFish
  • 7,101
  • 5
  • 35
  • 54
  • Much clearer and to the point than the current "top" answer – Marco13 Feb 22 '16 at 22:15
  • Although I accepted this as the answer, I believe that the actual answer is a combination of all other answers. 1) `byte z = (a+=b);` being equal to `byte z = (byte)(a+b)` 2) Overflow of vaue – Flying Gambit Feb 25 '16 at 20:41
  • @FlyingGambit, overflow (strictly speaking) has nothing to do, because of the casting. Another test you can do to verify is `byte b = 125 + 5`, which generates a compilation error (`required byte, found int`, because 130 cannot fit a `byte` so the compiler cannot do automatic casting). On the other hand, `byte b = (byte) (125 + 5)` compiles fine because you are forcing the compiler to treat the result as a `byte` (effective value is `-126` FYI) – ThanksForAllTheFish Feb 28 '16 at 11:57
5

I came to the conclusion that the result of an expression that involves variables cannot be guaranteed. The resulting value can be within or outside the byte range so compiler throws off an error.

No, that's not the reason. The compilers of a staticly-typed language work in this way: Any variable must be declared and typed, so even if its value is not known at compile-time, its type is known. The same goes for implicit constants. Based upon this fact, the rules to compute scales are basically these:

  • Any variable must have the same or higher scale than the expression at its right side.
  • Any expression has the same scale of the maximum term involved on it.
  • An explicit cast forces, of corse, the scale of the right-side expression.

(These are in fact a simplified view; actually might be a little more complex).

Apply it to your cases:

byte d = 1 + b

The actual scales are:

byte = int + byte

... (because 1 is considered as an implicit int constant). So, applying the first rule, the variable must have at least int scale.

And in this case:

byte z = (a+=b);

The actual scales are:

byte = byte += byte

... which is OK.

Update

Then, why byte e = a + b produce a compile-time error?

As I said, the actual type rules in java are more complex: While the general rules apply to all types, the primitive byte and short types are more restricted: The compiler assumes that adding/substracting two or more bytes/shorts is risking to cause an overflow (as @Makoto stated), so it requires to be stored as the next type in scale considered "safer": an int.

Little Santi
  • 8,563
  • 2
  • 18
  • 46
  • Then shouldn't `byte e = a + b;` compile as it is `byte = byte + byte` ? – Flying Gambit Feb 22 '16 at 08:27
  • OK. Good point: As I said, the actual type rules in java are more complex: While the general rules apply to all types, the primitive `byte` and `short` types are more restricted: The compiler assumes that adding/substracting two or more bytes/shorts is risking to causa an overflow (as @Makoto stated), so it requires to be stored as the next type in scale considered "safer": an `int`. – Little Santi Feb 22 '16 at 09:04
  • Is the same true for two floats ? Can you provide any link for further reading ? – Flying Gambit Feb 22 '16 at 09:43
  • I answered pointing to the JLS, but since my answer is pretty down the list, you can read as to why `byte e = a + b` does not compile while `byte z = (a+=b);` does here: https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.26.2 – ThanksForAllTheFish Feb 22 '16 at 15:12
4

The basic reason is that the compiler behaves a little differently when constants are involved. All integer literals are treated as int constants (unless they have an L or l at the end). Normally, you can't assign an int to a byte. However, there's a special rule where constants are involved; see JLS 5.2. Basically, in a declaration like byte b = 5;, 5 is an int, but it's legal to do the "narrowing" conversion to byte because 5 is a constant and because it fits into the range of byte. That's why byte b = 5 is allowed and byte b = 130 is not.

However, byte z = (a += b); is a different case. a += b just adds b to a, and returns the new value of a; that value is assigned to a. Since a is a byte, there is no narrowing conversion involved--you're assigning a byte to a byte. (If a were an int, the program would always be illegal.)

And the rules say that a + b (and therefore a = a + b, or a += b) won't overflow. If the result, at runtime, is too large for a byte, the upper bits just get lost--the value wraps around. Also, the compiler will not "value follow" to notice that a + b would be larger than 127; even though we can tell that the value will be larger than 127, the compiler won't keep track of the previous values. As far as it knows, when it sees a += b, it only knows that the program will add b to a when it runs, and it doesn't look at previous declarations to see what the values will be. (A good optimizing compiler might actually do that kind of work. But we're talking about what makes a program legal or not, and the rules about legality don't concern themselves with optimization.)

ajb
  • 31,309
  • 3
  • 58
  • 84
  • a += 130 is valid but when you write it like a = a + 130 it will give compile time error – Pooya Feb 22 '16 at 06:58
  • True, but that has nothing to do with this question. – ajb Feb 22 '16 at 07:00
  • you say a+=b is valid because both of them are bytes but even if you declare b as int it will also be true and there would be no compile time error. In my opinion the meaning of += is quite different than a = a + b – Pooya Feb 22 '16 at 07:02
  • Read carefully. I didn't say that. I didn't say anything about `b`'s type when talking about `a += b`, because it's not relevant to the question. – ajb Feb 22 '16 at 14:07
  • @ajb, I think the best JLS is `15.26.2` (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.26.2) which deals explicitly with assignment operators – ThanksForAllTheFish Feb 22 '16 at 21:24
4

I have encountered this before in one project and this is what I learned:

unlike c/c++, Java is always use signed primitives. One byte is from -128 to +127 so if you assign anything behind this range it will give you compile error.

If you explicitly convert to byte like (byte) 150 still you won't get what you want (you can check with debugger and see it will convert to something else).

When you use variables like x = a + b because the compiler doesn't know the values at run time and cannot calculate whether -128 <= a+b <= +127 it will give error.

Regarding your question about why compiler doesn't give error on something like a+=b :

I dig into java compiler available from openjdk at

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

I traced the tree processing of operands and came to an interesting expression in one of the compiler files Lower.java which partially responsible for traversing the compiler tree. here is a part of the code that would be interesting (Assignop is for all of the operands like += -= /= ...)

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;:

                        ....

as you can see if the rhs has different type than the lhs, the type cast would take place so even if you declare float or double on the right hand side (a+=2.55) you will get no error because of the type cast.

Pooya
  • 6,083
  • 3
  • 23
  • 43
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;
    }
}

After decompilation of your Code

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

Internally, Java replaced your a+=b operator with (byte)(n+n2) the code.

Vikrant Kashyap
  • 6,398
  • 3
  • 32
  • 52
  • And how does this explain why there's no error at compile time? Sorry, this is an answer to a different question. – ajb Feb 22 '16 at 06:41
  • Why there is no call to super() in constructor ? I thought it inherited java.lang.Object ? – Flying Gambit Feb 22 '16 at 06:43
  • 1
    @FlyingGambit Probably because the compiler knows that the default `Object` constructor does nothing. The language rules say that `super()` should be called, but the compiler is allowed to know that it's a waste of time, when it's generating code. The language rules really only tell us what the program is supposed to behave like--they don't tell us the exact code that must be generated. – ajb Feb 22 '16 at 06:56
0

The expression byte1+byte2 is equivalent to (int)byte1+(int)byte2, and has type int. While the expression x+=y; would generally be equivalent to var1=var1+var2;, such an interpretation would make it impossible to use += with values smaller than int, so the compiler will treat byte1+=byte2 as byte1=(byte)(byte1+byte2);.

Note that Java's type system was designed first and foremost for simplicity, and its rules were chosen to as to make sense in many cases, but because making the rules simple was more important than making them consistently sensible, there are many cases where the type system rules yield nonsensical behavior. One of the more interesting ones is illustrated via:

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

In the real world, one wouldn't try to round long constants, of course, but the situation might arise if something like:

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

were changed to eliminate the scale factor [and getDistance() returned long]. What values would you expect l1 and l2 should receive? Can you figure out why they might receive some other value?

supercat
  • 77,689
  • 9
  • 166
  • 211