Object Oriented Programming in JAVA

Photo by Shawn Rain on Unsplash

Object Oriented Programming in JAVA

Inheritance (`extends`/ is-a)

  1. 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

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

  3. Upcasting:

    Upcasting allows treating a subclass object as its superclass type. This offers flexibility in handling objects

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

  4. 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

  5. Utilizing 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.

  6. 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.

    1. Declaration:

       public class MyClass {
           public static int staticVariable = 0;
       }
      
    2. 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.

    3. 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.

  1. Declaration:

     public class MyClass {
         public static void staticMethod() {
             System.out.println("Static method called");
         }
     }
    
  2. 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.

  3. 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.

  1. Declaration:

     public class MyClass {
         static {
             // Initialization code
         }
     }
    
  2. Characteristics:

    • Executed Once: When the class is loaded.

    • Order of Execution: Multiple static blocks execute in the order they appear.

  3. 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.

  1. Declaration:

     public class OuterClass {
         public static class NestedClass {
             // Class code
         }
     }
    
  2. 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 within MeetingScheduler for better code organization, even though it doesn't depend on any instance-specific data from MeetingScheduler

  3. 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.

  1. 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:

  1. Static Variables: Shared across all instances, accessed via the class name.

  2. Static Methods: Belong to the class, cannot access instance variables, hidden not overridden.

  3. Static Blocks: Used for static initialization, executed once when the class is loaded.

  4. Static Nested Classes: Defined within another class, no reference to an instance of the enclosing class.

  5. 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.

  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.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:

  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.

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)