✈️ Comparison of Object-Oriented Programming in Java, C++, and Ada

Created by: Andrew J. Pounds, Ph.D.

Welcome to a comprehensive comparison of Object-Oriented Programming (OOP) across three distinct programming languages: Java, C++, and Ada. This document explores how each language approaches the fundamental concepts of OOP and provides practical examples using the GNAT (GNU Ada) Compiler. By understanding these comparisons, you'll develop a deeper appreciation for language design decisions and be better equipped to choose the right tool for your projects.

❓ What is Object-Oriented Programming?

Object-Oriented Programming is a paradigm that organizes code around "objects" that contain both data (variables) and functions (methods). Think of an object like a blueprint: a Drink class defines what properties all drinks have and what operations can be performed on them.

The core principles of OOP are:

📊 Language Feature Comparison Table

Feature Java C++ Ada
Class Definition class ClassName class ClassName type T is tagged record
Constructor ClassName() ClassName() function Create return T
Destructor Automatic (Garbage Collected) ~ClassName() procedure Finalize (Controlled types)
Inheritance extends : type T is new Parent with record
Access Modifiers public, private, protected public, private, protected private section in package spec
Method Override @Override annotation virtual keyword overriding keyword
Polymorphism Virtual by default Must use virtual Dispatching on tagged types
Memory Management Automatic (Garbage Collection) Manual (new/delete) Manual or Controlled types
Interfaces interface keyword Pure virtual classes interface keyword (Ada 2005+)
Constants final keyword const keyword constant keyword
Compilation javac g++ gnatmake

✏️ Example 1: Basic Class Definition

Let's compare how to define a basic class in all three languages:

☕ Java
public class Drink {
    final int MAXIMUM = 10;
    private String name;
    
    public Drink(String brandName) {
        name = brandName;
    }
    
    public String getName() {
        return name;
    }
}
⚙️ C++
class Drink {
private:
    static const int MAXIMUM = 10;
    string name;

public:
    Drink(string brandName) {
        name = brandName;
    }
    
    string getName() {
        return name;
    }
};
✈️ Ada
-- Package Specification (drink_package.ads)
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Drink_Package is
    type Drink is tagged private;
    
    function Create(Brand_Name : String) return Drink;
    function Get_Name(D : Drink) return String;

private
    Maximum : constant Integer := 10;
    
    type Drink is tagged record
        Name : Unbounded_String := Null_Unbounded_String;
    end record;
end Drink_Package;

🔍 Key Observations:

  • Java: Requires access modifiers on each method; uses final for constants
  • C++: Uses separate private: and public: sections; uses static const for class constants
  • Ada: Uses tagged record for OOP types; separates specification (.ads) from body (.adb); uses private section for encapsulation; constants declared with constant keyword

✏️ Example 2: Constructor Overloading

All three languages support multiple constructors with different parameter lists:

☕ Java
public Drink(String name) {
    this.name = name;
    this.cost = 1.75;
}

public Drink(String name, double cost) {
    this.name = name;
    this.cost = cost;
}
⚙️ C++
Drink(string name) {
    this->name = name;
    this->cost = 1.75;
}

Drink(string name, double cost) {
    this->name = name;
    this->cost = cost;
}
✈️ Ada
function Create(Brand_Name : String) return Drink is
    D : Drink;
begin
    D.Name := To_Unbounded_String(Brand_Name);
    D.Cost := 1.75;
    return D;
end Create;

function Create(Brand_Name : String; Cost : Float) return Drink is
    D : Drink;
begin
    D.Name := To_Unbounded_String(Brand_Name);
    D.Cost := Cost;
    return D;
end Create;

🔍 Key Observations:

  • Java: Uses this.variable to reference instance variables
  • C++: Uses this->variable pointer notation
  • Ada: Uses functions that return the type; explicit record field assignment; uses := for assignment; overloading based on parameter signatures

✏️ Example 3: Methods and Encapsulation

Here's how methods work in each language, using the vending machine example:

☕ Java
public void vend() {
    if (available > 0) {
        available -= 1;
        consumed += 1;
        moneyMade += cost;
        System.out.println(name + " Purchased.");
    }
}

