Types of variables

Division One

Based on the type of value represented by a variable, all variables are divided into two types:

import java.lang.*;

class Test {
    public static void main(String[] args) {
        int x = 10; // primitive variable: used to represent primitive values
        Student s = new Student(); // reference variables: used to refer objects
    }
}

Division Two

Based on position of declaration and behavior, all variables are divided into three types:

Instance variables (object level variables)

If the value of a variable is varied from object to object, such types of variables are called instance variables

For every object, a separate copy of instance variables will be created. For example if you have a Student object, the value of each student is different, i.e, each student object has a different name and roll number.

Diagram illustrating instance variables.
Diagram illustrating instance variables.

Instance variables should be declared withing the class directly, but outside any method or block or constructor.

Instance variable is created at the point of object creation (e.g. when student object is created) and destroyed at the time of object destruction. Hence, the scope of instance variable is exactly the same as the scope of object.

Instance variables are stored in heap memory as part of object.

import java.lang.*;

class Test {

    int x = 10;

    public static void main(String[] args) {
        System.out.println(x); // CTE: non static variable x cannot be referenced from a static context

        Test t = new Test;
        System.out.println(t.x); // 10
    }

    public void m1() {
      System.out.println(x); // 10
    }
}

We can't access instance variable directly from static area, but we can access by using object reference.

But we can access instance variables directly from instance area.

import java.lang.*;

class Test {
    int x;
    double d;
    boolean b;
    String s;

    public static void main(String[] args) {
        Test t = new Test;
        System.out.println(t.x); // 0
        System.out.println(t.d); // 0.0
        System.out.println(t.b); // false
        System.out.println(t.s); // null
    }
}

For instance variables, JVM will always provide default values, and we are not required to initialise them.

Instance variables are also known as object level variables or attributes.

Static variables (also known as class level variables or fields)

If the value of a variable is not varied from object to object, it is not recommended to declare such a variable as an instance variable, it should be declared at class level using static modifier.

In the previous example, the student name and role number were varied from object to object, but if we were to add an extra field like "schoolName", it would be the same for all students as they attend the same school. Meaning, the "schoolName" variable would be created for each student, wasting memory and potentially impacting performance.

A variable like "schoolName" that is the same across all objects should not be declared as an instance variable. It should be declared as a static variable at class level:

import java.lang.*;

class Student {
    String name;
    int rollNo;
    static String schoolName;
}

When a variable is declared as static variable, at class level, only one copy will be created and this copy will be shared for all objects of the class.

The main difference between an instance variable and a static variable is that an instance variable is created for every object, whereas for static variable only one variable will be created and this copy will be shared for all objects of the class.

Like instance variables, static variables should be declared withing the class directly, but outside any method or block or constructor.

Static variables are created when the class is loaded into memory and are destroyed when the class is unloaded from memory. Hence, the scope of a static variable is same as scope of the class file.

Order of execution the running a java class. What happens after user runs:

java Test
  1. Starts JVM
  2. JVM creates and starts main thread
  3. Main thread locates Test.class file(if Test.class file is not found then exception in thread main "Error: Could not find or load main class Test" & "Caused by: java.lang.ClassNotFoundException: Test")
  4. Load Test.class into memory (static and instance variables created here)
  5. Execute main() method
  6. After execution completed, unload Test.class(static and instance variables destroyed here)
  7. JVM terminates main thread
  8. Shutdown JVM

Static variables are stored in method area

Static variables can be accessed by using either object reference or class name. But it is recommended to use class name to access static variable to differentiate static variables to instance variables, makes the code more readable.

import java.lang.*;

class Test {

    static int x = 10;

    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.x); // Accessing static variable using object reference (not recommended, reduces readability as it is difficult to differentiate between static an instance variables)
        System.out.println(Test.x); // Accessing static variable using class name (recommended approach)
        System.out.println(x); // When within the same class, static variables can be accessed directly, not required to use class name in this instance
    }

    public void method() {
        System.out.println(x); // When within the same class, static variables can be accessed directly, not required to use class name in this instance
    }
}

Static variable can be accessed directly from both instance and static area

import java.lang.*;

class Test {

    static int x = 10;

    public static void main(String[] args) {
        System.out.println(x); // Static variable accessed from static area
    }

    public void method() {
        System.out.println(x); // Static variable accessed from instance area
    }
}

Where static variables are declared but not initialised, JVM will provide default values.

import java.lang.*;

class Test {

    static int x;
    static double d;
    static String s;

    public static void main(String[] args) {
        System.out.println(x); // 0
        System.out.println(d); // 0.0
        System.out.println(s); // null
    }
}
import java.lang.*;

class Test {

    static int x = 10;
    int y = 20;

    public static void main(String[] args) {
        Test t1 = new Test();
        t1.x = 888; // modifying static variable means all object's "x" will now be 888 instead of 10
        t1.y = 999;

        Test t2 = new Test();
        System.out.println(t1.x); // 888
        System.out.println(t1.y); // 999
        System.out.println(t2.x); // 888
        System.out.println(t2.y); // 20
    }
}

Local variables (also referred to as temporary variables, stack variables, automatic variables)

A local variable is a variable declared inside a method, static block or constructor for temporary use. Hence, local variables are only accessible within the method, static block or constructor they are declared.

Local variables are stored in stack memory and are created when executing the block in which they are declared and destroyed when the block execution completes. Hence, the scope of a local variable is the same as the block it's declared in.

