Just Learn Code

Java’s Non-Access Modifiers: Controlling Behavior for Efficient Programs

Introduction to Java Non-Access Modifiers

As a beginner, it can be challenging to navigate the world of programming languages, especially the Java application structure. The Java language is known for its robustness, versatility, and ability to work seamlessly with other programming languages.

Java developers have access to a wide array of programming tools, some of which include non-access modifiers. Non-access modifiers play a significant role in controlling the behavior of Java applications.

In this article, we will explore the definition of non-access modifiers, their purpose, a list of common non-access modifiers, and an in-depth analysis of the static modifier.

List of Non-Access Modifiers

Non-access modifiers are keywords that modify the behavior of Java variables, methods, and classes. There are several non-access modifiers in Java.

Some of which include;

1. Static Modifier

2.

Final Modifier

3. Abstract Modifier

4.

Synchronized Modifier

5. Volatile Modifier

6.

Transient Modifier

7. Native Modifier

Each of these modifiers serves various purposes and has different functionalities.

Let’s take a closer look at each of them.

The Static Modifier

The static modifier is one of the most commonly used non-access modifiers in Java. It is a keyword that defines a variable or method as independent of any particular instance of a class.

In simpler terms, the static modifier allows us to access variables and methods without needing to create an object from that class. The functionality of the static modifier can be grouped into two aspects: static variables and static methods.

Static Variables

In Java, a static variable belongs to the class, not an instance of the class. Therefore, it retains its value across all instances of its class.

This means that any changes made to a static variable affect every object created from that class. For example, suppose we have a Person class with the static variable currentPopulation.

In this case, the current population variable would hold the current number of person objects created from the class. Any changes made to the value of currentPopulation would affect every instance created from the Person class.

Static Methods

In Java, a static method belongs to the class and not an instance of the class. Therefore, it can be called using the name of the class rather than creating an instance of it.

Static methods are useful when we want to create utility functions or methods that do not rely on object instances. For example, Java provides a Math class that contains static methods such as abs(), sin(), cos(), and tan().

These methods can be called directly using the Math class without the need for object instances.

Example and Usage

The static modifier is incredibly useful when dealing with variables and methods that are not tied to specific instances of a class. To demonstrate its functionality, let’s consider the following example:

“`

public class Person {

static int currentPopulation = 0;

String name;

public Person(String name) {

this.name = name;

currentPopulation++;

}

public static void displayPopulation(){

System.out.println(“Current Population is ” + currentPopulation);

}

}

“`

In the example above, we have created a Person class with a static variable called currentPopulation.

We also have a non-static method, displayPopulation, which simply displays the current population of the Person class. The static variable currentPopulation is incremented every time we create a new instance of the Person class.

This way, we can keep track of the total population of person objects created. We can call the static method displayPopulation at any time, without any need to create an instance of the Person class, as demonstrated below:

“`

public class StaticDemo {

public static void main(String[] args) {

Person p1 = new Person(“Peter”);

Person p2 = new Person(“John”);

Person p3 = new Person(“Mary”);

Person.displayPopulation(); // prints “Current Population is 3”

}

}

“`

In the example above, we create three instances of the person class and run the displayPopulation method.

Since this is a static method, there is no need to create an instance of the Person class. The output displayed will be the current population of objects created from the Person class.

Conclusion

Non-access modifiers are essential tools in the Java programming language, and the static modifier is among the most used. It enables the creation of variables and methods that are not tied to specific object instances, making it incredibly useful in several programming situations.

Understanding the use of non-access modifiers is crucial for any Java developer looking to create robust and efficient applications. 3.

The Final Modifier

In Java, the final modifier is another non-access modifier that is used to restrict the modification of classes, methods, and variables. When applied to a class, it means that the class cannot be extended or inherited by any other class.

Likewise, when used with a method, it signifies that the method cannot be overridden in any sub-classes. Finally, when applied to a variable, it denotes that the variable’s value cannot be modified once it has been initialized.

The primary purpose of using the final modifier is to create unmodifiable entities in Java programs. This ensures that once a class, method or variable has been defined, its behavior or value will remain constant throughout the program’s lifecycle.

This is useful in situations where we want to ensure that certain entities in our program are immutable and do not change, which can help to improve application performance and stability.

Example and Usage

