Table of contents
1.
Introduction
2.
Non-Access Modifiers in Java
3.
Static Modifier
3.1.
Static Variables
3.2.
Static Methods
4.
Final Modifier
4.1.
Final Variables
4.2.
Final Methods
4.3.
Final Classes
5.
Abstract Modifier
5.1.
Abstract Class
5.2.
Abstract Methods
6.
Synchronized Modifier
6.1.
Synchronized Methods
6.2.
Synchronized Blocks
7.
Transient Modifier
8.
Native Modifier
9.
Frequently Asked Questions
9.1.
Can non-access modifiers be used together?
9.2.
What happens if a variable is marked as both `static` and `final`?
9.3.
Can a class be both `abstract` and `final`?
10.
Conclusion
Last Updated: Nov 14, 2024
Easy

Non Access Modifiers in Java

Author Sinki Kumari
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

In Java, non-access modifiers are special keywords that modify the behavior of classes, methods, and variables. These modifiers provide additional functionality and characteristics to the code elements to which they are applied. They do not control the access level but instead provide other functionality. Non-access modifiers in Java include static, final, abstract, synchronized, transient, and native. Each modifier has a specific purpose, and the knowledge of their use is essential for writing efficient and well-structured Java programs. 

Non Access Modifiers in Java

In this article, we will discuss the different non-access modifiers in Java, their significance, and how to use them effectively. We will cover each modifier in detail, with examples to understand its implementation.

Non-Access Modifiers in Java

In Java, non-access modifiers are keywords that provide additional information about the characteristics and behavior of classes, methods, and variables. Unlike access modifiers (public, private, protected), which control the visibility and accessibility of code elements, non-access modifiers define special properties and functionalities.

The non-access modifiers in Java are:

1. static
 

2. final
 

3. abstract
 

4. synchronized
 

5. transient
 

6. native


Each modifier has a specific role and purpose, which can be used to modify the behavior of classes, methods, or variables in specific ways.

  1. The `static` modifier is used to create class-level members that belong to the class itself rather than instances of the class.
     
  2. The `final` modifier creates constants and prevents classes, methods, or variables from being modified or overridden.
     
  3. The `abstract` modifier defines abstract classes and methods that must be implemented by subclasses.
     
  4. The `synchronized` modifier controls access to shared resources in multi-threaded environments.
     
  5. The `transient` modifier is used to exclude variables from serialization.
     
  6. The `native` modifier indicates that a method is implemented in native code (e.g., C or C++).

Static Modifier

The `static` modifier in Java creates members (variables and methods) that belong to the class itself rather than instances of the class. When a member is declared as static, it is shared among all instances of the class and can be accessed using the class name without the need to create an object of the class.

Static Variables

Static variables are class-level variables that are shared by all instances of a class. They are declared using the `static` keyword and are initialized when the class is loaded into memory. Static variables are useful for maintaining global state or constants that are common to all objects of a class.

For example:

public class Counter {
    private static int count = 0;


    public static void increment() {
        count++;
    }


    public static int getCount() {
        return count;
    }
}


In this example, the `count` variable is declared as static, making it a class-level variable. It can be accessed and modified using the class name, without creating an instance of the `Counter` class.

Static Methods

Static methods are class-level methods that can be called without creating an instance of the class. They are declared using the `static` keyword & can only directly access static members of the class. Static methods are often used for utility functions or operations that don't require access to instance-specific data.

For example:

public class MathUtils {
    public static int square(int number) {
        return number * number;
    }


    public static int multiply(int a, int b) {
        return a * b;
    }
}


In this example, the `square()` and `multiply()` methods are declared as static. They can be called directly using the class name without creating an instance of the `MathUtils` class.

int result1 = MathUtils.square(5);
int result2 = MathUtils.multiply(3, 4);


Static methods are used for utility functions, helper methods, or operations that don't depend on the state of specific instances.

Final Modifier

The `final` modifier in Java is used to create constants and prevent modifications or overriding of classes, methods, and variables. When a member is declared as final, its value cannot be changed once assigned, and the member cannot be overridden or extended by subclasses.

Final Variables

Final variables are variables whose values cannot be modified once assigned. They are declared using the `final` keyword and must be initialized either during declaration or in the constructor of the class. Final variables are useful for representing constants or values that should not change throughout the lifetime of an object.

Example:

```java
public class Circle {
    private final double PI = 3.14159;
    private double radius;
    public Circle(double radius) {
        this.radius = radius;
    }

    public double getArea() {
        return PI * radius * radius;
    }
}
```


In this example, the `PI` variable is declared as final, indicating that its value cannot be modified once assigned. It is initialized during declaration, and any attempt to change its value will result in a compilation error.

Final Methods

Final methods are methods that cannot be overridden by subclasses. They are declared using the `final` keyword and provide a guarantee that the implementation of the method will remain the same in all subclasses.

For example:

