13

So I came over a weird behaviour regarding bitwise operators and bit shift. I was trying to make a small check faster by using bit masks and I came accross this:

public class Weirdness {

    private final static int constant = 3;

    private static int notConstant = 3;

    public void stuff() {
        byte a = 0b1 << 3;
        byte b = 0b1 << (int) 3;
        byte c = 0b1 << constant;
        byte d = 0b1 << notConstant; //error
        byte e = 0b1 << getAnInt(); //error
        byte f = 0b1 << getAFinalInt(); //error
        int i = 3;
        byte g = 0b1 << i; //error
        final int j = 3;
        byte h = 0b1 << j;
    }

    public static int getAnInt() {
        return 3;
    }

    public static final int getAFinalInt() {
        return 3;
    }

}

a, b, c and h do not give compilation errors; But d, e, f and g do. The compiler asks to cast explicitly to byte or to declare the last variables as int. I have noticed a similar behaviour with the bitwize & and | too.

Could someone explain what is happening here? What kind of magic is compiler working for the a, b, c and hto work?

EDIT: Or How this is not exactly a duplicate

I believe this question is different because from Why can not I add two bytes and get an int and I can add two final bytes get a byte? because what is causing the interesting behaviour is how the compiler optimize the bitwize shift operations.

And since I seek a theoretical answer (because I already understand that I can make my code compile by casting) to how the shift and other bitwize operations determine their return value, I believe this question can complement Java - bit shifting with integers and bytes and bring more interesting information to StackOverflow.

Community
  • 1
  • 1
le-doude
  • 3,345
  • 2
  • 25
  • 55
  • The answer from [Rohit](http://stackoverflow.com/a/12639659/180100) is what you are looking for I think –  Feb 10 '14 at 06:38

2 Answers2

5

From JLS: The type of the shift expression is the promoted type of the left-hand operand.

The promoted type of byte is int - and that's the reason why, in most cases, you have to cast the result as follows:

byte e = (byte) (0b1 << getAnInt()); 

So the real question is why, on the first 3 lines, there is no need for casting. Now, that's not accurate either cause if you'll change the line:

private final static int constant = 3;

to:

private final static int constant = 1000;

you'll get a compilation error on:

byte c = 0b1 << constant;

as well.

The shift operation might create an integer number which has bigger value than the byte on the left side of the assignment can hold - which triggers the compile-time error and forces us to cast to byte in order to grab only the least significant 8 bits.

So why in the first 3 lines we didn't need to cast to byte ?
The compiler recognized that we're using a constant (or a final) and thus "knows" that this value cannot be changed later on, so it allows Narrowing Primitive Conversion for the assignment to byte - on the left side:

byte c = 0b1 << 3;
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
  • Wow! Thanks! So this is basically the cases where I have no errors are actually the excetions and not the other way around. Great answer. – le-doude Feb 10 '14 at 08:44
3

Short answer:

The compiler knows that a final value or a literal can not change and can safely implicitly cast constant and 3 to a byte with the given values.

Non-final values can't be reasoned about in this same way.

Explicit is better than implicit.

This is one example of why I hate implicit anything related to writing programs.

Exercise:

change constant or the literal 3 to something that won't fit in a byte and see how it complains

Community
  • 1
  • 1
  • If I am not mistaken Java is all pass by value. So why is it not working for `e` and `f`? Since it is working for `c`. I though Java could walk those kind of references and optimize. – le-doude Feb 10 '14 at 06:50
  • 1
    this has nothing to do with `pass by value` or anything else like that, and Java passes `references` by value. A function can return **anything**, `final` on a function just means it can't be overridden, it says nothing about its return value. [Final Word on Final](http://www.vertigrated.com/blog/2011/05/the-final-word-on-final/) –  Feb 10 '14 at 07:09