Java 11 Developer Certification - Variable Declaration and Initialization

September 27, 2020

What we are covering in this lesson

  1. An introduction to Primitive data types
  2. Declaring Primitive data types
  3. Initializing a local variable
  4. Partial initialization of a variable
  5. Narrowing and Widening
  6. Casting
  7. Declare and Initialize Variables - New Java 8 feature

An introduction to Primitive data types

Java supports two types of data, reference data types and primitive data types. The data in Primitive data types is stored in memory and is NOT a location reference to the original data, making it faster and more efficient in terms of performance.

Since Java allows primitive datatypes which are not objects,Java is not considered to be a pure Object Oriented Language.

There are 8 Primitive datatypes along with its wrapper class in java as mentioned below:

Primite Data Type Wrapper Class Range
byte Byte -128:128
char Character \u0000:\uFFFF
short Short -32768:32767
int Integer (-2^31):((2^31)-1)
long Long (-2^63):((2^63)-1)
float Float (1.4x10^-45):(3.4x0^38)
double Double (4.9x10^-324):(1.79x10^308)
boolean Boolean true or false

Important things to remember regarding primitive data types

  • Four datatypes are signed - byte, short, int and long. These datatypes along with char are represented in two’s complement format, where the leftmost bit represents the sign (+/-).
  • float and double are floating point representations, used when precision is critical.

Declaring Primitive data types

We declare a primitive data type with the data type and variable name. The variable name must be a valid identifier and multiple variables or same data type are allowed on a single line, but not multiple variables of different types without a break (semi-colon).

public class FirstJavaClass {
    public static void main(String[] args) {
        boolean boolVar;
        int intVar;
        float floatVar;
        char charVar;
        short shortVar;
        long longVar;
        double doubleVar;
        float floatVar;

        //This is invalid - int a, float b, char c;
        //Below is valid
        int a; float b; char c;    //Note the semicolon in between different datatypes

        //This is an example of assigning values to variables
        byte newByteVar = 1;
        int newIntVar = 2;

        //Long variable values can be suffixed using 'l' or 'L'
        long newLongVar = 1l;
        long anotherLongVar = 1L;

        //Double variable values can be suffixed using 'd' or 'D'
        double newDoubleVar = 1d;
        double anotherDoubleVar = 1D;

        //Float variable values can be suffixed using 'f' or 'F'
        float newFloatVar = 1f;
        floar anotherFloatVar = 1F;

        //Some more examples
        byte b = 0b1111111;     //binary 127 prefix 0b
        short s = 0177;         //octal 127 prefix 0 only
        int i = 0x007F;         //hexadecimal 127 prefix 0x
        byte b1 = 'a';          //equivalent to decimal 97
        char c1 = 'a';
        double d1 = 'a';        //again equivalent to 97.0
    }
}

You can try with your own numbers as well, and see for yourself what is valid and what is not!

Initializing a local variable

As discussed earlier, we need to initialize a variable before using it. Although, this doesnt apply for static variables and instance variables since default values are assigned to them based on the data type.

public class anotherClass {
    int intVar;     //This is a static variable, initialized to default 0 for int

    public static void main(String[] args) {
        int anotherVar;

        //Below line prints 0
        System.out.println("Static variable value is : " + intVar);

        //Below line gives compile time exception stating
        //Variable 'anotherVar' might not have been initialized
        System.out.println("Value is " + anotherVar);
    }
}

Note: You may have uninitialized variable if you dont plan to use it.

Partial initialization of a variable

There are a few situations where a variable may be partially initialized

  • Initializing in if block without a corresponding else block
  • Initializing in switch statement but not in default statment
  • Initializing in while loop.

Note: Initialization in a do/while loop is fine since it executes atleast once.

public static void main(String[] args) {
    int i;
    boolean isTrue = true;

    if (isTrue) {
        i = 123;
    }

    //Below line will show a compile time error since i is initialized only in if and not in else
    System.out.println("Value for i is : " + i)
}

The above example shows partial initialization in if. Similar initializations in while loop and switch statements will also throw compile time error when trying to use the variable i.

If we use if (true) then compiler will not give any error since the statement will always get executed.

public static void main(String[] args) {
    int i;
    int case = 2;

    switch (case) {
        case 0:
        case 1:
        case 2:
        case 3:
            i = 5;
            break;
        case 4:
        case 5:
            i = 3;
        default:
            i = 0;
    }

    System.out.println("Value for i is : " + i);
}