public class Vehicle {
    public final void startEngine() {
        System.out.println("Starting the engine...");
    }
}

public class Car extends Vehicle {
    // Cannot override the final method from Vehicle
}


In this example, the `startEngine()` method in the `Vehicle` class is declared as final. Any attempt to override this method in the subclass `Car` will result in a compilation error.

Final Classes

Final classes are classes that cannot be extended or inherited by other classes. They are declared using the `final` keyword, which provides a way to prevent subclassing and ensure that the class remains a standalone entity.

For example:

public final class Singleton {
    private static Singleton instance;
    private Singleton() {
        // Private constructor to prevent instantiation
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}


In this example, the `Singleton` class is declared as final, indicating that it cannot be extended by other classes. This is often used in scenarios where you want to enforce a single instance of a class, such as in the Singleton design pattern.

The `final` modifier helps in creating immutable objects, constants, and preventing unintended modifications or extensions of classes and methods.

Abstract Modifier

The `abstract` modifier in Java is used to define abstract classes and methods. An abstract class is a class that cannot be instantiated and may contain abstract methods, which are methods without an implementation. Abstract classes are meant to be subclassed, and the subclasses provide the implementation for the abstract methods.

Abstract Class

An abstract class is declared using the `abstract` keyword and may contain both abstract and non-abstract methods. It serves as a blueprint for its subclasses, providing common attributes and behaviors that can be inherited and extended.

Example:

```java
public abstract class Shape {
    protected String color;


    public Shape(String color) {
        this.color = color;
    }


    public abstract double getArea();


    public String getColor() {
        return color;
    }
}
```


In this example, the `Shape` class is declared as abstract. It contains an abstract method `getArea()`, which doesn't have an implementation. The `Shape` class also has a constructor and a non-abstract method `getColor()`.

Abstract Methods

Abstract methods are methods declared without an implementation. They are declared using the `abstract` keyword and have no method body. Abstract methods must be implemented by the subclasses that extend the abstract class.

Example:

```java
public class Circle extends Shape {
    private double radius;
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}
```


In this example, the `Circle` class extends the abstract `Shape` class and provides an implementation for the abstract `getArea()` method. The `Circle` class must implement all the abstract methods declared in its superclass.

Abstract classes and methods are used to define common behaviors and contracts that subclasses must adhere to. They provide a way to create a hierarchy of related classes and enforce certain methods to be implemented by the subclasses.
 

Some important points about abstract classes and methods which we need to remember are:

  1. An abstract class cannot be instantiated directly.
     
  2. An abstract class may contain both abstract and non-abstract methods.
     
  3. A subclass extending an abstract class must implement all the abstract methods, unless it is also declared as abstract.
     
  4. An abstract method cannot have a method body and must be implemented by the subclasses.
     

Abstract classes and methods are powerful tools for designing extensible and modular code structures.

Synchronized Modifier

The `synchronized` modifier in Java is used to control access to shared resources in a multi-threaded environment. It ensures that only one thread can execute a synchronized method or block at a time, preventing multiple threads from accessing the shared resource simultaneously and avoiding race conditions.

Synchronized Methods

When a method is declared as synchronized, it acquires a lock on the object before executing the method body. This means that only one thread can execute the synchronized method at a time, while other threads that try to access the same synchronized method will be blocked until the lock is released.

Example:

public class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}


In this example, the `increment()` and `getCount()` methods are declared as synchronized. When a thread calls the `increment()` method, it acquires a lock on the `Counter` object. Other threads that try to call `increment()` or `getCount()` will be blocked until the lock is released.

Synchronized Blocks

In addition to synchronized methods, Java also supports synchronized blocks. Synchronized blocks allow you to synchronize a specific section of code rather than an entire method. The lock is acquired on the specified object before executing the code within the synchronized block.

For example:

public class SharedResource {
    private int value;

    public void updateValue(int newValue) {
        synchronized (this) {
            value = newValue;
            // Perform some operations on the shared resource
        }
    }
}


In this example, the `updateValue()` method contains a synchronized block. The lock is acquired on the `SharedResource` object (using `this`) before executing the code within the synchronized block. This ensures that only one thread can update the `value` at a time.

Synchronized blocks are helpful when you need to synchronize access to a shared resource but don't want to synchronize the entire method.

Let’s discuss why synchronization is useful for us:

  1. Prevents race conditions: Synchronization ensures that multiple threads don't access shared resources simultaneously, avoiding data inconsistencies and race conditions.
     
  2. Maintains data integrity: By synchronizing access to shared resources, you can ensure that the data remains consistent and valid across multiple threads.
     
  3. Coordinates thread execution: Synchronization can be used to coordinate the execution of multiple threads, allowing them to work together and share resources safely.
     

Always remember that synchronization comes with a performance overhead, as threads may need to wait for locks to be released. Therefore, it should be used judiciously and only when necessary to protect shared resources.