Suppose we created a Person class that we wish to make immutable. We can accomplish this by using the final modifier in the following manner:

“`

public final class Person {

final String name;

final int age;

public Person(String name, int age) {

this.name = name;

this.age = age;

}

@Override

public String toString() {

return “Person{” + “name=” + name + “, age=” + age + ‘}’;

}

}

“`

In this example, we have defined the Person class as final, meaning that it cannot be inherited by any other classes.

Additionally, we have declared the name and age variables as final, indicating that once these variables are assigned values, their values cannot be modified. Now, suppose we want to use this immutable Person class in another class called Employee.

To use the immutable Person class in Employee, we would declare it as follows:

“`

public class Employee {

final Person person;

public Employee(Person person) {

this.person = person;

}

@Override

public String toString() {

return “Employee{” + “person=” + person + ‘}’;

}

}

“`

In this example, we construct an Employee object using a Person object. Since the Person class we created earlier is immutable, any changes to its name or age attributes are not permitted once it has been created.

This provides us with reliable and stable code. Additionally, we can use the final modifier to create a helper class that contains methods and fields that are not part of a specific inheritance hierarchy but can be shared between several classes.

For example, the Math class in the Java Standard Library contains several static final fields, such as E and PI, which are used throughout the library. 4.

The Abstract Modifier

In the Java language, the abstract modifier is another non-access modifier that can be used to define extensible classes and methods. An abstract class is a class that cannot be instantiated but can be inherited by sub-classes.

Abstract methods, on the other hand, are methods that are declared without any implementation and must be implemented by sub-classes. The purpose of the abstract modifier is to provide a template for other classes to follow.

By creating abstract classes, we can define the necessary attributes and behaviors that our sub-classes must implement. Abstract methods provide a similar role in ensuring that certain methods must be implemented by the sub-classes, which helps to provide consistency and structure to our programming.

Example and Usage

Let’s consider a Person class example that uses the abstract modifier:

“`

public abstract class Person {

String name;

int age;

public Person(String name, int age){

this.name = name;

this.age = age;

}

public abstract void sayHello();

}

“`

In this example, we have created an abstract class called Person and defined two fields, name and age. We have also defined a constructor that initializes these fields.

Additionally, we have declared an abstract method called sayHello(). Since this is an abstract method, we have not implemented any code for it.

We are only defining its name and method signature. Any sub-classes that inherit the Person class must implement the sayHello() method.

Now let’s consider a Student class that inherits from Person and implements the sayHello() method:

“`

public class Student extends Person {

int grade;

public Student(String name, int age, int grade) {

super(name, age);

this.grade = grade;

}

@Override

public void sayHello() {

System.out.println(“Hello, my name is ” + name + ” and I am in grade ” + grade);

}

}

“`

The Student class extends the Person class and defines an additional field, grade. It also implements the sayHello() method inherited from the abstract Person class by providing an implementation that prints a personalized greeting for a student.

Furthermore, any other classes that inherit from the Person class must also implement the sayHello() method. In conclusion, the abstract modifier is useful in ensuring that sub-classes implement specific behaviors or methods defined by the abstract class.

It provides structure, consistency, and improves code quality and readability. 5.

The Synchronized Modifier

In Java, concurrency can create problems when multiple threads try to access the same shared data simultaneously. To prevent data inconsistencies and other concurrency issues, Java provides a synchronization feature that ensures that only one thread at a time can access a shared resource.

The synchronization mechanism is implemented using the synchronized modifier, which can be applied to methods or blocks of code. The synchronized modifier ensures that only one thread can execute the method or block of code at any given time, while other threads wait until the executing thread has completed its task.

This helps to avoid concurrency issues, such as race conditions and deadlocks, which can lead to inconsistent data or program crashes.

Example and Usage

Let’s consider an example of a Number object with a shared printNumbers() method that must be accessed by multiple threads simultaneously:

“`

public class Number {

private int number;

public Number(int number) {

this.number = number;

}

public synchronized void printNumbers() {

for (int i = 0; i < 10; i++) {

System.out.println(Thread.currentThread().getName() + “: ” + number);

number++;

}

}

}

“`

In this example, we defined a Number object with a shared printNumbers() method. The printNumbers() method is marked as synchronized, meaning that only one thread can execute it at any given time.

Now, suppose we want to create two threads that access the printNumbers() method of the Number object simultaneously:

