Explore essential OOPs interview questions and detailed answers in Java, covering key concepts like Abstraction, Encapsulation, Inheritance, and Polymorphism. When preparing for a Java interview, understanding Object-Oriented Interview Questions in Java is crucial. OOP is at the core of Java and allows developers to design scalable, maintainable, and efficient code. This blog will walk you through some of the most common OOP interview questions. Each question is explained with detailed, practical answers and examples to help you stand out in your next interview!
![OOPs Interview Questions and Detailed Answers in Java](https://javadzone.com/wp-content/uploads/2024/12/Java-Interview-Questions-and-Answers-1024x529.png)
OOPs Interview Questions and Detailed Answers in Java
What is the OOP concept in Java?
Object-Oriented Programming (OOP) is a programming paradigm centered around the concept of “objects,” which are instances of classes. In Java, OOP helps organize code in a way that makes it reusable, scalable, and easier to maintain. The core idea is to structure programs using objects that represent real-world entities, encapsulating both data (attributes) and behavior (methods).
For example:
class Car {
String brand;
int speed;
void drive() {
System.out.println(brand + " is driving at " + speed + " km/h.");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.brand = "Toyota";
car.speed = 120;
car.drive();
}
}
What are the four principles of OOP? Can you explain Abstraction, Encapsulation, Inheritance, and Polymorphism?
The four principles of OOP are:
Abstraction:
Hides complex implementation details and shows only essential features. It simplifies the user interface.
Example: Using abstract classes or interfaces to define common methods.
Encapsulation:
Bundles data and methods into a single unit and restricts access to the internal details using access modifiers.
Example: Using private variables and providing public getter/setter methods.
Inheritance:
Allows a new class (child) to inherit properties and methods from an existing class (parent). It supports code reuse.
Example: A Dog
class inherits from an Animal
class.
Polymorphism:
Allows one method to behave differently based on the object. It can be achieved through method overloading (compile-time) or method overriding (runtime).
Example: A Dog
class overriding the sound()
method of the Animal
class.
What is the difference between Inheritance and Composition?
Aspect | Inheritance | Composition |
---|---|---|
Type of Relationship | “Is-a” relationship (Subclass is a specialized version of the superclass). | “Has-a” relationship (A class has components that perform specific tasks). |
Coupling | Inheritance leads to tight coupling between the parent and child class. | Composition leads to loose coupling, making it easier to change one class without affecting the other. |
Reusability | Reuses code through inheritance of properties and methods. | Reuses code by using objects of other classes as member variables. |
Flexibility | Less flexible, as changes in the parent class can affect all subclasses. | More flexible, as components can be swapped or replaced without affecting the overall class. |
When to use | Best used when there is a clear hierarchical relationship between classes. | Best used when objects are independent and should work together to provide functionality. |
How is Abstraction different from Polymorphism?
Aspect | Abstraction | Polymorphism |
---|---|---|
Definition | Hiding implementation details and exposing only essential functionality. | The ability of an object to take many forms and perform different behaviors. |
Purpose | To simplify complexity and focus on what an object does. | To enable flexibility, allowing a single interface to work with different types of objects. |
Achieved via | Abstract classes and interfaces. | Method Overloading (compile-time) and Method Overriding (runtime). |
Example | Abstract method definitions in an abstract class or interface. | A method with the same name performing different actions in different subclasses. |
What is the difference between an Abstract Class and an Interface?
Feature | Abstract Class | Interface |
---|---|---|
Purpose | Partial abstraction | Full abstraction |
Methods | Can have concrete methods | Only abstract (until Java 8) |
Fields | Can have fields | Only constants |
Multiple Inheritance | Not supported | Supported |
Can an Abstract Class have a constructor? If yes, what is its use if we don’t instantiate the abstract class?
Yes, an abstract class can have a constructor. It is used to initialize fields of the abstract class when a subclass is instantiated.
abstract class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
}
What are Composition and Aggregation? Can you provide examples for each?
Composition and Aggregation are both types of association in object-oriented programming that represent relationships between objects, but they differ in their lifespan and ownership.
1. Composition
Definition:
Composition is a stronger form of association where one object owns another object. If the parent object is destroyed, the child objects are also destroyed. In other words, the child objects cannot exist without the parent object. This is sometimes referred to as a “has-a” relationship.
Key Characteristics:
- The child object cannot exist independently of the parent object.
- The lifetime of the child object is controlled by the parent object.
- Often represented as a “part-of” relationship.
Example:
class Engine {
// Engine class represents a part of the Car
void start() {
System.out.println("Engine is starting");
}
}
class Car {
// Car class has an Engine, and Engine cannot exist without a Car.
private Engine engine;
public Car() {
engine = new Engine(); // Car creates and owns the Engine object
}
void drive() {
engine.start();
System.out.println("Car is moving");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.drive(); // Outputs: Engine is starting Car is moving
}
}
In this example, a Car
has an Engine
. If the Car
object is destroyed, the Engine
is also destroyed because the Engine
is created inside the Car
class. The Engine
cannot exist independently of the Car
, demonstrating composition.
2. Aggregation
Definition:
Aggregation is a weaker form of association where one object uses or has a reference to another object, but the child object can exist independently of the parent object. In this case, the child object can exist without the parent, and the parent does not own the child. This is often referred to as a “has-a” relationship but with independent lifecycles.
Key Characteristics:
- The child object can exist independently of the parent object.
- The lifetime of the child object is not controlled by the parent object.
- Often represented as a “has-a” relationship, but without strong ownership.
Example:
class Department {
private String departmentName;
public Department(String name) {
this.departmentName = name;
}
public String getDepartmentName() {
return departmentName;
}
}
class Employee {
private String employeeName;
private Department department; // Aggregation, employee works in a department
public Employee(String name, Department department) {
this.employeeName = name;
this.department = department;
}
void displayInfo() {
System.out.println(employeeName + " works in the " + department.getDepartmentName() + " department.");
}
}
public class Main {
public static void main(String[] args) {
Department department = new Department("HR");
Employee employee = new Employee("John", department);
employee.displayInfo(); // Outputs: John works in the HR department.
}
}
n this example, an Employee
works in a Department
. The Department
object can exist without the Employee
object, and vice versa. The Employee
does not own the Department
; it is merely associated with it, demonstrating aggregation.
Composition vs Aggregation
Aspect | Composition | Aggregation |
---|---|---|
Ownership | Parent owns the child object. | Parent does not own the child object. |
Lifetime | Child object’s lifecycle is controlled by the parent. | Child object can exist independently of the parent. |
Dependency | Child cannot exist without the parent. | Child can exist independently of the parent. |
Example | Car has an Engine (Engine cannot exist without the Car). | Employee works in a Department (Department can exist independently). |
Can you explain Multilevel Inheritance with an example?
Multilevel Inheritance refers to a scenario where a class inherits from another class, and that class, in turn, inherits from a third class.
class Animal {
void eat() {
System.out.println("This animal eats food");
}
}
class Mammal extends Animal {
void walk() {
System.out.println("This mammal walks");
}
}
class Dog extends Mammal {
void bark() {
System.out.println("This dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
dog.walk();
dog.bark();
}
}
What is Abstraction? How is it different from Encapsulation?
1. Abstraction:
Abstraction is the concept of hiding the implementation details and only exposing the necessary functionality or behavior to the user. It allows you to focus on what an object does, rather than how it does it. In Java, abstraction can be achieved using abstract classes and interfaces.
Purpose:
- Simplifies the interaction with complex systems by exposing only essential features.
- Helps in defining common behavior while leaving the implementation details to subclasses.
2. Encapsulation:
Definition:
Encapsulation is the practice of bundling data (variables) and methods that operate on the data into a single unit (a class). It also involves restricting direct access to some of the object’s components and only exposing certain parts through methods, typically using access modifiers like private
, protected
, or public
.
Purpose:
- Protects the internal state of the object by restricting unauthorized access.
- Provides controlled access to the object’s data using getter and setter methods.
When should you use Encapsulation and Abstraction in a project?
Encapsulation and Abstraction are both fundamental Object-Oriented Programming (OOP) concepts, but they are used in different scenarios based on the design needs of a project.
1. When to use Encapsulation
Definition:
Encapsulation is the concept of hiding the internal details of an object and only exposing the necessary functionality to the outside world. This is typically done by making fields private
and providing public getter and setter methods to access and modify these fields.
When to use it:
- To protect data: When you need to control how data is accessed or modified (e.g., validating input before storing it).
- To simplify maintenance: If the internal implementation of a class changes, encapsulation ensures that the external interface remains consistent. Users of the class won’t need to change their code.
- To enhance security: Encapsulation helps prevent unauthorized access to sensitive data, ensuring that only valid operations can be performed.
2. When to use Abstraction
Definition:
Abstraction is the concept of hiding the complexity and showing only the essential features of an object. It allows you to define abstract classes or interfaces that provide a blueprint, leaving the implementation details to the subclass.
When to use it:
- To hide complexity: Use abstraction when you want to expose only the necessary behavior to the users and hide the internal complexities.
- When designing APIs or frameworks: Abstraction is great for defining common interfaces that various classes can implement, without focusing on the underlying implementation details.
- When you want to ensure flexibility: By defining abstract classes or interfaces, abstraction allows subclasses to provide their specific implementation while keeping the codebase flexible and extendable.
How do you achieve Encapsulation in Java?
Encapsulation is achieved by:
- Declaring fields as private.
- Providing public getter and setter methods to access and update the fields.
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
What is Polymorphism, and how can it be achieved in Java?
Polymorphism allows an object to take multiple forms. It is achieved via:
- Method Overloading (Compile-time polymorphism)
- Method Overriding (Runtime polymorphism)
Example:
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
class Shape {
void draw() {
System.out.println("Drawing Shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing Circle");
}
}
What are Method Overloading and Method Overriding? Can you explain with examples?
1. Method Overloading
Definition:
Method overloading occurs when multiple methods in a class have the same name but different parameter lists (either in number, type, or order of parameters). It allows a class to perform similar operations with different types or numbers of inputs.
Example:
class Calculator {
// Overloaded method with two integer parameters
int add(int a, int b) {
return a + b;
}
// Overloaded method with two double parameters
double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Calls int add(int, int)
System.out.println(calc.add(5.5, 10.5)); // Calls double add(double, double)
}
}
- The
add
method is overloaded to handle both integer and double values. - Based on the parameters passed, the correct method is invoked.
2. Method Overriding
Definition:
Method overriding occurs when a subclass provides a specific implementation of a method that is already defined in its parent class. It allows a subclass to define its own behavior for an inherited method.
Example:
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// Overriding the sound method of the parent class
@Override
void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.sound(); // Output: Animal makes a sound
Dog dog = new Dog();
dog.sound(); // Output: Dog barks
}
}
The Dog
class overrides the sound()
method from the Animal
class.The method in the subclass provides a specific behavior, which is called when the method is invoked on an object of the subclas
What is Method Overriding? How does it work with access specifiers? Can you provide an example?
Method overriding happens when a subclass provides a specific implementation of a method in the parent class. The access specifier of the overriding method must be the same or more permissive.
class Parent {
protected void display() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
public void display() {
System.out.println("Child");
}
}
What are the rules for Method Overriding with exception handling? Can you explain with examples?
- The overriding method cannot throw checked exceptions that are new or broader than those declared in the overridden method.
- It can throw the same or narrower exceptions.
class Parent {
void display() throws IOException {
System.out.println("Parent method");
}
}
class Child extends Parent {
@Override
void display() throws FileNotFoundException {
System.out.println("Child method");
}
}
Can we have overloaded methods with different return types? Why or why not?
No, we cannot overload methods in Java based solely on return type. Method overloading requires differences in the parameter list (number, type, or order of parameters). Java does not consider the return type when determining if methods are overloaded, so having only a different return type results in a compile-time error.
Can we override a static method in Java? Why can’t we do that?
No, we cannot override a static method in Java. Static methods are bound to the class, not instances, and are resolved at compile-time based on the reference type, not the object type. When a subclass defines a static method with the same signature, it hides the method from the superclass, but does not override it. Static methods do not participate in runtime polymorphism.
Key point: It’s called method hiding, not overriding.
What are the SOLID principles in Java? Can you explain them with examples?
- S: Single Responsibility Principle
- O: Open-Closed Principle
- L: Liskov Substitution Principle
- I: Interface Segregation Principle
- D: Dependency Inversion Principle
1. Single Responsibility Principle (SRP):
Definition:
A class should have only one reason to change, meaning it should only have one job or responsibility. This makes the class easier to understand and maintain.
Example:
// Violating SRP: The class has multiple responsibilities
class Employee {
private String name;
private int id;
public void calculateSalary() {
// Calculate salary logic
}
public void saveToDatabase() {
// Save employee data to the database
}
}
// Adhering to SRP: Split the responsibilities
class Employee {
private String name;
private int id;
public void calculateSalary() {
// Calculate salary logic
}
}
class EmployeeDatabase {
public void saveToDatabase(Employee employee) {
// Save employee data to the database
}
}
In the violating example, the Employee
class has two responsibilities: managing employee data and saving it to the database. In the adhering example, the responsibilities are split into two classes: Employee
for employee-related logic and EmployeeDatabase
for persistence logic.
2. Open/Closed Principle (OCP):
Definition:
A class should be open for extension, but closed for modification. This means you can extend the behavior of a class without changing its existing code.
Example:
// Violating OCP: Modifying existing class to add new behavior
class Discount {
public double calculateDiscount(Product product) {
if (product.getCategory().equals("Electronics")) {
return 10;
} else if (product.getCategory().equals("Clothing")) {
return 15;
}
return 0;
}
}
// Adhering to OCP: Using inheritance to extend behavior
abstract class Discount {
public abstract double calculateDiscount(Product product);
}
class ElectronicsDiscount extends Discount {
public double calculateDiscount(Product product) {
return 10;
}
}
class ClothingDiscount extends Discount {
public double calculateDiscount(Product product) {
return 15;
}
}
In the violating example, you would need to modify the Discount
class whenever a new category is added. The adhering example uses inheritance, allowing you to add new discount types without modifying the base class.
3. Liskov Substitution Principle (LSP):
Definition:
Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. Subtypes must be substitutable for their base types.
Example:
// Violating LSP: Subclass changes the behavior of the superclass method
class Bird {
public void fly() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Ostriches can't fly");
}
}
// Adhering to LSP: Proper substitution of subclasses
abstract class Bird {
public abstract void move();
}
class Sparrow extends Bird {
public void move() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
public void move() {
System.out.println("Running");
}
}
In the violating example, Ostrich
doesn’t adhere to the expected behavior of Bird
by throwing an exception. The adhering example uses an abstract move()
method, allowing subclasses to implement their own version of movement without violating the principle.
4. Interface Segregation Principle (ISP):
Definition:
Clients should not be forced to depend on interfaces they do not use. It’s better to have many small, specific interfaces rather than a large, general-purpose interface.
Example:
// Violating ISP: A large interface with methods not needed by all clients
interface Worker {
void work();
void eat();
}
class WorkerImpl implements Worker {
public void work() {
System.out.println("Working");
}
public void eat() {
System.out.println("Eating");
}
}
class Robot implements Worker {
public void work() {
System.out.println("Working");
}
public void eat() {
// Robots don’t eat, so this is unnecessary
}
}
// Adhering to ISP: Segregated interfaces
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Robot implements Workable {
public void work() {
System.out.println("Working");
}
}
class Human implements Workable, Eatable {
public void work() {
System.out.println("Working");
}
public void eat() {
System.out.println("Eating");
}
}
5. Dependency Inversion Principle (DIP):
Definition:
High-level modules should not depend on low-level modules. Both should depend on abstractions. Also, abstractions should not depend on details. Details should depend on abstractions.
Example:
// Violating DIP: High-level module depends directly on low-level module
class LightBulb {
public void turnOn() {
System.out.println("Bulb turned on");
}
public void turnOff() {
System.out.println("Bulb turned off");
}
}
class Switch {
private LightBulb bulb;
public Switch(LightBulb bulb) {
this.bulb = bulb;
}
public void operate() {
bulb.turnOn();
}
}
// Adhering to DIP: Use of abstractions (interfaces)
interface Switchable {
void turnOn();
void turnOff();
}
class LightBulb implements Switchable {
public void turnOn() {
System.out.println("Bulb turned on");
}
public void turnOff() {
System.out.println("Bulb turned off");
}
}
class Switch {
private Switchable device;
public Switch(Switchable device) {
this.device = device;
}
public void operate() {
device.turnOn();
}
}
In the violating example, Switch
directly depends on the LightBulb
class. In the adhering example, both Switch
and LightBulb
depend on the Switchable
interface, adhering to the Dependency Inversion Principle.
What is the difference between Cohesion and Coupling? Can you explain with examples?
Cohesion and Coupling are two important concepts in software design that help define how different parts of a program interact. They are key to writing maintainable, scalable, and modular code.
1. Cohesion:
Cohesion refers to how closely the methods and properties of a class or module are related to each other. In other words, it measures the degree to which the elements of a class or module work together to perform a single, well-defined task.
- High Cohesion: A class or module has high cohesion if its methods and variables are highly related and focus on a single responsibility. This makes the class easier to understand, maintain, and test.
- Low Cohesion: A class with low cohesion does multiple unrelated tasks, which can make it more difficult to understand and maintain.
Example of High Cohesion:
class Calculator {
private int value;
public Calculator(int value) {
this.value = value;
}
public int add(int num) {
return this.value + num;
}
public int subtract(int num) {
return this.value - num;
}
public int multiply(int num) {
return this.value * num;
}
public int divide(int num) {
if (num != 0) {
return this.value / num;
}
throw new ArithmeticException("Division by zero");
}
}
In the above ex, the Calculator
class is highly cohesive because all methods perform arithmetic operations related to the value
attribute, which makes it focused on a single task (calculations).
Example of Low Cohesion:
class Utility {
public void saveToDatabase() {
// Save data to the database
}
public void sendEmail() {
// Send email
}
public void formatDate() {
// Format a date
}
}
Here, the Utility
class has low cohesion because it performs several unrelated tasks (saving data, sending emails, and formatting dates), making it harder to maintain and understand.
2. Coupling:
Coupling refers to the degree of dependency between different classes or modules. It describes how tightly or loosely connected two classes are. In general, you want to aim for low coupling, where classes are minimally dependent on each other, which leads to more flexible, reusable, and maintainable code.
- Tight (High) Coupling: Classes are highly dependent on each other, making changes to one class more likely to affect others.
- Loose (Low) Coupling: Classes are independent of each other, meaning that changes to one class have little or no effect on others.
Example of High Coupling:
class Order {
private Customer customer;
public void processOrder() {
customer.sendEmail(); // Directly calling method of Customer
// Process the order
}
}
class Customer {
public void sendEmail() {
// Send email to customer
}
}
In the above example, the Order
class has high coupling with the Customer
class because Order
directly calls a method of Customer
. If the Customer
class changes, the Order
class may also need to change.
Example of Low Coupling:
interface NotificationService {
void sendEmail();
}
class EmailService implements NotificationService {
public void sendEmail() {
// Send email
}
}
class Order {
private NotificationService notificationService;
public Order(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void processOrder() {
notificationService.sendEmail(); // Loosely coupled
// Process the order
}
}
Differences:
- Cohesion is about how related the tasks within a class are. High cohesion means a class is focused on a single responsibility, while low cohesion means the class has multiple unrelated responsibilities.
- Coupling is about how dependent classes are on each other. Low coupling means classes are independent and changes in one class won’t significantly impact others, while high coupling means classes are tightly dependent on each other.
What is the use of the static keyword in Java?
In Java, the static
keyword is used to indicate that a particular field, method, or inner class belongs to the class itself rather than to instances of the class. This means that static members are shared among all instances of the class and are accessed without the need for creating an object of that class. It provides memory efficiency and allows certain members to be accessed directly from the class.
Uses:
- Static Variables: Shared among all instances of the class.
class Counter {
static int count = 0;
public Counter() {
count++;
}
}
2. Static Methods: Can be called without creating an instance of the class.
class MathUtility {
static int add(int a, int b) {
return a + b;
}
}
3. Static Block: Used for one-time initialization of static variables.
static {
// initialization code
}
4. Static Inner Classes: Can be instantiated without an outer class instance.
class Outer {
static class Inner {}
}
What is Covariant Return Type in Java? Can you explain with an example?
Covariant Return Type in Java refers to the ability of an overridden method to return a type that is a subtype (i.e., more specific type) of the return type of the method in the superclass. In simpler terms, it allows a method in a subclass to return a more specific type than the method in the parent class, while still maintaining the original method signature.
This feature was introduced in Java 5 and is a way to ensure that the return type is still compatible with the parent class, but allows more flexibility in the subclass.
Advantages of Covariant Return Type:
It helps to retain polymorphism when working with subclass objects, while still adhering to the class hierarchy.
It allows more specific return types to be used in subclasses, improving code readability and type safety.
Example:
class Vehicle {
// Method returns a type of Vehicle
public Vehicle getInstance() {
return new Vehicle();
}
}
class Car extends Vehicle {
// Overriding the method to return a more specific type (Car)
@Override
public Car getInstance() {
return new Car();
}
}
class Bike extends Vehicle {
// Overriding the method to return a more specific type (Bike)
@Override
public Bike getInstance() {
return new Bike();
}
}
public class Main {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
Vehicle carVehicle = new Car();
Vehicle bikeVehicle = new Bike();
// Calling getInstance() on a Vehicle object
Vehicle vehicleInstance = vehicle.getInstance();
System.out.println(vehicleInstance.getClass().getName()); // Outputs: Vehicle
// Calling getInstance() on a Car object
Car carInstance = carVehicle.getInstance();
System.out.println(carInstance.getClass().getName()); // Outputs: Car
// Calling getInstance() on a Bike object
Bike bikeInstance = bikeVehicle.getInstance();
System.out.println(bikeInstance.getClass().getName()); // Outputs: Bike
}
}
What exactly is Abstraction? What are its advantages over Concrete Classes?
Abstraction in Java is the concept of hiding the implementation details of a class and exposing only the essential features or behaviors. It allows us to focus on what a system does rather than how it does it. In Java, abstraction is achieved using abstract classes and interfaces, where you define methods without providing a concrete implementation. Concrete classes, on the other hand, provide full implementations of methods.
Advantages of Abstraction over Concrete Classes:
- Simplifies Maintenance: Since abstraction hides complex implementation details, the system becomes easier to maintain and extend. If the implementation of a method changes, the abstract class or interface doesn’t require changes in the classes that use it.
- Increases Reusability: Abstraction allows you to define general-purpose interfaces or abstract classes that can be reused by different concrete classes. This promotes the reuse of code and minimizes redundancy.
- Enhances Flexibility and Modularity: Abstraction separates the “what” from the “how,” making the system more flexible. You can change the implementation of a class without affecting the classes that depend on the abstract class or interface, thus maintaining the system’s modularity.
- Improves Code Readability and Understandability: By hiding complex details, abstraction helps reduce the cognitive load required to understand the code. It allows developers to focus on high-level concepts rather than getting bogged down in implementation specifics.
abstract class Animal {
// Abstract method (does not have a body)
public abstract void sound();
// Concrete method (does have a body)
public void sleep() {
System.out.println("This animal is sleeping");
}
}
class Dog extends Animal {
// Providing implementation of the abstract method
public void sound() {
System.out.println("Dog barks");
}
}
class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.sound(); // Outputs: Dog barks
dog.sleep(); // Outputs: This animal is sleeping
}
}
In above example:
- Abstraction is achieved using the
Animal
abstract class, which provides the blueprint (sound()
method) and some default behavior (sleep()
method). - The concrete class
Dog
provides its own implementation of the abstract methodsound()
.
What is a Default Method in an Interface? What is its purpose?
A default method in an interface is a method with a body, introduced in Java 8. It allows interfaces to provide a default implementation for methods, which can be used by classes that implement the interface. The purpose of default methods is to enable backward compatibility when adding new methods to interfaces without breaking existing implementations. It also helps avoid code duplication by allowing common behavior to be shared across multiple classes.
Example:
interface Vehicle {
default void start() {
System.out.println("Vehicle is starting");
}
void stop();
}
class Car implements Vehicle {
public void stop() {
System.out.println("Car has stopped");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.start(); // Uses default implementation
car.stop(); // Uses custom implementation
}
}
What are the rules for overriding methods with respect to exceptions?
When overriding methods in Java, there are certain rules regarding exceptions that must be followed to ensure consistency and proper exception handling. The overriding method cannot throw checked exceptions that are new or broader than the exceptions declared by the overridden method in the superclass. Let’s break this down in more detail:
- Cannot Throw Broader Checked Exceptions: If the superclass method throws a specific checked exception, the overriding method in the subclass cannot throw a broader checked exception. For example, if the parent method throws
IOException
, the overriding method cannot throw a more general exception likeException
orThrowable
.
class Animal {
public void makeSound() throws IOException {
System.out.println("Animal makes a sound.");
}
}
class Dog extends Animal {
// This is valid as IOException is the same exception type
@Override
public void makeSound() throws IOException {
System.out.println("Dog barks.");
}
// This would cause a compile-time error
// @Override
// public void makeSound() throws Exception { // Error: cannot throw 'Exception' because 'IOException' is narrower
// System.out.println("Dog barks.");
// }
}
In above example, if we try to throw Exception
in the subclass, we’ll get a compile-time error because Exception
is broader than IOException
, which was declared in the superclass.
What is the difference between static variables and instance variables?
When you work with classes in Java, you’ll come across two types of variables: static variables and instance variables. Although they might look similar at first glance, they serve very different purposes. Let’s break down the differences in a clear, easy-to-understand way.
1. Static Variables:
A static variable is a variable that is shared by all instances (objects) of a class. It is a class-level variable, meaning there is only one copy of the static variable in memory, regardless of how many objects of the class are created.
- Key points:
- Shared across all instances of the class.
- Can be accessed using the class name or through an instance of the class (though the former is preferred for clarity).
- Static variables are initialized only once when the class is loaded into memory.
- They are typically used for values that should be common to all instances of a class, such as constants or counters.
2. Instance Variables:
An instance variable, on the other hand, is tied to a specific instance (object) of the class. Each object has its own copy of the instance variables, meaning the values of these variables can be different for each object.
- Key points:
- Unique to each instance of the class.
- Each time you create a new object, the instance variables are initialized for that particular object.
- Can have different values for each object.
- Typically used to represent the state or attributes of an object.
Example:
public class Car {
// Instance variable: each car will have its own value for this.
private String model;
// Static variable: shared by all instances of Car.
private static int carCount = 0;
// Constructor to initialize instance variable and increment static variable
public Car(String model) {
this.model = model;
carCount++; // Increment the static carCount variable each time a new car is created
}
// Getter for model
public String getModel() {
return model;
}
// Static method to get the total count of cars created
public static int getCarCount() {
return carCount;
}
}
public class Main {
public static void main(String[] args) {
// Creating car objects with different models
Car car1 = new Car("Tesla Model S");
Car car2 = new Car("BMW X5");
// Accessing instance variable through the car objects
System.out.println(car1.getModel()); // Output: Tesla Model S
System.out.println(car2.getModel()); // Output: BMW X5
// Accessing static variable through the class name
System.out.println("Total cars created: " + Car.getCarCount()); // Output: 2
}
}
How can you make a class immutable in Java?
- Declare the class as
final
: This prevents any subclass from modifying the behavior or state of the class. - Make all fields
private
andfinal
: This ensures the fields are not directly accessible and their values cannot be changed after initialization. - No setter methods: Setter methods would allow changing the values of fields, which would make the class mutable.
- Initialize fields via the constructor: All fields are set when the object is created, ensuring that the object cannot change after construction.
Example: Making a Person
class Immutable in Java
// Step 1: Declare the class as final so it cannot be subclassed.
public final class Person {
// Step 2: Declare all fields as private and final.
private final String name;
private final int age;
// Step 3: Initialize fields via a constructor.
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Step 4: Provide getter methods but no setter methods.
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Why Immutable Classes?
- Thread-Safety: Immutable objects are inherently thread-safe because their state cannot be changed after they are created.
- Security: Since their state is final and cannot be modified, they provide better security in scenarios like passing objects between methods or threads.
- Predictability: Immutable objects are easier to reason about and debug since their state does not change.