public double profit() {
    return moneyMade;
}
⚙️ C++
void vend() {
    if (available > 0) {
        available -= 1;
        consumed += 1;
        moneyMade += cost;
        cout << name << " Purchased." << endl;
    }
}

double profit() {
    return moneyMade;
}
✈️ Ada
procedure Vend(D : in out Drink) is
begin
    if D.Available > 0 then
        D.Available := D.Available - 1;
        D.Consumed := D.Consumed + 1;
        D.Money_Made := D.Money_Made + D.Drink_Cost;
        Put_Line(To_String(D.Name) & " Purchased.");
    end if;
end Vend;

function Profit(D : Drink) return Float is
begin
    return D.Money_Made;
end Profit;

🔍 Key Observations:

  • Java: Uses System.out.println() for output
  • C++: Uses cout and endl from iostream
  • Ada: Uses Put_Line() for output (from Ada.Text_IO); uses & for string concatenation; uses procedure for methods that modify state (in out parameter) and function for methods that return values; explicit parameter passing mode

🔧 The Complete Vending Machine Example

Now let's look at a complete, working example using the Ada language and the GNAT (GNU Ada) Compiler. This program implements a simple vending machine that tracks drink sales and profits using separate package specification and body files.

⚠️ Important: Packages and the with Clause in Ada

When using Ada with separate files, you organize code into packages. Each package has a specification (.ads) containing public declarations and a body (.adb) containing the implementation. Other files include packages with the with clause.

✅ The Correct Approach

Create a package specification (drink_package.ads) with type and subprogram declarations, a package body (drink_package.adb) with implementations, and a main procedure file (machine.adb) that uses that package.

Why Packages Are Essential for Ada

The package system in Ada is crucial because:

💡 How It Works

When you write with Drink_Package;, the compiler looks for drink_package.ads, reads its specification, and makes those declarations available to your program. The use Drink_Package; clause makes the names directly visible without qualification.

drink_package.ads - The Package Specification

✈️ Ada - Package Specification
-- drink_package.ads
-- Package Specification for the Drink class

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package Drink_Package is

    type Drink is tagged private;

    -- Constructors
    function Create(Brand_Name : String) return Drink;
    function Create(Brand_Name : String; Cost : Float) return Drink;

    -- Methods
    procedure Vend(D : in out Drink);
    procedure Refill(D : in out Drink);
    function Profit(D : Drink) return Float;
    function Drinks_Sold(D : Drink) return Integer;
    function Get_Drink_Name(D : Drink) return String;
    function Restock_Amount(D : Drink) return Integer;

private
    -- Constants
    Maximum_Capacity : constant Integer := 10;
    Default_Drink_Cost : constant Float := 1.75;

    -- Private tagged record definition
    type Drink is tagged record
        Available : Integer := 0;
        Consumed : Integer := 0;
        Use_Default_Cost : Integer := 1;
        Use_Supplied_Cost : Integer := 0;
        Drink_Cost : Float := Default_Drink_Cost;
        Money_Made : Float := 0.0;
        Name : Unbounded_String := Null_Unbounded_String;
    end record;

end Drink_Package;

drink_package.adb - The Package Body

✈️ Ada - Package Body
-- drink_package.adb
-- Package Body for the Drink class

with Ada.Text_IO; use Ada.Text_IO;

