Casting, primitive types, literals in Java - Part 00

All those bits getting truncated!
  • literal: source code representation of data.
  • There are four numeral systems that can be used to represent integer literals in the Java language: binary(base 2), octal(base 8), decimal(base 10), and hexadecimal(base 16).
import static java.lang.System.out;
 
class Main {
    public static void main(final String[] args) {
        integerRepresentations();
    }
 
    static void integerRepresentations() {
        final int tinyfin = 0b00001111; // binary!
        final int octal = 017;
        final int decimal = 15;
        final int hexelent = 0xF;
 
        out.print((tinyfin == octal) == (decimal == hexelent));
        // prints true!
    }
}
  • There are four primitive types to store integer numbers in the Java language: byte(8 bits), short(16 bits), int(32 bits), long(64 bits).
    • There also exists char, just to make things confusing! Let 's ignore char for now..
import static java.lang.System.out;
 
class Main {
    public static void main(final String[] args) {
        integerTypes();
    }
 
    static void integerTypes() {
        final byte tinyfin = 15;
        final short octal = 017;
        final int decimal = 0xF;
        final long hexelent = 0b00001111;
 
        out.print((decimal == octal) == (hexelent == tinyfin));
        // prints true!
    }
}
  • Whatever number system(binary, octal, decimal or hexadecimal) you choose or to whatever type(byte, short, int, long) you assign the value, type of the literal by default is always int. This means, you may or may not need to explicitly cast the literal value when assigning it to any type except an 'int'.

Assigning a decimal literal(which is implicitly an int) to type byte
import static java.lang.System.out;
 
class DecimalIntLiteralsToByteExamples {
    public static void main(final String[] args) {
        // -128 is an int literal..
        // But it fits into a byte so implicitly casted by the compiler.
        final byte minValue = -128;
 
        // Same here..
        final byte maxValue = 127;
 
        // We are passing bytes to the method, and the compiler is happy..
        assignDecimalIntLiteralsToByte(maxValue, minValue);
 
        byte minValueNotFinal = -128;
        byte maxValueNotFinal = 127;
 
        // The compiler is still happy eventhough variables are not final..
        assignDecimalIntLiteralsToByte(minValueNotFinal, maxValueNotFinal);
 
        // We are passing the same values we have assigned to a and b above but
        // all of a sudden the compiler is not so happy anymore!
        // Implicit cast does not work in method arguments!
        assignDecimalIntLiteralsToByte((byte) 127, (byte) -128);
    }
 
    static void assignDecimalIntLiteralsToByte(final byte a, final byte b) {
        // Explicit cast required since the compiler has no idea what a and b
        // is and it is possible that they both
        // maybe 127, which means the value of the sum operation  will not fit into a byte!
        final byte maybeWillNotFit = (byte) (a + b);
 
        // No cast required, implicitly casted!
        final byte minValue = -128;
 
        // No cast required, implicitly casted!
        final byte maxValue = 127;
 
        // No cast required.. Since the variables are final,
        // the compiler is already knows foo also has a constant
        // value, and surprisingly it fits into a byte!
        final byte definetlyFits = minValue + maxValue;
 
        byte minValueNotFinal = -128;
        byte maxValueNotFinal = 127;
 
        // Variables in the operation are not final, hence the compiler will kindly ask you to cast!
        final byte iRequireCast = (byte) (minValueNotFinal + maxValueNotFinal);
 
        // Cast required, since maximum value for byte is 127!
        final byte aDecimalTooLarge = (byte) 212;
        // But what is the final value after casting? Let 's find out!
        // 212 in 2s complement in binary in 32 bits: 0000_0000_0000_0000_0000_0000_1101_0100.
        // Chop the first 24 bits to cast it down to a byte, what remains is: 1101_0100.
        // Seems like it fits in 8 bits right?
        // But Java is smart enough to figure out that the final value will be a negative value,
        // since the first digit will be one after chopping, yet our starting value was a positive value.
        // So if we do not cast, this source code will not compile and the error will be:
        // Error:incompatible types: possible lossy conversion from int to byte
 
        out.println(aDecimalTooLarge); // It turns out 1101_0100 equals -44
    }
}

Assigning a binaryliteral(which is implicitly an int) to type byte
public class BinaryIntLiteralsToByteExamples {
 
    public static void main(final String[] args) {
        // Equals -128 and the compiler is happy!
        final byte minValue_0 = 0b1111_1111_1111_1111_1111_1111_1000_0000;
 
        // Cast required because the following value is essentially equal to:
        // 00000000000000000000000010000000
        // which in turn is equal to 128
        // which will not fit into a byte!
        // But we cast it so all the leading 1 's are gone and the result is -128!
        final byte minValue_1 = (byte) 0b1000_0000;
 
        // Same example, uglier:
        // Again, cast is required, just like the example above..
        final byte minValue_2 = (byte) 0b0000_0000_0000_0000_0000_0000_1000_0000;
 
        // Somewhat tricky:
        final byte minBinaryRep_0 = -0b1000_0000;
    }
}

Conclusions
  • A byte(8 bits) can hold integer values between -128(included) and +127(included).
  • Since all integer literals in Java by definition are of type int(32 bits), sometimes you will need to cast the value you are assigning, when assigning the value to a variable of type byte.
  • You do not need casting when..
    • .. you assign decimal values between -128 to +127 to a byte variable.
    • .. you do an operation on 2 constant expressions.
References: 1, 2, 3, 4

Part 01 coming - sometime in the future!