In the world of Java programming, understanding modifiers is crucial for writing efficient and secure code. Modifiers provide additional information about classes, methods, and variables, influencing their behavior and accessibility within the program. From controlling access levels to enforcing specific characteristics, modifiers play a vital role in shaping the structure and functionality of Java applications.
In this blog, we’ll delve into the various types of modifiers in Java, exploring their uses, syntax, and best practices. Whether you’re a beginner looking to grasp the basics or an experienced developer aiming to refine your skills, this comprehensive guide will serve as a valuable resource for mastering Java modifiers.
Class Level Modifiers
When writing our own class in Java, we need to provide some extra information to the JVM, such as whether the class is accessible everywhere, if child class creation is allowed, or whether object creation is possible or not, by using appropriate modifiers.
There are a total of 12 modifiers in Java:
public
private
<default>
protected
final
abstract
static
synchronized
native
strictfp
transient
volatile
Among them, there are 8 class-level modifiers:
For top-level classes, only five are allowed: public
, <default>
, final
, abstract
, and strictfp
.
For inner classes, top-level modifiers along with private
, protected
, and static
are permissible.
For Example:
private class Test {
}
Results in a compilation error: “Modifier ‘private’ not allowed here.”
public class Test {
private class Test {
}
}
Compiles without issues because private
and static
are allowed for inner classes.
private class Test {
// CE: modifier 'private' not allowed here.
}
public class Test {
private class Test {
// code compiler without any problem because 'private' and 'static' are allowed for inner classes.
}
static class Test {
// code compiler without any problem because 'static' is allowed for inner classes.
}
}
Similarly, this compiles fine since static
is permitted for inner classes.
Access Specifiers and Modifiers in Java
In C and C++ languages, public
, protected
, <default>
, and private
are considered access specifiers, while all other modifiers are regarded as access modifiers. However, this distinction does not apply to Java, where all modifiers are considered as access modifiers.
public
: Accessible everywhere within packages; even outside packages can access it.<default>
: Accessible everywhere within the current package; from outside the package, it cannot be accessed. Also known as package-level access.protected
: Enables access within the same package and subclasses, even if they are outside the package.private
: Restricts access to within the class where it’s declared, preventing access from other classes.final modifiers
: Applicable for classes, methods, and variables.- Final method: Method implementation is final, not allowed to change in a child class. Compiler Error: Child class cannot override overridden method declared as final in parent.Final class: If a class is final, it cannot be extended, meaning the whole implementation cannot be changed. Inheritance is not possible.Final variable: If a variable is final, its value cannot be changed, acting as a constant.
Advantages and Disadvantages of Final Modifiers:
Advantages of using final
modifiers include enhanced security and ensuring a unique implementation of a class. However, there are disadvantages such as limitations on key Object-Oriented Programming (OOP) features like inheritance (due to final classes) and polymorphism (due to final methods). Thus, it’s generally not recommended to use the final
keyword unless specific requirements necessitate its usage.
Before proceeding, a very silly but extremely important question regarding these concepts comes to my mind,
What are access specifiers and access modifiers in Java, and what is the difference between them?
In Java, access specifiers and access modifiers are used to control the visibility and accessibility of classes, methods, and variables within a program. These modifiers determine which parts of a program can access a particular member (class, method, or variable) and from where.
So,
Access Specifiers: Access specifiers define the scope of a class, method, or variable.
and,
Access Modifiers: Access modifiers are a subset of access specifiers that specifically modify the behavior of classes and members. Let’s take simple example, The final modifier can be applied to classes, methods, and variables. When applied to a class, it means that the class cannot be subclassed. When applied to a method, it means that the method cannot be overridden by subclasses. When applied to a variable, it means that the variable’s value cannot be changed once initialized.
Difference between Access Specifiers and Access Modifiers:
- Access specifiers define the visibility of a class, method, or variable (e.g., public, protected, default, private).
- Access modifiers are a subset of access specifiers and further modify the behavior of classes and members (e.g., final, abstract).
In short, accesAbstract Classs specifiers define where a class, method, or variable can be accessed from, while access modifiers modify the behavior of classes and members. However, it’s important to note that in Java, all of these are considered as access modifiers only.
Abstract Modifier in Java
In Java, the abstract
modifier is applicable only to classes and methods, not to variables.
Abstract Method
Only declaration is available but no implementation is provided for that method. Hence, abstract methods end with a semicolon (;).
Example:
public abstract int getNoOfWheels();
- The child class is responsible for providing an implementation for the parent class’s abstract method.
- If a class contains any abstract method, then it is compulsory for that class to be declared as abstract.
- The main advantage of declaring abstract methods in a parent class is that every child class is compulsory to implement that abstract method.
It’s crucial to note that abstract
methods are solely about declaration and lack implementation. Attempting to combine abstract
with modifiers that imply implementation leads to a compilation error. These illegal combinations include final
, native
, synchronized
, static
, private
, and strictfp
. For instance:
abstract final void m1()
// Results in a compilation error: "Illegal combination of modifiers 'abstract' and 'final'"
Means, abstract methods never deal with implementation. If any modifier pertains to implementation, then such modifier combinations with abstract are illegal. We get the same Compiler Error (CE).
Abstract Class
An abstract class is used for any Java class where object creation is not allowed due to partial implementation. Such types of classes are declared with abstract methods. Instantiation of an abstract class is not possible; we cannot create an object of an abstract class. If attempted, a Compiler Error (CE) is raised: “class is abstract, cannot be instantiated.”
If a class contains at least one abstract method, then it must be declared as abstract, otherwise, a Compiler Error will be encountered. Why? Because if a class contains at least one abstract method, its implementation is not complete, and it’s not recommended to create an object of such a class. To restrict object instantiation, it’s compulsory to declare the class as abstract. Even if a class contains zero abstract methods, we can still declare the class as abstract if we don’t want object creation of that class. For example, HttpServlet
is abstract but doesn’t contain any abstract methods. Similarly, every adapter class is abstract but doesn’t contain any abstract methods.
When extending an abstract class, it’s necessary to implement each and every method present in the abstract class in the child class. If this is not possible, then the child class must be declared as abstract. In such cases, the next level child class is responsible for providing the implementation.
Abstract vs. Final
- Abstract method: Compulsory to override in child class to provide implementation. Final method cannot be overridden; hence, the combination of final and abstract is illegal for methods.
- For final classes, we cannot create a child class, whereas for abstract classes, we should create a child class to provide implementation. Hence, the combination of final and abstract is illegal for classes.
- An abstract class can contain a final method, whereas a final class cannot contain an abstract method.
Example:
abstract class Test {
public final void m1() {
// Implementation
}
}
This is allowed, as an abstract class can contain final methods.
final class Test {
public abstract void m1(); // Not allowed
}
This is not allowed, as a final class cannot contain abstract methods.
It is highly recommended to use the abstract modifier in our day-to-day programming as it promotes several Object-Oriented Programming (OOP) features like polymorphism (method overriding) and inheritance (creating child classes). However, the final modifier suppresses OOP features, so it is not recommended to use it.
strictfp (strict floating point) Modifier
The strictfp
modifier can be used with classes and methods but not for variables. Usually, floating-point results vary from platform to platform, and if we want platform-independent results, we use the strictfp
modifier. A strictfp
method needs to follow IEEE 754 standards to achieve platform-independent results. Since strictfp
always pertains to implementation and abstract
doesn’t, the combination of both modifiers is illegal.
strictfp at the Class Level
If every concrete method inside a class needs to follow IEEE 754 standards when using floating point, we declare that class as strictfp
. However, if the class contains some abstract methods, then that class needs to be defined as abstract. Hence, the combination of strictfp
and abstract
is legal at the class level but illegal at the method level.
Member-Level Modifiers (Method or Variable Level Modifiers)
In Java, member-level modifiers are used to control access to methods or variables within classes. Here’s a breakdown of the commonly used member-level modifiers:
- public member: If a member is declared as public, it can be accessed from anywhere, but the corresponding class should be visible. Before checking member visibility, class visibility must be considered. This means that both the class and member need to be public for access from outside the package.
- default member: If a member is default, it can be accessed within the current package, but it cannot be accessed from outside the package. Hence, it is also known as package-level access.
- private member: If a member is private, it can be accessed only within the current class; outside the class, it cannot be accessed. Abstract methods are available to child classes for providing implementation, whereas private methods are not available to child classes for implementation. Therefore, the combination of abstract and private is illegal.
- protected member: If a member is protected, it can be accessed within the current package and also by child classes outside the package. It is one of the most misunderstood modifiers in Java. Protected access is equivalent to default access plus access for child classes.
- We can access protected members within the current package anywhere using either parent reference or child reference. However, outside the package, we can access them only using child reference. If attempted to access using parent reference, a Compiler Error (CE) will be raised: “member has protected access in that class”.
Visibility rules for member-level modifiers
- Within the same class: All access modifiers (
private
,<default>
,protected
,public
) are allowed. - From child classes of the same package:
<default>
,protected
, andpublic
are allowed, butprivate
is not. - From non-child classes of the same package:
<default>
,protected
, andpublic
are allowed, butprivate
is not. - From child classes of outside packages:
protected
is accessible using child class references only,public
is allowed, and others are not. - From non-child classes of outside packages: Only
public
access is allowed; others are not.
Final Variables
In Java, final
variables are those whose values cannot be changed once they are assigned. Here’s a breakdown of final
variables, let’s first focusing on final instance variables:
Final instance variable
The value of a variable that varies from object to object is called an instance variable or object-level variable. For every object, a separate instance variable copy is created. For instance variables, we don’t require explicit initialization because the JVM always provides default values. However, in the case of final instance variables, the JVM won’t provide a default value. We need to provide initialization explicitly whether it’s used or not; otherwise, we will encounter a Compiler Error (CE): “variable might not have been initialized”.
There are rules for initializing final instance variables:
Rule – Before constructor completion:
Case 1: At the time of declaration:
class Test {
final int x = 10;
}
Case 2: Inside an instance block:
class Test {
final int x;
{
x = 10;
}
}
Case 3: Inside a constructor:
class Test {
final int x = 10;
Test() {
x = 10; // CE: Cannot assign value to final variable
}
}
Note: If we try to initialize it in any method, we’ll get a CE: “cannot assign value to final variable”.
Final Static Variable
A final static variable is used when the value of the variable does not vary from object to object. Such a variable is called a static variable or class-level variable and is not recommended to be declared as an instance variable. It needs to be declared as static, and only a single copy exists at the class level, shared by every object.
In a static variable, the JVM always provides a default value at the time of initialization. We don’t need to do it explicitly. However, in the case of a final static variable, we must perform initialization explicitly; otherwise, we’ll encounter a Compiler Error (CE): “variable might not have been initialized”.
Rule: Before class loading completion, we need to perform initialization.
Case 1: At the time of declaration:
class Test {
final static int x = 10;
}
Case 2: Inside a static block:
class Test {
final static int x;
static {
x = 10;
}
}
Note: We cannot initialize a final static variable inside a method; otherwise, we will get a CE: “cannot assign value to final variable”.
Final Local Variable
In Java, a final
local variable is a variable declared within a method, block, or constructor, and its value remains constant once initialized. These variables are temporary and are stored on the stack, such variables are called local or temporary or stack or automatic variables.
Key points about final
local variables:
- JVM does not provide a default value for local variables. They must be explicitly initialized before use.
- Even if a local variable is declared as
final
, initialization is only required if the variable is used. Unusedfinal
local variables do not need initialization. - The only applicable modifier for local variables is
final
. Attempting to use any other modifiers likepublic
,private
,protected
,static
,transient
, orvolatile
will result in a compilation error. - Formal parameters of a method act as local variables within that method. They can also be declared as
final
, in which case, reassignment within the method is not allowed.
Example of declaring a final
local variable:
void myMethod() {
final int x = 10; // Declaring and initializing a final local variable
// Other code using variable x
}
Formal parameter example:
void myMethod(final int param) {
// param acts as a final local variable within this method
// Reassignment of param is not allowed within this method
}
Note: The only applicable modifier for a local variable is final. If we try to use any other modifier like public, private, protected, static, transient, or volatile, we will encounter a Compiler Error (CE): “illegal start of expression”. If no modifier is declared, by default, it is <default>, but this rule applies only to instance and static variables, not for local variables.
Aso, formal parameters of a method simply act as local variables of that method. Formal parameters can be declared as final. If a formal parameter is declared as final, reassignment within the method is not permitted.
Static Modifiers
Static modifiers are applicable only for methods and variables and not for top-level classes. However, in static nested inner classes, it is applicable.
- Instance Variable: A separate copy is created for every variable. In static variables, only one copy is created at the class level and shared by every object of the class.
- Instance Variable vs. Static Variable: Instance variables can be accessed only from the instance area and cannot be accessed directly from the static area. However, static variables can be accessed from anywhere.
Consider the following declarations and let’s determine the correct combination:
I. int x = 10;
II. static int x = 10;
III. public void m1() { System.out.println(x); }
IV. public static void m1() { System.out.println(x); }
- Correct Combinations:
- I and III: Correct. Instance variable
x
is accessed within an instance method. (Instance variable and instance method) - II and III: Correct. Static variable
x
is accessed within an instance method. (Static variable and instance method) - II and IV: Correct. Static variable
x
is accessed within an static method. (Static variable and static method)
- I and III: Correct. Instance variable
- Incorrect Combinations:
- I and IV: Incorrect. Compiler Error (CE): “non-static variable x cannot be referenced from a static context.”
- I and II: Incorrect. Duplicate variable
x
declaration within the same class. - III and IV: Incorrect. Duplicate method
m1()
declaration within the same class.
Regarding the main method overloading
If both public static void main(String[] args)
and public static void main(int[] args)
are present in the same class, and sop("String[]")
is called, the output will be "String[]"
. This is because both methods are overloaded, and the class entry point is public static void main(String[] args)
.
Inheritance and Static Methods
The inheritance concept is applicable to static methods, including the main method. Hence, while executing a child class, if the child class doesn’t contain any main method, then the parent class’s main method will be executed.
class P {
public static void main(String[] args) {
System.out.println("Parent main");
}
}
class C extends P {
public static void main(String[] args) {
System.out.println("Child main");
}
}
In this case, it is method hiding but not method overriding. So, when we run P.java
, the output will be “Parent main”, and when we run C.java
, the output will be “Child main”.
When to Use Static and Instance Methods:
- Use instance methods when at least a single instance variable is being used.
- Use static methods when no instance variable is being used, regardless of whether static variables are being used or not.
Abstract Static Combination:
The combination of abstract and static is illegal for methods because abstract means only declaration without implementation, while static means only implementation.
Synchronized Modifiers
In Java, the synchronized
modifier is applicable only to methods and blocks but not to variables or classes.
Key points about the synchronized
modifier:
- It is used to prevent data inconsistency problems, such as race conditions, that may occur when multiple threads operate simultaneously on the same Java object.
- When a method or block is declared as
synchronized
, only one thread is allowed to execute that method or block on the given object at a time, ensuring data consistency. - However, using
synchronized
can lead to increased waiting time for threads and performance issues. Therefore, it’s not recommended to use it unless there is a specific requirement to do so.
Combining synchronized
with abstract
is illegal for methods because:
abstract
methods lack implementation and only provide a declaration.synchronized
methods require implementation to specify the synchronized behavior.- Combining these modifiers is contradictory, as
abstract
implies a lack of implementation whilesynchronized
implies specific implementation details.
Therefore, synchronized abstract
is not allowed for methods.
Native Modifiers
The native
modifier is applicable only for methods and cannot be applied anywhere else.
Methods that are implemented in non-Java languages, mostly C and C++, are called native methods or foreign methods. Native methods are used when performance is critical. For example, the hashCode
method in Java is implemented using native code.
The main objectives of using native methods are:
- To improve performance problems.
- To achieve machine-level communication.
- To utilize already existing legacy non-Java code.
Pseudo-code for using native methods in Java:
Load native libraries:
static {
System.loadLibrary("native_library_path");
}
Declare a native method:
public native void m1();
Invoke a native method:
n.m1();
For Java, native method implementations are already available in other old languages like C and C++. Therefore, we are not responsible for providing the implementation, and hence native methods end with a semicolon (;).
public native void m1(); // CE: Native methods cannot have a body
The combination of native
and abstract
is illegal for methods because native methods already have implementation in old languages like C/C++, while abstract methods don’t have.
The combination of native
and strictfp
is also illegal for methods because strictfp
follows IEEE 754 standards, but there is no guarantee that native methods, which are implemented in C/C++, adhere to IEEE 754 standards.
The main advantage of using native methods is improved performance, but the main disadvantage is that it introduces Java dependency as we completely depend on other language implementations.
Transient Modifiers
In Java, the transient
modifier is applicable only to variables and is commonly used in the context of serialization.
Key points about the transient
modifier:
- It is used to indicate that a variable should not be serialized.
- When an object is serialized, all of its non-transient instance variables are saved to a file or sent over a network. However, transient variables are excluded from this process.
- Transient variables are useful when there are sensitive or unnecessary fields that should not be saved during serialization. For example, fields containing passwords or temporary data.
- When an object is deserialized, transient variables will be initialized to their default values (e.g.,
null
for objects,0
for numeric types). - Using
transient
achieves better security by preventing certain data from being persisted or transmitted. - Transient variables are typically declared as
private
, though this modifier is not strictly necessary for serialization purposes.
Example usage:
import java.io.Serializable;
class MyClass implements Serializable {
private transient String sensitiveData; // This variable will not be serialized
// Other non-transient variables and methods
}
In this example, the sensitiveData
variable will not be included in the serialization process, ensuring that sensitive information is not persisted or transmitted.
Volatile Modifiers
The volatile
modifier is applicable only for variables and not for anywhere else.
If the value of a variable keeps changing in multiple threads, there may be a chance of a data inconsistency problem. We can solve this problem using the volatile
keyword.
When a variable is declared as volatile, JVM will create a separate copy of that variable for each thread. Any modification performed by any thread is on its own copy, ensuring that it will not affect the value seen by any other thread.
The combination of final
and volatile
is illegal for variables because final
implies that the value never changes, while volatile
implies that the value keeps on changing.
Advantage: Overcoming data inconsistency problems.
Disadvantage: Creating and maintaining separate copies of variables, leading to reduced performance and increased memory usage. Therefore, volatile
is deprecated and not recommended for use.
Short Modifiers Summary:
Here’s a brief revision of the modifiers mentioned:
- Local Variable: Only applicable modifier is
final
. - Constructor: Applicable modifiers are
public
,private
,protected
, and<default>
. - Methods: Only applicable modifier is
native
. - Variables: Applicable modifiers are
volatile
andtransient
. - Class (not for interface): Only applicable modifier is
final
. - Class (not for enum): Applicable modifiers are
final
andabstract
.
Conclusion
Java modifiers serve as powerful tools for developers to enhance the functionality, security, and performance of their applications. By understanding the nuances of each modifier type and applying them appropriately, programmers can write more robust and maintainable code.
From controlling access levels with keywords like public
, private
, protected
, and <default>
, to managing synchronization and serialization with volatile
and transient
, modifiers offer a wide range of capabilities to meet various programming needs.
As you continue your journey in Java development, remember to leverage modifiers effectively, striking the right balance between accessibility, security, and performance. With practice and a solid understanding of modifiers, you’ll be well-equipped to tackle complex programming challenges and build successful Java applications.