package body Drink_Package is

    -- Create (1) --
    function Create(Brand_Name : String) return Drink is
        D : Drink;
    begin
        D.Name := To_Unbounded_String(Brand_Name);
        D.Consumed := 0;
        D.Available := 0;
        D.Money_Made := 0.0;
        D.Drink_Cost := Default_Drink_Cost;
        D.Use_Default_Cost := 1;
        D.Use_Supplied_Cost := 0;
        return D;
    end Create;

    -- Create (2) --
    function Create(Brand_Name : String; Cost : Float) return Drink is
        D : Drink;
    begin
        D.Name := To_Unbounded_String(Brand_Name);
        D.Consumed := 0;
        D.Available := 0;
        D.Money_Made := 0.0;
        D.Drink_Cost := Cost;
        D.Use_Default_Cost := 0;
        D.Use_Supplied_Cost := 1;
        return D;
    end Create;

    -- Vend --
    procedure Vend(D : in out Drink) is
    begin
        if D.Available > 0 then
            D.Available := D.Available - 1;
            D.Consumed := D.Consumed + 1;
            D.Money_Made := D.Money_Made +
                (Float(D.Use_Default_Cost) * Default_Drink_Cost +
                 Float(D.Use_Supplied_Cost) * D.Drink_Cost);
            Put_Line(To_String(D.Name) & " Purchased.");
        else
            Put_Line("");
            Put_Line("        SOLD OUT        ");
            Put_Line("");
        end if;
    end Vend;

    -- Refill --
    procedure Refill(D : in out Drink) is
    begin
        D.Available := Maximum_Capacity;
    end Refill;

    -- Profit --
    function Profit(D : Drink) return Float is
    begin
        return Float(Integer(D.Money_Made * 100.0)) / 100.0;
    end Profit;

    -- Drinks_Sold --
    function Drinks_Sold(D : Drink) return Integer is
    begin
        return D.Consumed;
    end Drinks_Sold;

    -- Get_Drink_Name --
    function Get_Drink_Name(D : Drink) return String is
    begin
        return To_String(D.Name);
    end Get_Drink_Name;

    -- Restock_Amount --
    function Restock_Amount(D : Drink) return Integer is
    begin
        return Maximum_Capacity - D.Available;
    end Restock_Amount;

end Drink_Package;

machine.adb - The Main Program

✈️ Ada - Main Program
-- machine.adb
-- Main driver program for the Vending Machine

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Drink_Package; use Drink_Package;

procedure Machine is
    Drink1, Drink2, Drink3, Drink4 : Drink;
    Total_Sold : Integer;
    Total_Profit : Float;
begin
    -- Create drinks using different constructors
    Drink1 := Create("Coke");
    Drink2 := Create("Sprite");
    Drink3 := Create("Doctor Pepper");
    Drink4 := Create("V8 Splash", 4.25);

    -- Refill all drinks
    Refill(Drink1);
    Refill(Drink2);
    Refill(Drink3);
    Refill(Drink4);

    -- Purchase 8 Cokes
    for I in 1 .. 8 loop
        Vend(Drink1);
    end loop;

    -- Purchase 6 Sprites
    for I in 1 .. 6 loop
        Vend(Drink2);
    end loop;

    -- Purchase 2 Doctor Peppers
    Vend(Drink3);
    Vend(Drink3);

    -- Purchase 11 V8 Splash (will exceed capacity)
    for I in 1 .. 11 loop
        Vend(Drink4);
    end loop;

    -- Calculate totals
    Total_Sold := Drinks_Sold(Drink1) + Drinks_Sold(Drink2) +
                  Drinks_Sold(Drink3) + Drinks_Sold(Drink4);
    Total_Profit := Profit(Drink1) + Profit(Drink2) +
                    Profit(Drink3) + Profit(Drink4);

    -- Display results
    Put("A Total of ");
    Put(Total_Sold, Width => 0);
    Put(" drinks were sold for a profit of $");
    Put(Total_Profit, Fore => 1, Aft => 2, Exp => 0);
    New_Line;
    New_Line;

    Put(Restock_Amount(Drink1), Width => 0);
    Put_Line(" " & Get_Drink_Name(Drink1) & " needed to restock machine");

    Put(Restock_Amount(Drink2), Width => 0);
    Put_Line(" " & Get_Drink_Name(Drink2) & " needed to restock machine");

    Put(Restock_Amount(Drink3), Width => 0);
    Put_Line(" " & Get_Drink_Name(Drink3) & " needed to restock machine");

    Put(Restock_Amount(Drink4), Width => 0);
    Put_Line(" " & Get_Drink_Name(Drink4) & " needed to restock machine");

end Machine;

⚡ Compilation and Execution Instructions

Step-by-Step: Compilation with GNAT (Correct Method)

  1. Create drink_package.ads with the package specification (shown above)
  2. Create drink_package.adb with the package body (shown above)
  3. Create machine.adb with the main procedure (shown above)
  4. Save all three files in the same directory
  5. Open a terminal and navigate to that directory
  6. Compile the program using gnatmake:
    gnatmake machine.adb
  7. Run the program:
    ./machine

