Java Modifiers

Java Modifiers

Modifiers are keywords that you add to definitions to change their meanings. Java has a wide variety of modifiers, which are divided into two groups:

  1. Access Modifiers: Controls the access level (visibility).
  2. Non-Access Modifiers: Do not control access level, but provide other functionality.

1. Access Modifiers

Access modifiers are used to set the accessibility of classes, interfaces, variables, methods, constructors, and data members.

For Classes:

For Attributes, Methods, and Constructors:

The table below illustrates the visibility:

Modifier Same Class Same Package Subclass World
public Y Y Y Y
protected Y Y Y N
default Y Y N N
private Y N N N

Best Practices for Access Modifiers

Access Modifier Example

public class MyClass {
  public String publicVar = "I am public!";
  private String privateVar = "I am private!";

public void display() { // The private variable is accessible inside the class System.out.println(privateVar); } }

// In another class... public class Main { public static void main(String[] args) { MyClass obj = new MyClass(); System.out.println(obj.publicVar); // This works // System.out.println(obj.privateVar); // This would cause an error obj.display(); // This works and prints the private variable } }


2. Non-Access Modifiers

Non-access modifiers are used to provide information about the characteristics of a class, method, or variable to the JVM.

For Classes:

For Attributes and Methods:

Static and Final Example

public class Main {
  final double PI = 3.14; // A final variable (constant)
  static int instanceCount = 0; // A static variable

public Main() { instanceCount++; // Increment the static counter for each new object }

public static void main(String[] args) { Main obj1 = new Main(); Main obj2 = new Main(); System.out.println("Instances created: " + Main.instanceCount); // Access static var on the class } }


Deep Dive: Advanced Non-Access Modifiers

To truly master Java, especially in large-scale enterprise environments, you need to understand how Java handles saving objects and managing multiple tasks running at the same time (multithreading). Let's take a closer look at three advanced non-access modifiers:

1. The transient Keyword (Security & Saving)

In Java, you can convert an object into a byte stream to save it to a file or send it over a network (this is called Serialization). But what if your object contains sensitive data, like a password or a Social Security number?

By marking a variable as transient, you tell the JVM: "Do not serialize this variable!" When the object is saved and later loaded back into memory, the transient variable is ignored and will simply default to null (or 0).

Transient Example

import java.io.Serializable;

class UserProfile implements Serializable { String username = "JaneDoe"; // This field is skipped during serialization for security! transient String password = "SuperSecretPassword123"; }

2. The volatile Keyword (Main Memory Access)

Modern CPUs have multiple cores, and each core has its own tiny, super-fast memory cache. To speed things up, threads in Java often copy variables from the main RAM into their local cache. However, if two threads are working on the same variable, Thread A might change the value in its cache, but Thread B won't see the change because it is looking at its own cache!

📝 The "Personal Notepad" Analogy: Imagine workers (threads) copying a master to-do list (Main RAM) onto their personal notepads (cache). If one worker crosses off an item on their notepad, the others don't know! Marking a variable as volatile takes away their notepads. It forces all threads to walk over and read/write directly to the master whiteboard (Main RAM), ensuring every thread always sees the most up-to-date value.

Volatile Example

class SharedTask {
  // Ensures the flag is read directly from main memory by all threads
  volatile boolean stopTask = false;

public void runTask() { while (!stopTask) { // Keep doing work until stopTask is true across the system } } }

3. The synchronized Keyword (Thread Safety)

When multiple threads try to modify the same data at the exact same time, things can crash or data can be corrupted. This is called a Race Condition. Adding the synchronized keyword to a method ensures that only one thread can execute that method at a time.

🚽 The "Restroom Key" Analogy: A synchronized method is like a single-stall restroom. When a thread wants to execute it, it grabs the key (the object's lock). Any other thread that wants to execute that method must wait in line outside until the first thread finishes and returns the key.

Synchronized Method Example

class BankAccount {
  int balance = 1000;

// The lock ensures two threads can't withdraw the same $1000 simultaneously! public synchronized void withdraw(int amount) { if (balance >= amount) { balance -= amount; } } }

4. The static Keyword (Class-Level Sharing)

Normally, every time you create an object, it gets its own distinct copy of instance variables. But what if you want all objects of a class to share a single, shared variable?

🚰 The "Water Cooler" Analogy: Regular instance variables are like personal water bottles—every object has its own. A static variable is like the office water cooler. There is only one water cooler, and everyone (all objects) shares it. If one person empties the water cooler, it is empty for everyone!

You can use static on variables, methods, and nested classes. Because static members belong to the blueprint itself and not an individual object, you can call them directly on the class name without using new (e.g., Math.random()).

Static Modifier Example

public class Main {
  static class WaterCooler {
    // Shared by everyone
    static int cupsRemaining = 10; 
  }

public static void main(String[] args) { System.out.println("Cups: " + WaterCooler.cupsRemaining); WaterCooler.cupsRemaining -= 1; // Someone takes a cup System.out.println("Cups left: " + WaterCooler.cupsRemaining); } }

5. The final Keyword (The Ultimate Restriction)

The final keyword is used to enforce strict rules, but its behavior changes depending on where it is applied:

Final Modifier Example

public class Main {
  public static void main(String[] args) {
    final int MAX_SPEED = 120;
    System.out.println("Max speed is " + MAX_SPEED);
    // MAX_SPEED = 150; // Uncommenting this will cause a compilation error!
  }
}

6. The abstract Keyword (The Incomplete Blueprint)

The abstract keyword is used when you want to define a class, but you don't want anyone to create objects from it directly. It serves only as a base blueprint for other classes to inherit from.

Abstract Modifier Example

abstract class Vehicle {
  // No body!
  abstract void start(); 
}

class Car extends Vehicle { // The child MUST provide the body for the abstract method void start() { System.out.println("Car is starting... Vroom!"); } }

public class Main { public static void main(String[] args) { Car myCar = new Car(); myCar.start(); } }


⚠️ Special Note: The Two Meanings of default

It is important to know that the word default serves two completely different purposes in Java depending on where you use it:

  1. Access Modifier (Package-Private): As discussed earlier, if you don't write any access modifier on a class, method, or variable, it gets "default" access (only visible within the same package). You do not actually type the word default here.
  2. Interface Method (Java 8+): Inside an interface, you can explicitly write the default keyword to provide a method with an actual body! This was introduced so developers could add new methods to existing interfaces without breaking all the older classes that already implemented them.

Java 8 Interface Default Method

public interface MyInterface {
  // A default method IN AN INTERFACE actually has a body!
  default void myDefaultMethod() {
    System.out.println("This is a default method implementation.");
  }
}

Advanced Notes: Modifier Combinations


Exercise

?

Which access modifier makes a member accessible only within its own class?