Chapter[16]: Passing Class Objects as Parameters in Java: Payment System Example

Automate This. By Mrigank Saxena
5 min readJan 27, 2025

--

Image by freepik

In this article, we’ll explore how to pass objects as parameters in Java using a payment system example. By the end, you’ll understand how parent class references work, how child class objects are passed, and how dynamic method dispatch comes into play.

Building a Payment System: The Basics

Let’s say you’re creating an online payment system that supports multiple payment methods like Credit Cards and PayPal. Each payment method is represented as a class that extends a common Payment parent class. Here’s an example:

abstract class Payment {
abstract void processPayment();
}

class CreditCardPayment extends Payment {
@Override
void processPayment() {
System.out.println("Processing credit card payment...");
}
}
class PayPalPayment extends Payment {
@Override
void processPayment() {
System.out.println("Processing PayPal payment...");
}
}

Here’s what’s happening:

  1. Parent Class (Payment): Defines the structure (the processPayment() abstract method) that all child classes must implement.
  2. Child Classes (CreditCardPayment and PayPalPayment): Provide specific implementations for the processPayment() method.

Breaking Down the Concept

1: What is a Subtype?

A subtype is a class that inherits from or implements another class or interface. For example:

abstract class Payment { ... }  // Parent class
class CreditCardPayment extends Payment { ... } // Subtype
class PayPalPayment extends Payment { ... } // Subtype

Here, CreditCardPayment and PayPalPayment are subtypes of the Payment class because they inherit or extend from it.

2: What Does Payment payment1 Mean in the below Example?

Payment payment1 declares a variable of type Payment. Since Payment is the parent class, this variable can reference objects of the Payment type or any of its subtypes.

3: Holding Objects of Subtypes

Even though payment1 is declared as Payment, it can hold instances of any class that is a subtype of Payment. For example:

Payment payment1 = new CreditCardPayment(); // `payment1` holds a `CreditCardPayment` object
Payment payment2 = new PayPalPayment(); // `payment2` holds a `PayPalPayment` object

4: Child is-a Parent: In Oops we refer the relation between child and parent as Child is-a Parent, and the above objet creation is allowed because a CreditCardPayment is-a Payment (via inheritance), and a PayPalPayment is-a Payment.

Why Is This Useful?

Using parent references allows you to write flexible code. For instance, you can write methods that operate on Payment objects without needing to know the exact subtype. Once a Payment reference is created, you can use it to pass any number of child objects.

Here’s an example:

void processTransaction(Payment payment) {
payment.processPayment();
}

At runtime, the method called on payment will depend on the actual object (subtype) it holds parent1, or parent2. This is called dynamic method dispatch. For example:

Payment payment1 = new CreditCardPayment();
processTransaction(payment1); // Calls `processPayment()` in `CreditCardPayment`

How Parent Class References Work

When you write this code:

Payment payment1 = new CreditCardPayment();
Payment payment2 = new PayPalPayment();

Here’s what’s happening:

  1. Declaring a Parent Class Reference: Payment payment1 creates a variable of type Payment. This variable can hold any object that is a subtype of Payment.
  2. Creating a Child Class Object: new CreditCardPayment() creates an instance of the CreditCardPayment class.
  3. Assigning the Child Object to the Parent Reference: Java allows this because of inheritance. A CreditCardPayment is-a Payment, so it can be stored in a Payment variable.

This concept is a cornerstone of polymorphism in Java, where parent class references can refer to child class objects.

Passing Objects as Parameters

Let’s pass these objects to a method:

public class Main {
public static void main(String[] args) {
Payment payment1 = new CreditCardPayment();
Payment payment2 = new PayPalPayment();

processTransaction(payment1); // Pass `CreditCardPayment` object
processTransaction(payment2); // Pass `PayPalPayment` object
}
static void processTransaction(Payment payment) {
payment.processPayment(); // Dynamic method dispatch
}
}

What Happens Here?

1: Creating Objects:

  • payment1 is a CreditCardPayment object stored in a Payment reference.
  • payment2 is a PayPalPayment object stored in a Payment reference.