✅ The Key to Success

Use with Drink_Package; in machine.adb. The GNAT compiler will automatically locate and compile drink_package.ads and drink_package.adb when it processes the with clause.

The Difference Between File Types in Ada

File Type Extension Purpose Contains
Package Specification .ads Public interface declaration Type declarations, subprogram specs, constants
Package Body .adb Implementation of the specification Subprogram bodies, private code
Main Procedure .adb Executable entry point procedure Name is ... begin ... end Name;

Sample Output:

Coke Purchased. Coke Purchased. Coke Purchased. Coke Purchased. Coke Purchased. Coke Purchased. Coke Purchased. Coke Purchased. Sprite Purchased. Sprite Purchased. Sprite Purchased. Sprite Purchased. Sprite Purchased. Sprite Purchased. Doctor Pepper Purchased. Doctor Pepper Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. V8 Splash Purchased. SOLD OUT A Total of 26 drinks were sold for a profit of $70.50 8 Coke needed to restock machine 6 Sprite needed to restock machine 2 Doctor Pepper needed to restock machine 10 V8 Splash needed to restock machine

🎯 Understanding the Code

Package Structure and the with/use Clauses

In Ada, when you split code into multiple files, you use packages with specifications and bodies:

File Section Purpose
drink_package.ads package Drink_Package is Declares the public interface
drink_package.ads private Hidden implementation details
drink_package.adb package body Drink_Package is Implements all subprograms
machine.adb with Drink_Package; Makes package visible
machine.adb use Drink_Package; Allows direct name access (no prefix needed)

Why Separate Specification and Body?

The separation is essential because:

Tagged Types and Object-Oriented Programming

Aspect Ada Approach Description
Class Definition type Drink is tagged record Tagged types enable OOP features
Encapsulation tagged private Full record hidden in private section
Methods Subprograms with type as parameter procedure Vend(D : in out Drink)
Inheritance type Child is new Parent with record Extends parent with new fields

Parameter Modes in Ada

Ada uses explicit parameter modes to indicate how parameters are used:

Mode Meaning Example
in Read-only (default for functions) function Profit(D : Drink)
out Write-only, value assigned inside procedure Get_Value(Result : out Integer)
in out Read and write (like pass by reference) procedure Vend(D : in out Drink)

Encapsulation in Action

Notice that in the Drink_Package specification, the type Drink is declared as tagged private. This means the actual record structure cannot be accessed directly from outside the package. Instead, users must use the public subprograms:

✅ This is encapsulation

The internal details (how money is tracked, how inventory works) are hidden in the private section, and users interact through well-defined subprograms.

❌ Common Mistakes and Solutions

Mistake 1: Forgetting the with Clause

❌ Wrong (machine.adb):
-- Missing: with Drink_Package;
procedure Machine is
    D : Drink;  -- ERROR: Drink is not visible
begin
    D := Create("Coke");
end Machine;
✅ Correct (machine.adb):
with Drink_Package; use Drink_Package;
procedure Machine is
    D : Drink;  -- Now Drink is visible
begin
    D := Create("Coke");
end Machine;

Mistake 2: Using Wrong Assignment Operator

❌ Wrong:
D.Name = To_Unbounded_String(Brand_Name);  -- ERROR: = is comparison
✅ Correct:
D.Name := To_Unbounded_String(Brand_Name);  -- Ada uses := for assignment

Mistake 3: Forgetting in Out for Modifying Parameters

❌ Wrong:
procedure Vend(D : Drink) is  -- ERROR: D is read-only by default
begin
    D.Available := D.Available - 1;  -- Cannot modify D!
end Vend;
✅ Correct:
procedure Vend(D : in Out Drink) is  -- D can be read and modified
begin
    D.Available := D.Available - 1;  -- Now this works
end Vend;

Mistake 4: Using + for String Concatenation

❌ Wrong:
Put_Line(To_String(D.Name) + " Purchased.");  -- ERROR: + doesn't concatenate strings
✅ Correct:
Put_Line(To_String(D.Name) & " Purchased.");  -- Ada uses & for concatenation

