Table of contents
Inheritance (`extends`/ is-a)
Understanding Method Variations
Overridden Methods: Subclasses can modify the behavior of inherited methods.
Specialized Methods: Subclasses can introduce new methods specific to their type
Inherited Methods: Subclasses automatically inherit methods from their superclass
Java's Universal Base
Every Java class implicitly extends the
Object
class. This universal inheritance ensures all objects have basic functionalities liketoString()
andequals()
.Upcasting:
Upcasting allows treating a subclass object as its superclass type. This offers flexibility in handling objects
Inherited Variables:
Superclass reference can directly access its own variables.
Subclass inherits non-private variables from the superclass but cannot access private variables directly.
Overridden Methods:
Superclass can call overridden methods but executes the overridden version defined in the subclass.
@Override ensures that the compiler checks whether the method signature matches that of a method in the superclass. This helps catch errors where there might be typos in patent class method names
Superclass cannot access subclass-specific variables/methods directly.
Coming from a C++ background , we can understand that in java all methods are by default virtual (late binding understands the context of the object, rather than context of the caller)
Specialized Methods:
Superclass reference cannot access specialized methods (methods unique to subclasses) directly.
Subclass-specific variables/methods are hidden from the superclass type unless downcasted to the subclass type.
Constructor Dynamics
Constructors don't participate in inheritance however, a subclass constructor implicitly calls its superclass constructor using
super()
. If a subclass constructor callsthis()
instead, it overrides the implicitsuper()
call, skipping the superclass's default constructor.- this is used to implement constructor chainingUtilizing
this()
andsuper()
this()
: Used within a class to invoke another constructor within the same class, aiding in constructor overloading and chaining.super()
: Invokes the superclass constructor, ensuring proper initialization of superclass attributes before subclass-specific tasks.
Access Boundaries: Super's Limitations
The
super
keyword can't directly access variables or methods of a grandparent class (super.super.a
is invalid).
Role of Access Specifiers in Inheritance
While overriding a function
you cannot reduce the visibility of the methods you are overriding
you can increase the visibility of methods you are overriding
you cannot change the return type . Except in one case , when the return type is a covariant return type, is-a relationship between types
you cannot change parameter list of a a function you want to override, if you do it would be overloaded instead
Polymorphism
Same code doing multiple things according to context , for example animal.speak() will return "Woof" or "Meow" according to the type of instance object , and if instance object has overridden the base class speak implementation
Overloading - compile time VS Overriding - runtime
Compile time polymorphism - method overloading and constructor overloading
Runtime polymorphism - method overriding and Polymorphic Method Invocation
Abstraction
Abstract keyword in Java
in java we can have method signatures only without body , such methods should be declared as abstract
Abstract methods can only be made in abstract class
Any class with even one abstract method ,MUST be declared as abstract
Abstract class can have both abstract as well as concrete methods
An abstract method must either be overridden or the inheriting class should also be made abstract
abstractions are used to define the specification but not the details
you cannot instantiate objects of an abstract class
A class can be abstract, a method can be abstract , but never a variable
An abstract class can have a constructor
you cannot make constructor abstract , it has a default body with super () and not even inherited so there is no point of it being overridden as it is with other methods which are abstract in parent class
Abstract class can have a constructor and it gets executed because of super() call present in the child class constructor
Final keyword in java
final variable cannot be re-assigned
final methods although participate in inheritance , cannot be overridden
final class cannot be inherited from ie We cannot inherit from final class , so it can also cannot be made abstract (what would even be the point ?)
Abstract class / methods can never be final
Static keyword in java
static keywords are initialised once for the class and are not instance specific
static methods although participate in inheritance, cannot be overridden, but ! if you try to do so,it will become a specialised method of child class and it will hide the parents inherited implementation- This is called method hiding
Static Variables
Static variables, also known as class variables, are shared across all instances of a class.
Declaration:
public class MyClass { public static int staticVariable = 0; }
Characteristics:
Shared Across Instances: All instances share the same static variable.
Memory Allocation: Allocated once when the class is loaded.
Accessed via Class Name:
MyClass.staticVariable
.
Usage Example:
public class MyClass { public static int counter = 0; public MyClass() { counter++; } public static void main(String[] args) { MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); System.out.println(MyClass.counter); // Output: 2 } }
Static Methods
Static methods belong to the class, not any instance. They can't be overridden, but they can be hidden.
Declaration:
public class MyClass { public static void staticMethod() { System.out.println("Static method called"); } }
Characteristics:
No Access to Instance Variables: Only access static variables and methods.
Called via Class Name:
MyClass.staticMethod()
.Cannot Be Overridden: Static methods are hidden, not overridden. If a subclass defines a static method with the same signature, it hides the superclass method.
Usage Example:
public class MyClass { public static void printMessage() { System.out.println("Hello, World!"); } public static void main(String[] args) { MyClass.printMessage(); // Output: Hello, World! } }
Static Blocks
Static blocks initialize static variables or execute static initialization code. Executed when the class is loaded.
Declaration:
public class MyClass { static { // Initialization code } }
Characteristics:
Executed Once: When the class is loaded.
Order of Execution: Multiple static blocks execute in the order they appear.
Usage Example:
public class MyClass { public static int value; static { value = 42; System.out.println("Static block executed"); } public static void main(String[] args) { System.out.println(MyClass.value); // Output: 42 } }
Static Nested Classes
Static nested classes are defined within another class and do not have a reference to an instance of the enclosing class.
Declaration:
public class OuterClass { public static class NestedClass { // Class code } }
Characteristics:
No Reference to Enclosing Instance: Can't access instance variables or methods of the enclosing class directly.
Accessed via Class Name:
OuterClass.NestedClass
.Static nested classes are often used to group related classes and interfaces together for better organization. For example,
Pair
is likely a utility class used to represent a pair of values, and it makes sense to group it withinMeetingScheduler
for better code organization, even though it doesn't depend on any instance-specific data fromMeetingScheduler
Usage Example:
public class OuterClass { public static class NestedClass { public void display() { System.out.println("Inside static nested class"); } } public static void main(String[] args) { OuterClass.NestedClass nestedObject = new OuterClass.NestedClass(); nestedObject.display(); // Output: Inside static nested class } }
Static Classes (Top-Level)
In Java, top-level classes cannot be static. The static
keyword can only be used with nested classes. However, understanding this context helps avoid confusion when reading or writing Java code.
Static Import
Static import allows members (fields and methods) defined in another class to be used in the current class without qualifying them with the class name.
Declaration:
import static java.lang.Math.*; public class StaticImportExample { public static void main(String[] args) { System.out.println(sqrt(16)); // Output: 4.0 System.out.println(PI); // Output: 3.141592653589793 } }
Summary
The static
keyword in Java serves multiple purposes:
Static Variables: Shared across all instances, accessed via the class name.
Static Methods: Belong to the class, cannot access instance variables, hidden not overridden.
Static Blocks: Used for static initialization, executed once when the class is loaded.
Static Nested Classes: Defined within another class, no reference to an instance of the enclosing class.
Static Import: Allows using static members of a class without qualifying them with the class name.
Interface in Java
used to provide 100% abstraction and loose coupling
interface keyword need not be accompanied by class keyword
you cannot instantiate object of an interface
unlike inheritance from parent class/abstract class where `extends` keyword is used here 'implements' keyword is used
All methods in an interface are by-default public and abstract
Since the type is abstract , you cannot give them any definition/body -IDEALLY. but in newer versions of java this is possible
Since you cannot reduce visibility and abstract default type is public, the overridden methods are also public and obviously same return type
Like earlier we are allowed to do upcasting of implementors to interface type - to achieve runtime polymorphism . using type interface we can access overridden methods but bit specialised methods in implementors(will have to do downcasting if that functionality is needed )
multiple inheritance is not allowed in java , but you can implement multiple interfaces
One interface in java can
extend
another interface , but one interface cannot 'implement' another interface`implements` means that we are promising implementation
We can have variables inside an interface , but all variables by default are marked as public static final , hence none of the classes can modify it
Reference to parent interface can only access methods override specific to it , While upcasting to a parent (interface) type , the parent ref pointing to child instance , it can only access methods provided by it and implemented by child
A class can extend and implement at the same time , but it should extend first then implement
An interface with no-body is called marker interface,developer need not implement this , example of this is serializable interface
Abstract classes vs Interface
Interfaces have 100% abstraction , whereas abstract classes can have partial
All methods in interface are by default are, public and abstract , and variables are public static final, no such implicit behaviour in abstract classes
only one abstract class can be extended at a time , but multiple interfaces can be implemented at once
New changes in interfaces
(JAVA 8) In an interface we can now have a method with body/implementation by declaring method as default. - Default methods in an interface. They get inherited in the implementor class
Its not compulsory for implementing class to override this default method - its not abstract. but obviously it can be overridden if required
(JAVA 8) An interface can have static methods also, they will NOT get inherited in implementing classes but can be invoked using interface type
(JAVA 9)Now Suppose some code is common across all methods of the interface , for this purpose private methods as also allowed in interfaces now
Lambda functions
There are three ways to implement an interface
using the `implements` keyword
Creating an anonymous inner class
Use Lambda functions , which are just syntactic sugar for anonymous inner classes
In java 8 a new operator is introduced , lambda operator/arrow operator
Lambda expression is like an anonymous method, an un-named overriding method, a method with no name,
There is left side and right side of the operator , left side consists of parameter list , right side is the body, return type is implicitly defined
Since there is no name , they cannot be called independently
You can write lambda only if there is functional interface. An interface with only single abstract method is called functional interface @FunctionalInterface. The annotation ensures no one can add another method in functional interface
When you don't want to re-use implementation
You cannot write lama independently. Can only be written if there is a functional interface
Can Lambda be written for abstract classes ? no
the syntax of lambda interface can be simplified much further
The Lambda kind of allowing us to instantiate an functional interface on the go .. , indirectly. Syntax rules same as arrow functions in javascript
Association / Composition (has-a)
In Java, composition/aggregation and inheritance are two different ways of achieving code reuse and structuring classes.
Composition/Aggregation:
Dependent object: This refers to an object that is part of another object's structure. For example, a
Car
class may have aEngine
object as a dependent object.Target object: This is the object that contains the dependent object. In the
Car
example, theCar
class is the target object.Dependency Injection: This is a technique where a dependent object is injected into the target object rather than being created within it. This can be done using setter methods (
setter injection
) or constructor arguments (constructor injection
).Advantages:
Flexibility: Allows for changing the dependent object at runtime without affecting the target object's behavior.
Modularity: Encourages loosely coupled classes, making the codebase easier to maintain and test.
Interface coupling: It's recommended to couple the target object to an interface rather than a concrete implementation. This promotes code flexibility and scalability.
Correct Way for Composition:
- To follow best practices, the target object should be coupled to an interface representing the dependent object's behavior rather than directly to a concrete class. This promotes flexibility and allows for easy switching of implementations.Below is an example of strategy design pattern demonstrating composition
Interface for Payment Strategy:
// Step 1: Define PaymentStrategy interface
public interface PaymentStrategy {
void pay(double amount);
}
Concrete Payment Strategy Implementations:
// Step 2: Implement Concrete Payment Strategies
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via Credit Card");
// Payment logic for Credit Card
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via PayPal");
// Payment logic for PayPal
}
}
Payment Context Coupled to Interface:
// Step 3: Use PaymentStrategy interface in PaymentContext
public class PaymentContext {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
Usage Example:
public class Main {
public static void main(String[] args) {
PaymentContext paymentContext = new PaymentContext();
// Using Composition and Dependency Injection (Strategy pattern)
PaymentStrategy creditCardPayment = new CreditCardPayment();
paymentContext.setPaymentStrategy(creditCardPayment);
paymentContext.processPayment(100.00);
PaymentStrategy payPalPayment = new PayPalPayment();
paymentContext.setPaymentStrategy(payPalPayment);
paymentContext.processPayment(50.00);
}
}
Explanation:
Interface for Payment Strategy:
- We define the
PaymentStrategy
interface, which represents the behavior of different payment methods.
- We define the
Concrete Payment Strategy Implementations:
- Concrete classes like
CreditCardPayment
andPayPalPayment
implement thePaymentStrategy
interface, providing specific payment logic.
- Concrete classes like
Payment Context Coupled to Interface:
- The
PaymentContext
class is coupled to thePaymentStrategy
interface rather than specific payment classes. This promotes flexibility and allows for easy switching of payment implementations.
- The
Usage Example:
- We create instances of concrete payment strategies (
CreditCardPayment
,PayPalPayment
) and inject them into thePaymentContext
using setter injection (setPaymentStrategy
). This follows best practices by coupling to an interface, not concrete classes.
- We create instances of concrete payment strategies (
Summary
Constructors don't participate in inheritance however, a subclass constructor implicitly calls its superclass constructor using super()
You cannot reduce the visibility, change parameter type or parameter list or return type of the methods you are overriding
Overloading - compile time VS Overriding - runtime
Method signatures only without body , such methods should be declared as abstract, and only made inside abstract class
final methods although participate in inheritance , cannot be overridden
static methods although participate in inheritance, cannot be overridden
All methods in an interface are by-default public and abstract, all variables by default are marked as public static final
(JAVA 8) An interface can have static methods also, they will NOT get inherited in implementing classes but can be invoked using interface type, interfaces can now also have default methods and private variables
Lambda returns us an instance of a functional interface (its actually via anonymous inner class)