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 variablesfinal
- Applicable to classes, methods, and variablesabstract
- Applicable to classes and methods
While the remaining four are less frequently used:
synchronized
- Applicable to methodsvolatile
- Applicable to variablestransient
- Applicable to variablesnative
- 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.