Mistake 5: Package Name Not Matching File Name

❌ Wrong (file: drink_package.ads):
package Beverage is  -- ERROR! Package name should match filename
    ...
end Beverage;
✅ Correct (file: drink_package.ads):
package Drink_Package is  -- Package name matches filename
    ...
end Drink_Package;

Mistake 6: Forgetting end Statements

❌ Wrong:
if D.Available > 0 then
    D.Available := D.Available - 1;
-- ERROR: Missing "end if;"
✅ Correct:
if D.Available > 0 then
    D.Available := D.Available - 1;
end if;  -- Ada requires explicit end statements

Mistake 7: Using return in a Procedure

❌ Wrong:
procedure Vend(D : in out Drink) is
begin
    if D.Available = 0 then
        return D;  -- ERROR: Procedures don't return values
    end if;
end Vend;
✅ Correct:
procedure Vend(D : in out Drink) is
begin
    if D.Available = 0 then
        return;  -- Procedures use bare "return" to exit early
    end if;
end Vend;

Mistake 8: Forgetting to Import Text_IO Packages

❌ Wrong:
procedure Machine is
begin
    Put_Line("Hello");  -- ERROR: Put_Line is not visible
end Machine;
✅ Correct:
with Ada.Text_IO; use Ada.Text_IO;
procedure Machine is
begin
    Put_Line("Hello");  -- Now Put_Line is visible
end Machine;

✏️ Your Programming Exercise

Objective:

Modify the vending machine program to replace the V8 Splash drink with a new beverage of your choice.

Assignment:

The current program includes V8 Splash at $4.25 as the fourth drink. Your task is to:

  1. Replace V8 Splash: Change the drink name to something different (e.g., "Orange Juice", "Apple Cider", "Iced Tea", "Lemonade", etc.) and choose a new price (different from $4.25)
  2. Modify the vending operations: Change how many times each drink is purchased by modifying the loop counts for each drink's Vend calls
  3. Update the output display: Ensure all the drink names and restock amounts are printed correctly
  4. Test your program: Compile with gnatmake and run it to ensure it works properly

Specific Changes to Make:

  • In machine.adb, change the line:
    Drink4 := Create("V8 Splash", 4.25);
    to your new drink with a new price, for example:
    Drink4 := Create("Orange Juice", 3.50);
  • Keep all four drinks in the program
  • Keep all the Vend calls (you can change the loop ranges)
  • Keep the final output section that displays totals and restocking information

Testing Your Solution:

  1. Make your changes to machine.adb
  2. Save the file
  3. Compile:
    gnatmake machine.adb
  4. Run:
    ./machine
  5. Verify the output shows your new drink name and the correct profit calculations

🎯 Challenge:

Before running your program, calculate what the total profit should be. Does your output match your prediction?

⭐ Key Takeaways About Object-Oriented Programming

  • Classes bundle data (variables) and behavior (methods) together
  • Constructors initialize objects when they are created
  • Access modifiers (private sections) control what can be accessed
  • Encapsulation hides internal details and prevents misuse
  • Methods provide the interface for interacting with objects
  • Ada uses tagged record types for OOP instead of classes
  • Ada uses function Create return T pattern for constructors
  • Use with Package_Name; to import packages
  • Use use Package_Name; for direct visibility of names
  • Ada requires explicit parameter modes: in, out, in out
  • Package names should match file names (drink_package.ads contains package Drink_Package)
  • String concatenation in Ada uses the & operator
  • Ada uses Put_Line() for output (from Ada.Text_IO)
  • Use gnatmake to compile Ada programs
  • All compound statements require explicit end (e.g., end if;, end loop;)

📚 Language Strengths Summary

Language Strengths Best For
Java Write once, run anywhere; Strong ecosystem; Great for large projects Enterprise applications, web backends, Android apps
C++ High performance; Maximum control; Close to hardware System software, games, performance-critical apps
Ada Strong typing; Safety-critical features; Excellent readability; Built-in concurrency Aerospace, defense, safety-critical systems, embedded systems