“`

public static void main(String[] args) {

Number number = new Number(0);

Runnable r1 = () -> number.printNumbers();

Runnable r2 = () -> number.printNumbers();

Thread t1 = new Thread(r1, “Thread-1”);

Thread t2 = new Thread(r2, “Thread-2”);

t1.start();

t2.start();

}

“`

In this example, we created two threads (Thread-1 and Thread-2) that access the printNumbers() method of the Number object. Since the printNumbers() method is synchronized, only one thread can execute it at a time.

The synchronized modifier helps to ensure that the output produced by both threads accessing the shared method is consistent and predictable. 6.

The Volatile Modifier

In Java, the volatile modifier is used to ensure that variable values are consistent between threads. A volatile variable is a variable whose value can be changed by multiple threads simultaneously.

Without the volatile modifier, changes made by one thread may not be visible to another thread, leading to inconsistencies and bugs. The primary purpose of using the volatile modifier is to ensure that read and write operations on a variable are atomically consistent.

This means that when one thread writes to a volatile variable, all threads see the new value.

Example and Usage

To understand how the volatile modifier works, let’s consider an example of a normal variable vs. a volatile variable:

“`

public class VolatileDemo {

private int normalVariable = 0;

private volatile int volatileVariable = 0;

public void writeNormalVariable() {

normalVariable = normalVariable + 1;

}

public void writeVolatileVariable() {

volatileVariable = volatileVariable + 1;

}

}

“`

In this example, we have a VolatileDemo class with two methods that increment a normal variable and a volatile variable, respectively.

The volatile modifier is applied to the volatileVariable. Now let’s consider a scenario where multiple threads access the writeNormalVariable() and writeVolatileVariable() methods:

“`

public static void main(String[] args) throws InterruptedException {

VolatileDemo demo = new VolatileDemo();

Runnable r1 = () -> {

for (int i = 0; i < 1000; i++) {

demo.writeNormalVariable();

demo.writeVolatileVariable();

}

};

Runnable r2 = () -> {

for (int i = 0; i < 1000; i++) {

demo.writeNormalVariable();

demo.writeVolatileVariable();

}

};

Thread t1 = new Thread(r1);

Thread t2 = new Thread(r2);

t1.start();

t2.start();

t1.join();

t2.join();

System.out.println(“Normal Variable: ” + demo.normalVariable);

System.out.println(“Volatile Variable: ” + demo.volatileVariable);

}

“`

In this example, we create two threads that execute the writeNormalVariable() and writeVolatileVariable() methods.

The output at the end of the execution shows the normal variable and the volatile variable’s final value. Without the volatile modifier, the normalVariable output could vary each time we run the program.

However, when we apply the volatile modifier to the volatileVariable, the output for the volatile variable will always be consistent and predictable, regardless of the number of threads accessing it. The volatile modifier ensures that all threads see the same variable values, eliminating inconsistencies, and ensuring that the program operates correctly.

7. The Transient Modifier

In Java, the transient modifier is used to mark a field or variable as not serializable when a class implements the Serializable interface.

Serialization refers to the process of converting an object’s state to a byte stream, enabling it to be stored in a file or transferred over the network. Deserialization, on the other hand, is the process of reconstructing an object from a byte stream.

When applied to a field or variable, the transient modifier ensures that it is not included in the serialized object’s state. This can be useful when we have sensitive information that we do not want to be accessible during serialization, such as passwords or other security credentials.

Example and Usage

Let’s say we have a Person class as follows:

“`

public class Person implements Serializable {

private String name;

private transient int age;

private String address;

public Person(String name, int age, String address) {

this.name = name;

this.age = age;

this.address = address;

}

// Getter and Setter methods

… }

“`

In this example, the Person class implements the Serializable interface.

The class has three fields: name, age, and address. However, we have marked the age field as transient since we do not want it to be included in the serialized object’s state.

Now let’s consider an example where we serialize a Person object:

“`

import java.io.*;

public class SerializationDemo {

public static void main(String[] args) throws IOException, ClassNotFoundException {

Person person = new Person(“John Doe”, 30, “123 Main Street”);

// Serialize the Person object to a file

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“person.ser”));

out.writeObject(person);

out.close();

// Deserialize the Person object from the file

ObjectInputStream in = new Object

Popular Posts