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, thevaluearray isprivate final:- Ensures immutability and prevent external access or modification.
- In
java.lang.Integer, thevaluefield isprivate:- Internal representation stays hidden while exposing only necessary methods.
- In
- Use
protected– Only when subclasses genuinely need access- The
java.util.AbstractListclass, the default constructorprotected AbstractList()is protected:- Prevents direct instantiation.
- Ensures only subclasses like
ArrayListorLinkedListcan extend and initialize it.
- In
java.util.AbstractListclassprotected 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
staticModifier Effectively in Java? #
1. Use static final for Constants
- What? Use
static finalfor 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
Integerclass)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
staticmembers 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
finalModifier in Java? #
1. Use final for Constants
- What? Declare constants using
finalandstatic. - 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
finalreference 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
abstractModifier 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
defaultorstatic. - 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 |