A comprehensive guide to advanced Object-Oriented Programming (OOP) concepts in Java, with FAQs and interview questions for deeper understanding.

14.What is method overriding in Java and why is it used?
Method overriding in Java is a situation where a subclass provides a specific implementation of a method that is already defined in its superclass. This is done to change or extend the behavior of the inherited method. Overriding allows a subclass to provide its own behavior for a method while still keeping the method signature the same as in the superclass. It is used to achieve runtime polymorphism, which allows the correct method to be called based on the type of object at runtime.
Example:
class Animal {
void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
Here, the Dog class overrides the sound method of the Animal class with a more specific implementation.


15.What is polymorphism in Java and how it works?
In Java, Polymorphism refers to the ability of a single entity (method, object or operator) to take multiple forms.
There are two types of polymorphism in Java: method overloading which is compile time polymorphism and method overriding which is runtime polymorphism. This provides an avenue for making code more versatile and extensible with the aid of polymorphism since you use one common interface when dealing with more than one type of data.
Polymorphism in Java: It helps it call a particular method that relates to its real class with regard to its real class irrespective of the superclass or interface to which it might have been declared as a reference variable.
Use Example:
class Animal {
void sound() {
System.out.println("Animal makes sound");
}
}

class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}

class Cat extends Animal {
void sound() {
System.out.println("Cat meows");
}
}

public class TestPolymorphism {
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.sound(); /* Calls Dog's sound method*/
}
}
Here, polymorphism allows a Dog object to be treated as an Animal and the correct sound() method is invoked at runtime.


16.What is the difference between compile time and runtime polymorphism in Java?
In Java, compile time polymorphism also known as method overloading is when a method is selected at compile time based on the method signature that is the number or type of parameters. Runtime polymorphism also known as method overriding is when a method is chosen at runtime based on the actual object type, not the reference type. Compile time polymorphism is resolved by the compiler where as runtime polymorphism is resolved during the execution of the program.
Example of compile-time polymorphism:
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
Here, the appropriate add method is chosen based on parameters at compile time.


17.What is method overloading in Java and how does it differ from method overriding?
Method overloading in Java occurs when two or more methods in the same class have the same name but different parameter lists either in number, type or order of parameters. Overloading is a type of compile time polymorphism. This includes method overriding which is when one redefines the method of a subclass that already exists in a superclass with an identical method signature. The two differ as their resolution occurs during compile time and runtime respectively.
Here's an example of method overloading.
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}


18.What is abstraction in Java? How is abstraction achieved using abstract classes and interfaces?
Abstraction in Java is just the process of hiding the complex implementation details from the user and just exposing the essential features. The two ways provided by Java for achieving abstraction are abstract classes and interfaces.
•Abstract classes can have both abstract (without implementation) and concrete (with implementation) methods. A class is declared abstract when it cannot be instantiated directly.
•Interfaces define abstract methods but cannot provide method implementations (except for default and static methods introduced in Java 8).
Both abstract classes and interfaces allow a class to focus on what actions should be performed, without worrying about how they are executed.


19.What is the difference between an abstract class and an interface in Java?
Abstract classes and interfaces in Java are different in the following ways:
• Abstract Classes: Can have both abstract and concrete methods. Can have member variables (fields), constructors and methods with implementation. A class can inherit only one abstract class due to single inheritance.
•Interfaces: Only declare abstract methods except for default and static methods and a class can implement multiple interfaces which allows multiple inheritance. Interfaces cannot have constructors.


20.Define encapsulation in Java and tell why it is good for data security and code maintainability?
It is a practice in encapsulation of keeping fields or data and methods or functions that work on the data together within a class but limiting access to the inner workings of the class. Encapsulation in Java can be implemented through the use of access modifiers like private, protected and public to limit the access of fields. Accessing the data usually occurs through the use of getter and setter methods, thereby securing and ensuring the integrity of data.
Encapsulation is the hiding of an object's internal implementation to prevent access that may lead to data compromise or other changes from the outside environment. This characteristic prevents unauthorized people from accessing certain data and even allows the inside implementation to change without affecting any other part of the program.
It improves the maintainability in the code with modularity that makes it more understandable.
class Employee {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}