Understanding non-access modifier keywords in Java

The non-access modifier keywords in Java are a group of keywords that you can apply to a Java class and its members right during their declarations.

Together with access modifier keywords, the non-access modifier keywords will help you control how your Java application structure (classes, methods and variables) will behave.

As of today, there are currently seven non-access modifier keywords in the Java language.

The three most used modifier keywords are:

  • static - Applicable to methods and variables
  • final - Applicable to classes, methods, and variables
  • abstract - Applicable to classes and methods

While the remaining four are less frequently used:

  • synchronized - Applicable to methods
  • volatile - Applicable to variables
  • transient - Applicable to variables
  • native - Applicable to methods

This tutorial will help you learn how these non-access modifier keywords work when applied to Java classes, constructors, methods, and variables.

Let’s start with the static keyword.

The static modifier explained

The static modifier allows you to create methods and variables that exist independent from the object instances of a class.

By declaring a method or variable as static, you can call them without creating a new instance of the class.

For example, suppose you have a Person class with the following variable and method:

class Person {
    static String name = "Nathan";

    static void greetings(){
        System.out.println("Hello from" + name);
    }
}

Then anytime you need to make use of the name variable or the greetings() method of the Person class, you don’t need to create a new Person object.

You can call the Person class members directly as shown below:

public class Main {
    public static void main(String[] args) {
        Person.name = "Jack";
        Person.greetings(); // Hello from Jack
    }
}

If you have some Java programming experience, you may notice that the static modifier is applied to many of the Math class members.

This is why you are able to call on Math class members like Math.PI variable and Math.max() method without creating a new instance of the Math class.

Now that you know about the static modifier, let’s learn about the final modifier.

The final modifier explained

The final modifier allows you to create classes, methods, and variables that can’t be modified.

When you apply the final modifier to a variable, then the variable’s value will remain constant:

class Person {
    final String name = "Nathan";
}

When you create an instance of the Person class above, you won’t be able to change the value of the name variable as shown below:

public class Main {
    public static void main(String[] args) {
        Person developer = new Person();
        developer.name = "Jack"; 
        // ^ Error: cannot assign a value to final variable
    }
}

When you declare a method using the final modifier, then the method can’t be overridden by the class that inherits the method.

Suppose you have the following Person class with a final method:

class Person {
    final void greetings(){
        System.out.println("Hello World!");
    }
}

Then the class that inherits the Person class won’t be able to override the greetings() method above:

public class Main extends Person{
    @Override
    void greetings() {
    // ^ Error: overridden method is final
        super.greetings();
    }
}

Finally, when you apply the final modifier to a class declaration, then you won’t be able to create a subclass that inherits the class’ methods and variables.

Here’s how to declare a final class:

final class Person {
    String name = "Nathan";
}

Any class that tries to extends the Person class will throw an error: cannot inherit from final class.

The final modifier is frequently used together with the static modifier to create a utility or helper class that you can use for your application.

The Math class is one example of such a helper class:

public static final double PI = 3.141592653589793;

By applying the final modifier, you can’t accidentally change the value of Math.PI variable.

The abstract modifier explained

The abstract modifier is mainly used on Java classes and methods. Any class marked as abstract needs to be extended and can’t be instantiated.

Suppose you have the following abstract class:

abstract class Person {
    String name = "Nathan";
}

Then you won’t be able to instantiate an object of the Person class as follows:

public static void main(String[] args) {
    Person person = new Person();
    // ^ Error: Person is abstract; cannot be instantiated
}

An abstract class can have both abstract and normal methods. An abstract method must be overridden by the class inheritors.

When you create an abstract method, you only need to declare the method and its parameters. You don’t need to create the method’s body block as shown below:

abstract class Person {
    abstract void greetings(String name);
}

The class that inherits the Person class above will need to override the greetings method to prevent any error:

class Developer extends Person {
    @Override
    void greetings(String name) {
        System.out.println("Hello from Developer "+ name);
    }
}

Now you’ve learned about the three most frequently used Java non-access modifiers.

Let’s continue with learning the remaining four non-access modifiers, starting with the synchronized modifier.

The synchronized modifier explained

The synchronized modifier allows you to control the access of a shared method and prevent the method from being accessed by multiple threads at the same time.

The modifier is useful when you want to block access to a shared resource when it’s being used.

For example, suppose you have a method that prints a number from 1 to five as follows:

