If two interfaces have the same default method and a class implements both, what happens? How can this conflict be resolved?
If two interfaces (A and B) have the same default method and a class implements both, the compiler will throw an error because of ambiguity.
To fix it, the class must override the method !
Inside the override -
(1) we can choose to call a specific interface’s default implementation using InterfaceName.super.method()
OR
(2) write our own logic.
interface A {
default void show() {
System.out.println("A's show()");
}
}
interface B {
default void show() {
System.out.println("B's show()");
}
}
class Test implements A, B {
@Override
public void show() { // one override method handles both interface defaults.
// explicitly call A's version
A.super.show();
// explicitly call B's version
B.super.show();
// custom logic
System.out.println("Test class's own show()");
}
}
public class Main {
public static void main(String[] args) {
Test t = new Test();
t.show();
}
}
A.super.show(); → prints A's show()
B.super.show(); → prints B's show()
System.out.println("Test class's own show()"); → prints Test class's own show()
> Method overloading means creating MULTIPLE METHODS with SAME NAME but DIFFERENT PARAMETERS in the SAME CLASS.
> It helps you perform similar actions with different inputs.
> This is also called COMPILE TIME POLYMORPHISM!
NOTES : > We must write the return type in every method.
> Java ignores it while checking if two methods are overloaded.
> Overloading depends only on METHOD NAME, NUM OF PARAMETERS, TYPE and ORDER not on RETURN TYPE.class Calculator {
int sum(int a, int b) { // 2 parameters - int
return a + b;
}
int sum(int a, int b, int c) { // 3 parameters - int
return a + b + c;
double sum(int a, double b, int c) { int & double
return a + b + c;
}
}When a method involves both int and double parameters, any arithmetic operation between them results in a double value.
This is because Java automatically promotes the smaller data type (int, 4 bytes) to the larger one (double, 8 bytes) to avoid data loss.
So, the return type should be double.
If we try to return this double result using int as the return type, Java will throw a "possible lossy conversion" error.
That's because an int can only hold whole numbers and has less storage — using it would risk losing the decimal part of the result.
NOTES:
> In Java, when we mix different data types (like int and double) > Java automatically promotes the smaller type to the larger one — this is called implicit type casting (or widening).
> But if we try to store a larger type (like double) into a smaller one (like int), we must use explicit type casting, or Java will give a lossy conversion error.
double sum(int a, double b, int c) {
return a + b + c; // correct - result is double
}
int sum(int a, double b, int c) {
return a + b + c; // Error: lossy conversion from double to int
}
int sum(int a, double b, int c) {
return (int)(a + b + c); // Works if we cast, but decimal is lost
}> Method overriding happens when a child class (subclass) gives its own version of a method that already exists in the parent class (superclass) — using the same method name and signature.
> It happens between a parent and child class using inheritance, and is used to achieve runtime polymorphism.
NOTES:
> Overriding = child redefines the parent method
> Inheriting = child uses the parent method as-is
class Animal {. // Parent class
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal { // child class
@Override
void sound() {
System.out.println("Dog barks");
}
}== compares object references (memory addresses)
.equals() compares values (contents)
NOTES:
== compares values for primitives( int, float,double)
== compares memory addresses for objects (String, Integer etc)
.equals() compares content for objects
.equals() does not exist for primitives// For Non -Primitives
String a = "Java"; // Stored in String Intern Pool
String b = new String("Java"); // Object created in Heap
a == b // false → different memory locations
a.equals(b) // true → same content// For Primitives
int a = 5; // For primitive Types > == compares actual values
int b = 5; // No .equals() method for primitives
System.out.println(a == b); // true → because 5 == 5 > static variable → belongs to the class, shared across all objects.
> There is only ONE COPY of it, and it's shared by all instances (objects) of that class.
> static method → belongs to the class, not tied to objects.
> Static methods cannot be overridden, only hidden.
NOTE: Static members are loaded into memory ONLY ONCE
— when the class is loaded .
— and are shared across all objects.
(Static Members - static variables, methods, blocks, and nested classes)> A constructor is a special method with same name as class name!
> Constructors initialize instance variables when an object is created.
> It cannot be inherited, but the subclass can call the superclass constructor using super().
static variables are stored in the Method Area (or Class Area).
They are loaded once when the class is loaded into memory and are shared across all objects.
instance variables are stored in the heap, and every object gets its own copy.
No, interfaces cannot have constructors.
Because objects can't be created from interfaces directly.
Interfaces are meant to be implemented by classes, and only those classes can define constructors.
If the parent class has only a parameterized constructor and we don’t explicitly call super(args) in the child constructor, Java throws a compile-time error:
Error - constructor Parent in class Parent cannot be applied to given types.
But if the parent has a no-arg constructor - Java automatically inserts a call to super() in the first line of the child constructor.
## If class B extends class A, and both have a method show(), which one is called when wewrite A obj = new B()?
```java
class A {
void show() {
System.out.println("Show from A");
}
}
class B extends A {
void show() {
System.out.println("Show from B");
}
}
public class Main {
public static void main(String[] args) {
A obj = new B(); // Parent reference, Child object
obj.show(); // Output: Show from B
}
}The reference type is the parent, but the object type is the child.
At compile time: Java checks if the reference type (A) has the method show() - Yes, it exists → So no compile-time error.
At runtime: Java looks at the actual object (new B()) >> Since B overrides the show() method, Java calls the child class's version.
Even though obj is of type A, the show() method from class B is called — because the actual object is of type B - This is runtime polymorphism (also called dynamic method dispatch).
(1) Encapsulation (2) Abstraction (3) Inheritance (4) Polymorphism
Encapsulation protects the instance variables from invalid value assignments, which is achieved through public getter and setter methods by writing the validation logic.
Prevents illegal access or modification of data.
public class Student {
private String name; // Encapsulated variable
public void setName(String name) { // setter
this.name = name;
}
public String getName() { // getter
return name;
}
}|| Feature | Abstract Class | Interface |
| ----------------------- | ------------------------------------------ | ---------------------------------------------------- |
| Keyword | `abstract` | interface / implements |
| Object Creation | Cannot create object | Cannot create object |
| Constructor. | Can have constructors | Cannot have constructors |
| Methods. | Can have `abstract` and `concrete` methods | Can have `abstract`, `default`, and `static` methods |
| Inheritance. | Use `extends` | Use `implements` |
| Abstraction Type | Partial abstraction | Full abstraction |
| Inheritance Support | Single inheritance only. | Multiple inheritance via interfaces. |
Abstraction = Hiding implementation details and showing only the essential features (the “what”, not the “how”).
An abstract class provides partial abstraction because it can contain both abstract (unimplemented) and concrete (implemented) methods.
What is Partial Abstraction?
It can have - (1) abstract methods (no body) and (2) concrete methods (with body).
That means - some behavior is already defined, and some must be provided by child classes.
So it's partially abstract — not everything is hidden or forced to be implemented
abstract class Vehicle {
abstract void start(); // no body — abstract
void fuelType() { // has body — concrete
System.out.println("Petrol or Diesel");
}
}
This is partial abstraction, because:
One method (start()) is abstract — child must override it
One method (fuelType()) is already implemented - So the child class only needs to implement part of the logic.Interface Vehicle {
void start(); // abstract
void fuelType(); // abstract
}This is full abstraction because: There is no implementation at all (before Java 8).
An interface provides full abstraction (before Java 8) because it only contains method declarations and no implementation.The implementing class must define everything
After Java 8 default and static methods are introduced - Still Interface is considered to provide full abstraction -
The reason is — interface is primarily meant to define only abstract behavior without implementation.
The default and static methods are optional utility methods that do not break the purpose of abstraction.
(1) Unlike abstract classes, interfaces:
(2) Cannot have instance variables
(3) Cannot have constructors
(4) And cannot be instantiated directly.
So while technically it has some implementation now, it still enforces no object creation and no concrete state, which qualifies it as full abstraction in the design sense.
Problem: Create a class BankAccount with:
A private variable balance.
A setter deposit(double amount) → only accept if amount > 0.
A setter withdraw(double amount) → only allow if amount <= balance.
A getter getBalance() → to check balance.
Main Method Goal: Deposit 500, withdraw 200, withdraw 400 → observe validations.
package Encapsulation;
public class BankAccount {
private double balance;
// Only one private variable is enough
// Deposit method
public void deposit(double amount) {
if (amount > 0) {
balance += amount; // increase balance
System.out.println("Deposited: " + amount);
} else {
System.out.println("Invalid deposit amount!");
}
}
// Withdraw method
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount; // decrease balance
System.out.println("Withdrawn: " + amount);
} else {
System.out.println("Insufficient balance!");
}
}
// Getter for balance
public double getBalance() {
return balance;
}
}package Encapsulation;
public class BankAccountRunner {
public static void main(String[] args){
BankAccount account = new BankAccount(); // create object
account.deposit(500); // Deposit 500
account.withdraw(200); // Withdraw 200
account.withdraw(400); // Withdraw 400 -> should fail
System.out.println("Final Balance: " + account.getBalance());
}
}Usually we create getters and setters from our instance variables!
But here in your BankAccount case, depositAmount and withdrawAmount are temporary inputs, not state that belongs to the object.
The real state of the account is just the balance.
deposit(500) → means “add 500 to balance.”
withdraw(200) → means “subtract 200 from balance.”
Since depositAmount and withdrawAmount don’t need to be remembered, WE don’t create them as instance variables. We just use them as method parameters.
so, We only need one variable: balance, because that’s the actual state of the account.
Encapsulation principle → only keep variables that represent the state of the object.
In short: Balance is state, deposit/withdraw are actions (method parameters), so we don’t store them as separate variables.
Problem:Create a class Student with: Private variables: name, rollNo.
Constructor to initialize them.
Only getters (no setters).
Main Method Goal: Create student object and print details.
Try modifying → should not be allowed.
package Encapsulation;
public class Student {
private int rollnumber;
private String name;
public Student(int rollnumber, String name) {
this.rollnumber = rollnumber;
this.name = name;
}
public int getRollnumber() {
return rollnumber;
}
public String getName() {
return name;
}
}package Encapsulation;
public class StudentRunner {
public static void main (String[] args){
Student s1 = new Student(45,"Athira");
// Print details
System.out.println("Roll No: " + s1.getRollnumber());
System.out.println("Name: " + s1.getName());
// Try modifying
s1.rollnumber = 100;
//java error: rollnumber has private access in Encapsulation.Student
// s1.name = "New Name"; // ERROR → private variable
// No setter available
// s1.setName("New Name"); // ERROR → no method defined
// ✅ Values remain unchanged
System.out.println("After trying modification:");
System.out.println("Roll No: " + s1.getRollnumber());
System.out.println("Name: " + s1.getName());
}
}Setter = lets you modify/change the variable.
Getter = only allows you to read/access the variable.
In our example, we only gave getters and no setters.So:
We can read the student’s details (via getName(), getRollnumber()
We cannot modify them (because no setName() or setRollnumber()
Integer x = 1000;
Integer y = 1000;
System.out.print(x==y);
Integer a = 100;
Integer b = 100;
System.out.print(a==b);Integer x = 1000;
Integer y = 1000;
Interger is a wrapper class (Wrapper class is an object representation of primitive type)
Java caches Integer objects only in the range -128 to 127.
Since 1000 is outside that range, new objects are created.
When we write Integer x = 1000; → autoboxing happens.
Java calls Integer.valueOf(1000).
Integer.valueOf() checks the Integer cache (not the general heap)
- Cache range = -128 to 127.
If the number is inside that range → it returns the same cached object.
If outside → it creates a new Integer object on the heap.
x == y → false
Integer a = 100;
Integer b = 100;
System.out.print(a == b);
Value 100 is inside the cache range (-128 to 127).
So, a and b point to the same cached object.
a and b themselves (the references) are stored on the stack (as local variables).
The actual Integer object (with value 100) is stored in the heap (cached region).
Both a and b reference the same object.
a == b → true