What Are the Different Access Modifiers in Java and When Should You Use Them? #
Access modifiers control visibility of classes, methods, and variables.
Modifier | Class | Package | Subclass | World | Applicable To |
---|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ | Class, Methods, Constructors, Member Variables |
protected | ✅ | ✅ | ✅ | ❌ | Methods, Constructors, Member Variables |
default | ✅ | ✅ | ❌ | ❌ | Class, Methods, Constructors, Member Variables |
private | ✅ | ❌ | ❌ | ❌ | Methods, Constructors, Member Variables |
🎯 Best Practices:
- Use
private
– Best for encapsulating fields and internal logic- In
java.lang.String
, thevalue
array isprivate final
:- Ensures immutability and prevent external access or modification.
- In
java.lang.Integer
, thevalue
field isprivate
:- Internal representation stays hidden while exposing only necessary methods.
- In
- Use
protected
– Only when subclasses genuinely need access- The
java.util.AbstractList
class, the default constructorprotected AbstractList()
is protected:- Prevents direct instantiation.
- Ensures only subclasses like
ArrayList
orLinkedList
can extend and initialize it.
- In
java.util.AbstractList
classprotected void removeRange (int fromIndex, int toIndex)
:- Allows subclasses to efficiently remove elements in bulk.
- Not exposed publicly to avoid accidental misuse.
- The
- Use
public
– Expose only what external code truly needs- In
java.lang.String
, methods likelength()
andcharAt()
arepublic
:- Other classes can safely read string content without touching internals
- In
java.util.ArrayList
, methods likeadd()
andget()
arepublic
:- Other classes can use the collection methods to perform operations.
- In
- Avoid default (package-private) – Visibility leaks across unrelated classes in the same package
How Should You Use the
static
Modifier Effectively in Java? #
1. Use static final
for Constants
- What? Use
static final
for constant values. - Why? Saves memory by sharing a single copy across all instances (
static
)- Also the value cannot be changed (
final
)
- Also the value cannot be changed (
- JDK Example:
Integer.MAX_VALUE
,Math.PI
- Example:
class MathUtil { public static final double PI = 3.14159; }
2. Use static
for Utility Methods
- What? Methods that don't need instance variables.
- Why? Avoids unnecessary object creation.
- JDK Example:
Collections.sort()
,Math.abs()
- Example:
class MathUtil { public static int square(int num) { return num * num; } }
3. Use static
Blocks for Initialization
- What? Code that runs once when the class loads.
- Why? Useful for setting up static variables.
- Example:
class Config { public static String appName; static { appName = "MyApp"; //Other initialization of static variables System.out.println("Static block executed"); } }
- Source Code (from
Integer
class)private static class IntegerCache { static final Integer cache[]; static { // Cache range: -128 to 127 cache = new Integer[-(-128) + 127 + 1]; for (int i = 0; i < cache.length; i++) cache[i] = new Integer(i - 128); } }
4. Be Careful with static
Variables in Multithreading
- Why? Shared across threads, leading to concurrency issues.
- Solution: Use synchronization (or other concurrency alternatives - AtomicInteger, .. ) if needed.
- Example:
class Counter { private static int count = 0; // ✅ Use synchronized public static synchronized void increment() { count++; } }
5. Use static
Factory Methods for Object Creation
- Why? Improves readability and flexibility.
- Constructors are great for simple initialization of values
- Factory methods improve readability with meaningful names, allow caching, can return subtypes, and give better control over object creation (like returning Optional, a singleton, or even reusing existing instances)
- JDK Example:
List.of()
,Optional.of()
,LocalDate.now()
- Example:
class User { private String name; private User(String name) { this.name = name; } public static User createUser(String name) { return new User(name); } }
6. For Implementing Singleton Pattern
- What? One instance shared across app
- JDK Example:
Runtime.getRuntime()
(Gives access to the JVM's runtime environment to run system commands (exec()
), manage memory (totalMemory()
,freeMemory()
), trigger garbage collection (gc()
), or shut down the JVM (exit()
))class Singleton { private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
7. Use static
Imports Carefully
- What? Lets you access static members without class name
- Why? Overuse can make code less readable.
- Avoid? Wildcard imports – they reduce clarity
- Example:
import static java.lang.Math.sqrt; //import static java.lang.Math.*; AVOID class Example { double result = sqrt(25); }
8. Use static
Methods in Interfaces
- What? Interfaces can define static methods with implementation
- Why? Useful for utility methods specific to the interface
- Not inherited by implementing classes
- Belongs only to the interface
- JDK Example:
Comparator.comparing()
,Map.entry()
,Predicate.isEqual()
- Example:
interface MathOperations { static int square(int n) { return n * n; } } class Demo { public static void main(String[] args) { int result = MathOperations.square(5); System.out.println(result); // Output: 25 } }
9. Use static
Nested Classes
- What? A nested class that doesn't require an instance of the outer class
- Why? Ideal when inner class doesn't need access to instance variables
- Can Access? Only
static
members of the outer class - When? When you need a helper class that groups related functionality, like a Database.Connection class managing database connections without needing an outer class instance.
- JDK-Like Use Case:
Integer.IntegerCache
(cache -128 to 127 - inclusive ) - Example:
class Database { private static String url = "jdbc:mysql://localhost:3306/mydb"; static class Connection { void connect() { System.out.println("Connecting to: " + url); } } } class Main { public static void main(String[] args) { Database.Connection connection = new Database.Connection(); connection.connect(); // Output: Connecting to: jdbc:mysql://localhost:3306/mydb } }
What Are the Best Practices for Using the
final
Modifier in Java? #
1. Use final
for Constants
- What? Declare constants using
final
andstatic
. - Why? Prevents modification and improves code readability.
- JDK Example:
Integer.MAX_VALUE
,Math.PI
- Example:
class MathUtil { public static final double PI = 3.14159; } //Examples From JDK public final class Integer { public static final int MAX_VALUE = 0x7fffffff; } public final class Math { public static final double PI = 3.141592653589793; }
2. Use final
for Member Variables
- What? Makes a field constant after it’s set
- Why? Ensures the variable is assigned once and never changed
- When? Great for setting values in constructors
- Example:
class User { private final String name; public User(String name) { this.name = name; // ✅ Must assign in constructor } public String getName() { return name; } } //Example from JDK public final class String { private final byte[] value; }
3. Use final
with Classes to Prevent Inheritance
- What? Prevents other classes from extending it.
- Why? Useful for utility or immutable classes.
- Example:
final class Utility { public static void log(String message) { System.out.println(message); } } //Examples From JDK public final class String // Immutable text public final class Integer // Immutable wrapper public final class Math // Utility methods only
4. Use final
with Methods to Prevent Overriding
- What? Stops subclasses from modifying behavior.
- Why? Ensures security and correctness.
- Example:
class Parent { public final void show() { System.out.println( "This method cannot be overridden."); } } //Examples from JDK //These methods are marked `final` to prevent subclasses //from changing core thread behavior. Overriding them //could break thread safety, consistency, //or violate JVM expectations during execution. public class Thread implements Runnable { public final String getName() {} public final boolean isAlive() {} public final void setPriority( int newPriority) {} //And a lot of others! }
5. Be Cautious with final
Reference Variables
- What? A
final
reference variable cannot change its reference, but the object’s data can be modified. - Why? Prevents reassignment but allows state changes.
- Example:
class Example { public static void main(String[] args) { final StringBuilder sb = new StringBuilder("Hello"); sb.append(" World"); // Allowed // ❌ Compilation Error // sb = new StringBuilder("New"); } }
What Are the Best Practices for Using the
abstract
Modifier in Java? #
1. Best Use Case For abstract
class: Template Method Pattern
- What? Defines an algorithm structure, letting subclasses implement specific steps.
- Why? Ensures a controlled workflow while allowing flexibility.
- Example:
abstract class AbstractGame { abstract void initialize(); abstract void startPlay(); abstract void endPlay(); public final void play() { // Defines the sequence initialize(); startPlay(); endPlay(); } }
2. abstract
class: Use Proper Naming
- What? Use meaningful names that indicate a base class.
- Why? Makes the purpose of the class clear.
- Example:
abstract class AbstractLogger { abstract void log(String message); } //Examples from JDK public abstract class AbstractList<E> public abstract class AbstractMap<K,V> implements Map<K,V>
3. Prefer Interfaces if Abstract Class has No Implementation
- What? Use an interface when a class only defines method signatures.
- Why? Provides more flexibility than abstract classes.
- Example:
interface Flyable { void fly(); }
4. Avoid Full Implementation in an abstract
Class
- Why? If all methods are implemented, there is no need for
abstract
. - Bad Example:
abstract class Utility { // ❌ No need for abstract public void log(String message) { System.out.println(message); } }
5. Default in Interfaces – No Need to Specify abstract
- What? Methods in interfaces are implicitly abstract unless marked
default
orstatic
. - Why? Reduces redundancy in code.
- Bad Example:
interface Flyable { void fly(); // abstract void fly(); // ❌ 'abstract' is redundant // public abstract void fly(); // ❌ also redundant }
- Good Example:
interface Flyable { void fly(); // ✅ Abstract by default }
Can You Explain Other Useful Modifiers in Java With Simple Examples? #
Modifier | Purpose | Example | Simple Use Case |
---|---|---|---|
volatile | Each thread can cache variables in its own CPU core or registers. So, there might be a delay in visibility of updates from other threads. volatile ensures changes to a variable are visible to all threads immediately. |
private volatile int count; |
Shared variable in a multi-threaded program |
synchronized | Ensures thread safety by allowing only one thread to access a method/block at a time. | synchronized void method() { } |
Prevent race conditions in multi-threading |
transient | Prevents a field from being serialized. | private transient String password; |
Avoid saving sensitive data in a file |
native | Declares a method that is implemented in another language (e.g., C/C++). | native void nativeMethod(); |
Calling system-level functions from Java |
strictfp | Ensures consistent floating-point calculations across platforms. | strictfp class Calculator { } |
Consistent floating-point math across different CPUs |
sealed | Restricts which classes can extend a class. | sealed class Vehicle permits Car, Bike { } |
Control which classes can inherit from a base class |
non-sealed | Allows a subclass of a sealed class to be extended further. | non-sealed class Car extends Vehicle { } |
Allow further extension of a subclass of a sealed class |