Local variables are considered thread safe because for every thread, a separate copy of the variable is created.

import java.lang.*;

class Test {
    public static void main(String[] args) {
        int i = 0;
        for(int j = 0; i < 3; j++){
          i = i + j;
        }
        System.out.println(i);
        System.out.println(j); // CTE: Cannot find symbol, Symbol: variable j. Because j is out of scope when outside the for loop
    }
}
import java.lang.*;

class Test {
    public static void main(String[] args) {
        try {
            int j = Integer.parseInt("ten"); // CTE: Cannot find symbol, Symbol: variable j. Because j is out of scope when outside the try block
        } catch(NumberFormatException ex) {
            j = 10;
        }
        System.out.println(j); // CTE: Cannot find symbol, Symbol: variable j. Because j is out of scope when outside the try block
    }
}

The examples above demonstrates the scope of local variables.

JVM does not provide default values for local variables. So it is important to explicitly initialise local variables before using them.

import java.lang.*;

class Test {
    public static void main(String[] args) {
        int x; // This is fine, because even though it is not initialised, it is not being used anywhere
        System.out.println("Hello");
    }
}

The example above will work fine because even though it is not initialised, it is not being used anywhere

import java.lang.*;

class Test {
    public static void main(String[] args) {
        int x;
        System.out.println(x); // CTE: variable x might not have been initialised
    }
}
import java.lang.*;

class Test {
    public static void main(String[] args) {
        int x;
        if(args.length > 0) x = 10;
        System.out.println(x); // CTE: variable x might not have been initialised
    }
}

The example above fails to compile because the local variable 'x' is conditionally initialised. The issue is 'x' is only set when args are passed, when args not passed, 'x' is not initialised. To fix this, either initialise the local variable when it is declared or initialise it in a else block:

import java.lang.*;

class Test {
    public static void main(String[] args) {
        int x;
        if(args.length > 0) x = 10;
        else { x = 20; }
        System.out.println(x);
    }
}

In general, it is not recommended to initialise a local variable inside a logical block (e.g if, else) because at runtime there is no guarantee they will execute.

On the other hand it is recommended to initialise a local variable at declaration with a default value, i.e. 0 for int null for String etc.

The only applicable modifier for local variables is final:

import java.lang.*;

class Test {
    public static void main(String[] args) {
        public int a = 10; // CTE: illegal start of expression
        private int b = 10; // CTE: illegal start of expression
        protected int c = 10; // CTE: illegal start of expression
        static int d = 10; // CTE: illegal start of expression
        transient int e = 10; // CTE: illegal start of expression
        volatile int f = 10; // CTE: illegal start of expression
        int g = 10; // Valid
        final int h = 10; // Valid
    }
}

When declaring a variable without using any modifier, that variable is by default default, meaning that variable is accessible from any other class in the same package. This rule is only applicable to instance and static variables, but not local variables

Conclusions

For instance and static variables, JVM provides default values, hence it is not required to explicitly initialise instance and static variables. Whereas local variables, JVM does not provide default values, so local variables explicitly initialised before using the local variable.

Instance and static variables can be accessed by multiple threads simultaneously, hence, these are not thread safe. Whereas local variables, for every thread, a separate copy is created, hence local variables are thread safe.

Which type of variables are thread safe
Type of variableThread safe?
instance variableno
static variableno
local variableyes

Every variable in java is either instance, static or local. Every variable in java should be either primitive or reference. Hence, various possible combinations of variables in java.

Diagram illustrating that all variables types in java can be primitive or reference variables.
Diagram illustrating that all variables types in java can be primitive or reference variables.
import java.lang.*;

class Test {
    int x = 10; // instance primitive
    static String s = "abc"; // static reference
    public static void main(String[] args) {
        int[] y = new int[3]; // local reference
    }
}

Uninitialised arrays

When it comes to arrays, similar rules apply. Instance and static arrays are initialised with default values, when they are not explicitly initialised. Local arrays are not initialised with default values.

import java.lang.*;

class Test {
    int[] x;
    static int[] y;
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.x); // null
        System.out.println(t.x[0]); // RTE: NullPointerException

        System.out.println(Test.y); // null
        System.out.println(Test.y[0]); // RTE: NullPointerException

        int[] z;
        System.out.println(Test.z); // CTE: variable z might not have been initialised
        System.out.println(Test.z[0]); // CTE: variable z might not have been initialised
    }
}

In the example above, when static and instance arrays are declared but not initialised, the default value is null. When you try to access a value of a null array you get a NullPointerException.

For local array, when it is declared but not initialised, a compile time error is thrown saying value might not be initialised.

import java.lang.*;

class Test {
    int[] x = new int[3];
    static int[] y = new int[3];
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.x); // [I@181ea (string representation of an array object in Java) from toString() method
        System.out.println(t.x[0]); // 0

        System.out.println(Test.y); // [I@181ea (string representation of an array object in Java) from toString() method
        System.out.println(Test.y[0]); // 0

        int[] z = new int[3];
        System.out.println(Test.z); // [I@181ea (string representation of an array object in Java)  from toString() method
        System.out.println(Test.z[0]); // 0: Once array is created, every element is initialised with a default value, irrespective of whether it is a local, instance or static array
    }
}

In the example above, when static and instance arrays are declared and initialised, the value is the string representation of an array object. When you try to access a value of the arrays array you get a 0 because once array is created, every element is initialised with a default value. This applies to all arrays instance, static and local.