Introduction
Modifiers are keywords that let us fine-tune access to our class and its members, their scope, and behavior in certain situations. For example, we can control which classes/objects can access certain members of our class, whether a class can be inherited or not, whether we can override a method later, whether we should override a method later, etc.
Modifier keywords are written before the variable/method/class (return) type and name, e.g. private int myVar
or public String toString()
.
Modifiers in Java fall into one of two groups - access and non-access:
- Access:
public
,private
,protected
. - Non-access: static, final, abstract, synchronized, volatile, transient and
native
.
native
is not covered in more detail below since it is a simple keyword that marks a method that will be implemented in other languages, not in Java. It works together with the Java Native Interface (JNI). It's used when we want to write performance critical sections of code in more performance-friendly languages (like C).
Want to learn more about access modifiers, as opposed to non-access? If so, check out our article Access Modifiers in Java.
Non-Access Modifiers
These types of modifiers are used to control a variety of things, such as inheritance capabilities, whether all objects of our class share the same member value or have their own values of those members, whether a method can be overridden in a subclass, etc.
A brief overview of these modifiers can be found in the following table:
Modifier Name | Overview |
---|---|
static | The member belongs to the class, not to objects of that class. |
final | Variable values can't be changed once assigned, methods can't be overridden, classes can't be inherited. |
abstract | If applied to a method - has to be implemented in a subclass, if applied to a class - contains abstract methods |
synchronized | Controls thread access to a block/method. |
volatile | The variable value is always read from the main memory, not from a specific thread's memory. |
transient | The member is skipped when serializing an object. |
The static
Modifier
The static
modifier makes a class member independent of any object of that class. There are a few features to keep in mind here:
- Variables declared
static
are shared among all objects of a class (since the variable essentially belongs to the class itself in this case), i.e. objects don't have their own values for that variable, instead, they all share a single one. - Variables and methods declared
static
can be accessed via the class name (instead of the usual object reference, e.g.MyClass.staticMethod()
orMyClass.staticVariable
), and they can be accessed without the class being instantiated. static
methods can only usestatic
variables and call otherstatic
methods, and cannot refer tothis
orsuper
in any way (an object instance might not even exist when we call astatic
method, sothis
wouldn't make sense).
Note: It's very important to note that static
variables and methods can't access non-static
(instance) variables and methods. On the other hand, non-static
variables and methods can access static
variables and methods.
This is logical, as static
members exist even without an object of that class, whereas instance members exist only after a class has been instantiated.
Static Variables
For variables, we use static
if we want the variable to be common/shared for all objects.
Let's take a look at how static
variables behave differently from regular instance variables:
class StaticExample {
public static int staticInt = 0;
public int normalInt = 0;
// We'll use this example to show how we can keep track of how many objects
// of our class were created, by changing the shared staticInt variable
public StaticExample() {
staticInt++;
normalInt++;
}
}
// No instances of StaticExample have been created yet
System.out.println(StaticExample.staticInt); // Prints: 0
// System.out.println(StaticExample.normalInt); // this won't work, obviously
// Let's create two instances of StaticExample
StaticExample object1 = new StaticExample();
// We can refer to static variables via an object reference as well,
// however this is not common practice, we usually access them via class name
// to make it obvious that a variable/method is static
System.out.println(object1.staticInt); // Prints: 1
System.out.println(object1.normalInt); // Prints: 1
StaticExample object2 = new StaticExample();
System.out.println(object2.staticInt); // Prints: 2
System.out.println(object2.normalInt); // Prints: 1
// We can see that increasing object2's staticInt
// increases it for object1 (and all current or future objects of that class)
object1.staticInt = 10;
object1.normalInt = 10;
System.out.println(object2.staticInt); // Prints: 10
System.out.println(object2.normalInt); // Prints: 1 (object2 retained its own value for normalInt as it depends on the class itself)
Static Methods
The most common example of using static
is the main()
method, it is declared as static
because it must be called before any objects exist. Another common example is the Math
class since we use the methods of that class without making an instance of it first (like Math.abs()
).
A good way to think about static
methods is "Does it make sense to use this method without first creating an object of this class?" (e.g. you don't need to instantiate the Math
class in order to calculate the absolute value of a number).
Static methods can be used to access and modify static
members of a class. Though, they're commonly used to manipulate method parameters or compute something and return a value.
These methods are referred to as utility methods:
static int average(int num1, int num2) {
return (num1+num2)/2;
}
This utility method can be used to calculate the average of two numbers, for an example.
As mentioned above, the Math
class is often used for calling static
methods. If we look at the source code, we can notice that it mostly offers utility methods:
public static int abs(int i) {
return (i < 0) ? -i : i;
}
public static int min(int a, int b) {
return (a < b) ? a : b;
}
public static int max(int a, int b) {
return (a > b) ? a : b;
}
Static Blocks
There's also a static
block. A static
block gets executed only once when the class is first instantiated (or a static
member has been called, even if the class isn't instantiated), and before the rest of the code.
Let's add a static
block to our StaticExample
class:
class StaticExample() {
...
static {
System.out.println("Static block");
}
...
}
StaticExample object1 = new StaticExample(); // "Static block" is printed
StaticExample object2 = new StaticExample(); // Nothing is printed
Irrespective of their position in the class, static
blocks are initialized before any other non-static blocks, including constructors:
class StaticExample() {
public StaticExample() {
System.out.println("Hello from the constructor!");
}
static {
System.out.println("Hello from a static block!");
}
}
Instantiating this class would output:
Hello from a static block!
Hello from the constructor!
If multiple static
blocks are present, they will run in their respective order:
public class StaticExample {
static {
System.out.println("Hello from the static block! 1");
}
public StaticExample() {
System.out.println("Hello from the constructor!");
}
static {
System.out.println("Hello from the static block! 2");
}
}
Instantiating this class would output:
Hello from the static block! 1
Hello from the static block! 2
Hello from the constructor!
Static Imports
As already mentioned, it's better to call static
members prefixed with the class name, rather than the instance name. Also, in some cases, we never really instantiate a class with static
methods, such as the Math
class, which offers numerous utility methods regarding mathematics.
That being said, if we use a class' static
members often, we can import individual members or all of them using a static import
. This allows us to skip prefixing their calls with the class name:
package packageOne;
public class ClassOne {
static public int i;
static public int j;
static public void hello() {
System.out.println("Hello World!");
}
}
package packageTwo;
static import packageOne.ClassOne.i;
public class ClassTwo {
public ClassTwo() {
i = 20;
}
}
Or, if we'd like to import all static
members of ClassOne
, we could do it like so:
package packageTwo;
import static packageOne.ClassOne.*;
public class ClassTwo {
public ClassTwo() {
i = 20;
j = 10;
}
}
The same applies to methods:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
package packageTwo;
import static packageOne.ClassOne.*;
public class ClassTwo {
public ClassTwo() {
hello();
}
}
Running this would output:
Hello World!
This may not seem that important, but it helps when we call many static
members of a class:
public int someFormula(int num1, int num2, int num3) {
return Math.ceil(Math.max(Math.abs(num1), Math.abs(num2))+Math.max(Math.abs(num2), Math.abs(num3)))/(Math.min(Math.abs(num1), Math.abs(num2))+Math.min(Math.abs(num2), Math.abs(num3)));
}
// Versus...
import static java.lang.Math.*;
public int someFormula(int num1, int num2, int num3) {
return ceil(max(abs(num1), abs(num2))+max(abs(num2), abs(num3)))/(min(abs(num1), abs(num2))+min(abs(num2), abs(num3)));
}
The final
Modifier
The keyword final
can have one of three meanings:
- to define named constants (variables whose values can't change after initialization)
- to prevent a method from being overridden
- to prevent a class from being inherited
Named Constants
Adding the final
modifier to a variable declaration makes that variable unchangeable once it's initialized.
The final
modifier is often used together with the static
modifier if we're defining constants. If we only apply static
to a variable, it can still be changed easily. There's also a naming convention tied to this:
static final double GRAVITATIONAL_ACCELERATION = 9.81;
Variables such as these are often included in utility classes, such as the Math
class, accompanied by numerous utility methods.
Though, in some cases, they also warrant their own classes, such as Constants.java
:
public static final float LEARNING_RATE = 0.3f;
public static final float MOMENTUM = 0.6f;
public static final int ITERATIONS = 10000;
Note: when using final
with object reference variables, be careful of what type of behavior you expect. Consider the following:
class MyClass {
int a;
int b;
public MyClass() {
a = 2;
b = 3;
}
}
final MyClass object1 = new MyClass();
MyClass object2 = new MyClass();
The reference variable object1
is indeed final
and its value can't change, but what does that mean for reference variables anyway? It means that object1
can't change which object it is pointing to anymore, but we can change the object itself. This is something that often confuses people:
// object1 = object2; // Illegal!
object1.a = 5; // Perfectly fine
Method parameters can also be declared final
. This is used to make sure our method doesn't change the parameter it receives when it's called.
Local variables can also be declared final
. This is used to make sure the variable receives a value only once.
Preventing Overriding
If you specify the final
modifier while defining a method, any future subclass can't override it.
class FinalExample {
final void printSomething() {
System.out.println("Something");
}
}
class ExtendsFinalExample extends FinalExample {
// This would cause a compile-time error
//void printSomething() {
// System.out.println("Some other thing");
//}
// However, you are perfectly free to overload this method
void printSomething(String something) {
System.out.println(something);
}
}
One small bonus of declaring truly final methods as final
is a slight performance boost whenever we call this method. Usually, Java resolves method calls dynamically at run-time, but with methods declared final
, Java can resolve a call to it at compile time, or if a method is really small it can simply inline calls to that method since it "knows" that it won't be overridden. This eliminates the overhead associated with a method call.
Preventing Inheritance
This usage of final
is fairly straight-forward, a class defined with final
cannot be inherited. This of course implicitly declares all methods of that class final as well (they can't be overridden if the class can't be inherited in the first place).
final class FinalExample {...}
The abstract
Modifier
The abstract
modifier is used to define methods that will be implemented in a subclass later on. Most often it's used to suggest that some functionality should be implemented in a subclass, or (for some reason) it can't be implemented in the superclass. If a class contains an abstract
method, it must also be declared abstract
.
Note: You can not create an object of an abstract
class. In order to do that, you need to provide an implementation for all the abstract
methods.
An example would be if we had a simple class called Employee
that encapsulates data and methods for an employee. Let's say that not every employee is paid in the same way, some types of employees are paid by the hour and some are paid a fixed salary.
abstract class Employee {
int totalHours; // In a month
int perHour; // Payment per hour
int fixedRate; // Fixed monthly rate
...
abstract int salary();
...
}
class Contractor extends Employee {
...
// Must override salary if we wish to create an object of this class
int salary() {
return totalHours*perHour;
}
...
}
class FullTimeEmployee extends Employee {
...
int salary() {
return fixedRate;
}
...
}
class Intern extends Employee {
...
int salary() {
return 0;
}
...
}
If a subclass doesn't provide an implementation to all abstract
methods in the superclass, it has to be declared as abstract
as well, and an object of that class can't be created.
Note: abstract
is used heavily with polymorphism, e.g. we'd say ArrayList<Employee> employees = new ArrayList();
, and add Contractor
, FullTimeEmployee
, and Intern
objects to it. Even though we can't create an object of the Employee
class, we can still use it as a reference variable type.
The synchronized
Modifier
When two or more threads need to use the same resource, we somehow need to make sure that only one of them has access to it at a time, i.e. we need to synchronize them.
This can be achieved in several ways, and one simple and readable way (albeit with somewhat limited usage) is by using the synchronized
keyword.
An important concept to understand before you see how to use this keyword is the concept of a monitor. Every object in Java has its own implicit monitor associated with it. A monitor is a "mutually exclusive" lock, meaning that only one thread can "own" a monitor at a time. When a thread enters the monitor, no other thread can enter it until the first thread exits. This is what synchronized
does.
Threads are beyond the scope of this article so I will focus on the syntax of synchronized
only.
We can synchronize access to methods, and blocks of code. Synchronizing blocks of code works by providing an object instance that we want to synchronize access to and the code that we want to perform related to that object.
class SynchronizedExample {
...
SomeClass object = new SomeClass();
....
synchronized(object) {
// Code that processes objects
// only one thread at a time
}
// A synchronized method
synchronized void doSomething() {
...
}
...
}
The volatile
Modifier
The volatile
modifier tells Java that a variable can be changed unexpectedly by some other part of the program (like in multi-threaded programming), and so that variable's value is always read from main memory (and not from CPU cache), and that every change to the volatile
variable is stored in main memory (and not in CPU cache). With this in mind, volatile
should only be used when necessary, since reading/writing to memory every time is more expensive than doing so with CPU cache and only reading/writing to memory when necessary.
In simplified terms - when a thread reads a volatile
variable value, it is guaranteed that it will read the most recently written value. Basically, a volatile
variable does the same thing that synchronized
methods/blocks do, we just can't declare a variable as synchronized
.
The transient
Modifier
When a variable is declared as transient
, that means that its value isn't saved when the object is stored in memory.
transient int a;
means that when we write the object to memory, the contents of "a" will not be included. For example, it's used to make sure that we don't store private/confidential information in a file.
When we try to read an object that contains transient
variables, all transient
variable values will be set to null
(or default values for primitive types), no matter what they were when we wrote the object to the file. Another example of usage would be when a variable's value should be derived based on other data (such as someone's current age), and isn't part of the persistent object state.
Note: Something very interesting happens when we use transient
and final
together. If we have a transient final
variable that is evaluated as a constant expression (Strings or primitive types) the JVM will always serialize it, ignoring any potential transient
modifier. When transient final
is used with reference variables, we get the expected, default behavior of transient
.
Conclusion
Modifiers are keywords that let us fine-tune access to our class and its members, their scope and behavior in certain situations. They provide fundamental traits for our classes and their members. Every developer should thoroughly be acquainted with them to make the best use of them.
Like being aware that protected
access control can easily be bypassed, or the transient final
modifier when it comes to constant expressions.