Transient Modifier

The `transient` modifier in Java indicates that a variable should not be serialized when an object is serialized. Serialization is the process of converting an object's state into a byte stream, which can be stored in a file or transmitted over a network. When an object is deserialized, the byte stream is used to recreate the object's state.

By marking a variable as transient, you tell the Java serialization mechanism to ignore that variable during the serialization process. The value of a transient variable is not included in the serialized representation of the object.

Example:

public class User implements Serializable {
    private String username;
    private transient String password;
    // Constructor, getters, and setters
}


In this example, the `User` class implements the `Serializable` interface, indicating that its objects can be serialized. The `username` variable will be included in the serialized representation of the `User` object, but the `password` variable, marked as transient, will be excluded.
 

When an object of the `User` class is serialized and then deserialized, the `password` variable will be assigned its default value (null for object types, 0 for numeric types, false for boolean, etc.) because it was not included in the serialized data.

Let’s understand where we can use Transient Modifier:

1. Sensitive Data: If an object contains sensitive information, such as passwords or encryption keys, you may want to mark those fields as transient to avoid storing them in the serialized form.
 

2. Derived or Computed Values: If a variable's value can be derived or computed from other variables, you can mark it as transient. When the object is deserialized, you can recalculate or reinitialize the transient variable based on the other serialized variables.
 

3. Non-Serializable Dependencies: If a variable refers to an object that is not serializable, you can mark it as transient to avoid serialization errors. When the object is deserialized, you can manually reinitialize or reconstruct the transient variable.
 

It's important to note that the `transient` modifier only applies to the serialization process and does not affect the normal operation of the object. The transient variables can still be accessed and modified during the object's lifetime.

Also, remember that if a superclass is serializable, its transient variables will not be serialized even if the subclass is serialized. The `transient` modifier is not inherited by subclasses.

Native Modifier

The `native` modifier in Java indicates that a method is implemented in native code, which means it is written in a language other than Java, such as C or C++. Native methods are used to interact with system-specific functionality or to optimize performance-critical parts of an application.

When a method is declared as native, it does not have a method body in the Java code. Instead, the implementation of the method is provided in a separate native library, which is typically written in a lower-level language like C or C++.

For example:

public class NativeExample {
    public native void nativeMethod();
    static {
        System.loadLibrary("nativeLibrary");
    }
}


In this example, the `nativeMethod()` is declared as native, indicating that its implementation is provided in a native library. The `static` block is used to load the native library, which contains the actual implementation of the native method.

To use native methods in Java, you need to follow these steps:

1. Declare the native method in your Java class using the `native` keyword.
 

2. Create a C or C++ implementation of the native method. The method signature in the native code should match the declaration in the Java class.
 

3. Compile the native code into a shared library (e.g., .dll on Windows, .so on Linux).
 

4. Load the native library in your Java code using `System.loadLibrary()` or `System.load()`.
 

5. Call the native method from your Java code.

 

Native methods are helpful in situations where you need to:

  1. Access platform-specific features or APIs that are not available in Java.
     
  2. Integrate with existing native libraries or legacy code.
     
  3. Perform low-level system operations or hardware interactions.
     
  4. Optimize performance-critical sections of code.


Let’s understand few points we need to remember before using native methods: 

  1. Platform Dependency: Native methods are platform-specific, meaning that you must compile and distribute the native library separately for each target platform.
     
  2. Portability: Native methods reduce the portability of your Java code since they rely on platform-specific libraries.
     
  3. Security: Native methods operate outside the Java security model, so they can introduce security risks if not implemented correctly.


Maintainability: Mixing Java and native code can make the codebase more complex and harder to maintain, especially if the native code is not well-documented or if the developers are not familiar with the native language.


It's important to use native methods cautiously and only when necessary because you need to consider the issues it creates with performance, portability, and maintainability.

Frequently Asked Questions

Can non-access modifiers be used together?

Yes, multiple non-access modifiers can be used together, such as combining `static` and `final` to create a constant, or `abstract` and `final` to create an abstract class that cannot be subclassed.

What happens if a variable is marked as both `static` and `final`?

When a variable is declared as both `static` and `final`, it becomes a constant that belongs to the class itself. Its value is assigned once and cannot be changed throughout the program's execution.

Can a class be both `abstract` and `final`?

No, a class cannot be both `abstract` and `final`. An abstract class is meant to be subclassed, while a final class cannot be extended. These modifiers are mutually exclusive.

Conclusion

In this article, we discussed the different non-access modifiers in Java, which are `static`, `final`, `abstract`, `synchronized`, `transient`, and `native`. We learned that each modifier has a specific purpose, like creating class-level members, defining constants, enabling polymorphism, managing synchronization, controlling serialization, and integrating with native code. Utilizing these modifiers cautiously and properly can help you write more efficient, maintainable, and secure Java code. 

You can also check out our other blogs on Code360.

Live masterclass