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:
Access modifiers are used to set the accessibility of classes, interfaces, variables, methods, constructors, and data members.
public: The class is accessible by any other class.default: The class is only accessible by classes in the same package. This is used when you don't specify a modifier.public: The code is accessible for all classes.private: The code is only accessible within the declared class.protected: The code is accessible in the same package and subclasses.default: The code is only accessible in the same package.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 |
private by default: To achieve Encapsulation, always make your instance variables private unless there is a specific reason not to. Use public getter and setter methods to provide controlled access to them.public for API: Methods that other classes need to interact with (like core behaviors) should be public. Constants (e.g., public static final int MAX_USERS = 100;) are also typically public.protected for Inheritance: Use protected when you are building a parent class and want child classes (even if they reside in other packages) to have access to internal helper methods or variables, while hiding them from the rest of the world.default (package-private) for internal module logic: This is great when a group of classes in the same package need to work closely together but shouldn't be exposed to the outside application.
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
}
}
Non-access modifiers are used to provide information about the characteristics of a class, method, or variable to the JVM.
final: The class cannot be inherited by other classes.abstract: The class cannot be used to create objects. To access an abstract class, it must be inherited from another class.final: Attributes and methods cannot be overridden or modified.static: Attributes and methods belong to the class, rather than an object.abstract: Can only be used in an abstract class, and can only be used on methods. The method does not have a body, e.g. abstract void run();. The body is provided by the subclass.transient: Attributes and methods are skipped when serializing the object containing them.synchronized: Methods can only be accessed by one thread at a time.volatile: The value of an attribute is not cached thread-locally, and is always read from the "main memory".native: Used only on methods to indicate that the method is implemented in another programming language like C or C++ (using Java Native Interface).strictfp: Ensures floating-point calculations yield exactly the same results on all platforms. (Note: As of Java 17, this behavior is standard across all floating-point calculations, rendering strictfp largely obsolete).
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
}
}
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:
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).
import java.io.Serializable;class UserProfile implements Serializable { String username = "JaneDoe"; // This field is skipped during serialization for security! transient String password = "SuperSecretPassword123"; }
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
volatiletakes 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.
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
}
}
}
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
synchronizedmethod 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.
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;
}
}
}
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
staticvariable 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()).
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);
}
}
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 double PI = 3.14159;)extend a final class. For security and immutability reasons, many core Java classes (like String and System) are final classes!
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!
}
}
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.
new Vehicle() is not allowed if Vehicle is abstract).public abstract void honk();). The child class must override and provide the actual code for this method.
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();
}
}
defaultIt is important to know that the word default serves two completely different purposes in Java depending on where you use it:
default here.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.
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.");
}
}
private final fields are common for immutable object state. Assign them in the constructor and expose only safe accessors.public static final is the usual combination for constants. Constant names are commonly written in uppercase with underscores.abstract and final cannot be used together on a class because an abstract class must be extended and a final class cannot be extended.volatile gives visibility guarantees, but it does not make compound actions like count++ atomic.synchronized protects critical sections but can reduce concurrency when locks are too broad.Which access modifier makes a member accessible only within its own class?