class Number {
    void printNumbers(String threadName) {
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + ": "+ i);
        }
    }
}

Now suppose that you have two different threads that call the printNumbers() method from the Number object as follows:

class FirstThread extends Thread {
    Number num;

    FirstThread(Number num) {
        this.num = num;
    }

    public void run() {
        num.printNumbers("First thread");
    }
}

class SecondThread extends Thread {
    Number num;

    SecondThread(Number num) {
        this.num = num;
    }

    public void run() {
        num.printNumbers("Second thread");
    }
}

When you start both threads from your main() method, then both threads will execute the printNumbers() method, creating an inconsistent output:

public static void main(String[] args) {
    Number num = new Number();
    FirstThread ft = new FirstThread(num);
    SecondThread st = new SecondThread(num);
    ft.start();
    st.start();
}

The output will vary each time you run the program above:

# First run
First thread: 1
Second thread: 1
First thread: 2
Second thread: 2
First thread: 3
Second thread: 3
First thread: 4
Second thread: 4
First thread: 5
Second thread: 5

# Second run
Second thread: 1
Second thread: 2
Second thread: 3
Second thread: 4
Second thread: 5
First thread: 1
First thread: 2
First thread: 3
First thread: 4
First thread: 5

# Third run
First thread: 1
Second thread: 1
Second thread: 2
Second thread: 3
Second thread: 4
Second thread: 5
First thread: 2
First thread: 3
First thread: 4
First thread: 5

To fix this inconsistency, you can declare two different Number objects to be passed to the thread constructors as shown below:

public static void main(String[] args) {
    Number numOne = new Number();
    Number numTwo = new Number();
    FirstThread ft = new FirstThread(numOne);
    SecondThread st = new SecondThread(numTwo);
    ft.start();
    st.start();
}

Or you can declare the printNumbers() method as synchronized as follows:

class Number {
    synchronized void printNumbers(String threadName) {
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + ": "+ i);
        }
    }
}

By using the synchronized modifier, the printNumbers() method will be blocked from another thread access as long as it’s being used by the first thread that gains access to the method.

The volatile modifier explained

The volatile keyword modifies Java variables in a similar way to how the synchronized keyword modifies Java methods.

The volatile modifier enables consistency in the reading and writing process of a variable that’s accessed by multiple threads.

A thread may store a copy of a variable in the CPU cache. Each time the thread updates the value of that variable, only the variable copy in the CPU cache is updated while the main variable in the CPU memory still retains the old value.

By using the volatile modifier, Java will synchronize the variable update from the CPU cache to the main copy in the CPU memory.

Here’s how to declare a volatile variable:

class Person {
    volatile String name = "Nathan";
}

Both synchronized and volatile may be confusing if you are new to Java, but don’t worry because they are less frequently used than the first three modifiers.

The transient modifier explained

The transient modifier allows you to skip a variable value from the object serialization process, hiding the value from being displayed in the deserialization process.

A transient variable requires a class that implements the Serializable class.

Here’s an example of a Person class with both transient and normal variables:

import java.io.Serializable;  

class Person implements Serializable {
    transient int id = 779072;
    String name = "Nathan";
}

When you serialize the object instance of the class above, then the transient variable id won’t be written to the output file.

Then when you deserialize it back to an object, only the name variable value would be available for you.

The value of the id variable will be reverted to the default value of its data type. In this case, the default value of the Integer type is 0.

The native modifier explained

The native modifier allows you to create abstract methods that are implemented using native code.

The method signature looks as follows:

class Person {
    native void greetings();
}

The native keyword above means that the greetings() method is implemented in a platform-independent native code.

To make the method works, you need to import the native library that implements the greetings() method. This native code is usually written in C or C++.

The native code can be created, compiled, and linked to your java program using Java Native Interface (or JNI for short)

How to use the JNI is beyond the scope of this tutorial. Suffice to say here that the native modifier allows you to call on methods that are implemented using native code and linked to your Java application through JNI.

Conclusion

You’ve learned about non-access modifiers in Java and how they can affect the behavior of your Java application structures.

Don’t worry if you’re still confused about them. As you write more Java applications, the use of these modifiers will be instinctive to you.

Take your skills to the next level ⚡️

I'm sending out an occasional email with the latest tutorials on programming, web development, and statistics. Drop your email in the box below and I'll send new stuff straight into your inbox!

No spam. Unsubscribe anytime.