The above code works well since we have initialized i within default block. If we remove that part of code, it will throw a compile time error.

Narrowing and Widening

Narrowing is where you assign a larger primitive data literal or variable to a smaller one. Widening is where you put something small in a larger variable. In simple words, if you assign an int variable to a byte variable, its called narrowing and if you assign a byte variable to int variable, its widening.

You might be thinking why not just use doubles and longs everywhere? Theoretically yes, but in actual implementation you will be eating up as much as twice the memory unnecessarily, which could lead to problems in larger applications.

As mentioned in this table, the range mentioned are the respective MINVALUE and MAXVALUE for individual datatypes.

To perform narrowing, we need to manually type-cast the variable as per the datatype of the destination variable. We will discuss about casting in the next topic. Lets look at narrowing using a java code example.

public static void main(String[] args) {
    //Below values are within range
    byte b  = 127;
    short s = 32767;
    char c = 65535;
    //Below values will give compiler error since it is out of range for each datatype
    byte b1 = 128;
    short s = 32768;
    char c = 65536;

    int abc = 1;
    //Below code will again throw compile time error as no typecast done
    byte xyz = abc;
    char c123 = abc;
}

Note: Each numeric value literal that contains a decimal is by default a double, and unlike integral conversions, a narrowing conversion of a floating point number always requires the casting operator.

For widening, we do not need to do a manual typecase as Java handles it on its own. Lets see a widening example.

public static void main(String[] args) {
    long l = 0;     //widening int (0) to long
    double d = 0.0f;    //widening float (0.0f) to double
}

Casting

Casting is a way to force the compiler to overlook its narrowing and widening checks, because you have knowledge of the actual values occurring in the programme during execution. You may cast to a larger sized variable (widening) or smaller sized variable (narrowing).

public static void main(String[] args) {
    //Correct way to implement narrowing is as below
    int a = 200;
    byte b = (byte)a; // narrowing
}

We do have a problem with casting though. If your value doesn’t fall into the valid value range, your data may underflow or overflow. Underflow is defining or casting a value less than the minimum value for the datatype. Overflow is defining or casting a value greater than the maximum value for the datatype.

If you cast from a float or double to an int or long, it will truncate the number to a whole number.

public static void main(String[] args) {
    float f = 111.123f;
    int i = (int) f;

    System.out.println("Value of i is : " + i); //will print 111
}

Declare and Initialize Variables - New Java 8 feature

As we already know, casting can give unexpected results if not done properly. So instead, we can use the new Java 8 feature which allows us to evaluate a number as an unsigned String. Lets have a look at the code.

public static void main(String[] args) {
    int i = 123;
    String sInt = Integer.toUnsignedString(i);
    System.out.println("Value of unsigned String is : " + sInt); //will print 111
}

We also have something called binary literals, introduced in Java 7. The values must be prefixed with “0b” or “0B”.

public static void main(String[] args) {
    //8-bit 'byte' values
    byte b1 = (byte) 0b01111111;    //127
    byte b2 = (byte) 0b00100001;    //33

    //16-bit 'short' value
    short s1 = (short) 0b10100001_01000101; //-24251

    //32-bit 'int values
    int i1 = 0b10100001_01000101_10100001_01000101; //-1589272251
    int i2 = 0b101;                                 //5
    int i3 = 0B00000000_00000000_00000000_00000101; //5
}

Some important points to remember

  • Literals with decimal default to double, not float.
  • Doubles and float don’t overflow, since they’re approximated.
  • Local variable primitives aren’t initialised.
  • Class and static and instance members are initialised by default.
  • Null is not a valid type for any primitive data type.

The char data type is based on the original Unicode specification which defined characters as fixed-width 16-bit entities. The Unicode Standard has since been changed to allow for characters whose representation requires more than 16 bits. The range of legal code points is now U+0000 to U+10FFFF, known as Unicode scalar value. The set of characters from U+0000 to U+FFFF is sometimes referred to as the Basic Multilingual Plane, BMP. Characters whose code points are greater than U+FFFF are called supplementary characters.

The Java platform uses the UTF-16 representation in char arrays, and in the String and StringBuffer classes. In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogates range, and that’s Unicode D800 to Unicode DBFF, the second from the low-surrogates range, Unicode DC00 to Unicode DFFF.

You can read more about Unicode data representation in char datatype here