Object Oriented Programming in JAVA

Photo by Shawn Rain on Unsplash

Object Oriented Programming in JAVA

Β·

10 min read

Inheritance

  1. Inheritance in Java mirrors how real-world objects relate. Think of a hierarchy where a base class (like Vehicle) shares common traits with subclasses (like Car and Motorcycle). Subclasses inherit features from the base class, such as methods like start() and stop() in our example.

  2. Understanding Method Variations

    • Overridden Methods: Subclasses can modify the behavior of inherited methods, like a Car defining how start() operates differently.

    • Specialized Methods: Subclasses can introduce new methods specific to their type, such as accelerate() in a Car.

    • Inherited Methods: Subclasses automatically inherit methods from their superclass, inheriting functionalities like stop() from Vehicle.

  3. Private Members and Their Scope

    Private members (variables and methods) belong exclusively to their class and aren't inherited by subclasses. This keeps sensitive data within its class, promoting data encapsulation.

  4. Java's Universal Base

    Every Java class implicitly extends the Object class. This universal inheritance ensures all objects have basic functionalities like toString() and equals().

  5. Upcasting:

    Upcasting allows treating a subclass object as its superclass type. While this offers flexibility in handling objects, it limits access to superclass-specific features until explicitly accessed through downcasting.

    There are specific rules regarding what a superclass type can and cannot access, especially in the context of overridden, specialized, and inherited members. Here's a breakdown:

    1. Inherited Variables:

      • Superclass reference can directly access its own variables.

      • Subclass inherits non-private variables from the superclass but cannot access private variables directly.

    2. 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)

    3. Specialized Methods:

      • Superclass 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.

  1. Constructor Dynamics

    Constructors don't participate in inheritance; however, a subclass constructor implicitly calls its superclass constructor using super(). If a subclass constructor calls this()instead, it overrides the implicit super() call, skipping the superclass's default constructor.- this is used to implement constructor chaining

  2. Utilizingthis()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.

  3. Access Boundaries: Super's Limitations

    The super keyword can't directly access variables or methods of a grandparent class (super.super.a is invalid). This emphasizes encapsulation and hierarchical structure, maintaining code integrity.

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

NOTE:

Overloading - compile time VS Overriding - runtime

Compile time polymorphism - method overloading and constructor overloading

Runtime polymorphism - method overriding and Polymorphic Method Invocation

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 overriden the base class speak implementation

Abstraction

  • 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 overriden 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 overriden

  • 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 keywords in java

  • static keywords are initialised once for the class and are not instance specific

  • static methods although participate in inheritance, cannot be overriden, 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

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 defination/body -IDEALLY. but in newer versions of java this is possible

  • Since you cannot reduce visibiliity 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 overriden 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 behavious in abstract classes

  • only one abstract classs 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

In Java, composition/aggregation and inheritance are two different ways of achieving code reuse and structuring classes.

  1. Composition/Aggregation:

    • Dependent object: This refers to an object that is part of another object's structure. For example, a Car class may have a Engine object as a dependent object.

    • Target object: This is the object that contains the dependent object. In the Car example, the Car 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.

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:

  1. Interface for Payment Strategy:

    • We define the PaymentStrategy interface, which represents the behavior of different payment methods.
  2. Concrete Payment Strategy Implementations:

    • Concrete classes like CreditCardPayment and PayPalPayment implement the PaymentStrategy interface, providing specific payment logic.
  3. Payment Context Coupled to Interface:

    • The PaymentContext class is coupled to the PaymentStrategy interface rather than specific payment classes. This promotes flexibility and allows for easy switching of payment implementations.
  4. Usage Example:

    • We create instances of concrete payment strategies (CreditCardPayment, PayPalPayment) and inject them into the PaymentContext using setter injection (setPaymentStrategy). This follows best practices by coupling to an interface, not concrete classes.
Β