2: Passing Objects to processTransaction:

  • When processTransaction(payment1) is called, the CreditCardPayment object is passed as the argument.
  • When processTransaction(payment2) is called, the PayPalPayment object is passed as the argument.

3: Dynamic Method Dispatch: Inside processTransaction, the processPayment() method is called on the Payment object. At runtime, Java determines which implementation to execute based on the actual object type.

Processing credit card payment...
Processing PayPal payment...

Skipping the Variable: Passing Objects Directly

Instead of storing the object in a variable, you can directly pass it to the method:

processTransaction(new CreditCardPayment());
processTransaction(new PayPalPayment());

How This Works:

  1. Object Creation: new CreditCardPayment() creates a new instance of the CreditCardPayment class.
  2. Direct Passing: This object is directly passed to the processTransaction method, skipping the intermediate variable declaration.
// With Variable
Payment payment1 = new CreditCardPayment();
validatePayment(payment1);
processTransaction(payment1);
// Passing Directly
processTransaction(new CreditCardPayment());

Key Takeaways

  1. Parent References Hold Child Objects: A Payment variable can store a CreditCardPayment or PayPalPayment object because of inheritance.
  2. Dynamic Method Dispatch: Java determines at runtime which processPayment implementation to call based on the actual object type.
  3. Flexibility: Using parent references makes your code more flexible and scalable. You can add new payment types (e.g., UPIPayment) without modifying existing methods.
  4. Direct Passing: Passing objects directly is concise for one-time use cases but might reduce readability if overused.

By mastering these concepts, you can build scalable, reusable, and flexible Java applications — like a robust payment system! 🚀

Mastering The Composition

How to effectively use IS-A (inheritance) and HAS-A (composition) relationships in Java, along with abstraction. By the end, you’ll gain clarity through practical examples, progressing from the basics to more advanced implementations(in the next chapter).

Basics of IS-A and HAS-A Relationships

Before diving into abstraction, let’s clarify the two foundational concepts:

1: IS-A Relationship (Inheritance):

  • Depicts that one class is a type of another class.
  • Achieved through extends in Java.

2: HAS-A Relationship (Composition):

  • Shows that one class contains another class as part of its structure.
  • Achieved by including a class as a member variable.

Here’s a simple example with Car and Engine:

==============================================================================
// IS-A Relationship: Car is a type of Vehicle
class Vehicle {
void start() {
System.out.println("Vehicle is starting...");
}
}
class Car extends Vehicle {
void drive() {
System.out.println("Car is driving...");
}
}

==============================================================================
// HAS-A Relationship: Car has an Engine
class Engine {
void run() {
System.out.println("Engine is running...");
}
}
class CarWithEngine {
private Engine engine; // Car HAS-A Engine
public CarWithEngine(Engine engine) {
this.engine = engine;
}
void startCar() {
System.out.println("Car is starting...");
engine.run(); // Delegating the task to the Engine
}
}

==============================================================================
// Main Class to Test Relationships
public class CarEngineExample {
public static void main(String[] args) {

// Demonstrating IS-A Relationship
Car myCar = new Car();
myCar.start(); // Inherited from Vehicle
myCar.drive(); // Specific to Car

System.out.println(); // Separator

// Demonstrating HAS-A Relationship
Engine carEngine = new Engine();
CarWithEngine carWithEngine = new CarWithEngine(carEngine);
carWithEngine.startCar(); // Using the Engine's functionality

}
}
==============================================================================

Output:

Vehicle is starting...
Car is driving...

Car is starting...
Engine is running...

By combining these relationships with abstraction, you can build flexible, scalable, and maintainable Java applications. Let me know if you’d like to explore further!

You can find and download the codes from here

.

.

.

Happy Coding!

--

--

Automate This. By Mrigank Saxena
Automate This. By Mrigank Saxena

Written by Automate This. By Mrigank Saxena

Join me as I share insights, tips, and experiences from my journey in quality assurance, automation, and coding! https://www.linkedin.com/in/iammriganksaxena/

No responses yet