Java Tutorials Made Easy banner
 

Suppose you wanted to write a class that models a car. A car contains an engine that is described by its number of cylinders, its torque, and its horsepower. Therefore, you may try placing these attributes directly in the Car class as follows:

 

Listing 1 - Car.java (version 1 [incorrect])

public class Car
{
    private int numberOfCylinders;
    private double torque;
    private double horsepower;
}

This is a poor approach for several reasons. Firstly, the three fields model the state of one of the car’s components, its engine, rather than the state of the car itself. Secondly, as more components are added to the model, the Car class will have an unreasonable amount of fields.

Since the three fields, represent the state of an engine, they should be placed in a class named Engine as follows:

 

Listing 2 - Engine.java

public class Engine
{    
    private int numberOfCylinders;
    private double torque;
    private double horsepower;
    public Engine(int n, double t, double h)
    {
        numberOfCylinders = n;
        torque            = t;
        horsePower        = h;
    }
    public Engine(Engine e)
    {
        numberOfCylinders = e.numberOfCylinders;
        torque            = e.torque;
        horsePower        = e.horsePower;
    }
    @Override public String toString()
    {
        return "Engine: cylinders = " + numberOfCylinders
               + " torque = " + torque + " horsepower = "
               + horsepower;
    }
    public void setHorsepower(double h) { horsepower = h; }
}

Now that an engine’s attributes are encapsulated in a class, how do you add this to your car? You could try making the Car class a subclass of the Engine class as follows:

 

Listing 3 - Car.java (version 2 [incorrect])

public class Car extends Engine
{
    public Car(int n, double t, double h)
    {
        super(n, t, h);
    }
}

This is a poor approach since the purpose of inheritance is to make a specific version of a general concept. A car is not a specific kind of engine. Gasoline engines and diesel engines are specific kinds of engines. A gasoline engine not only has cylinders, torque, and horsepower, but also has spark plugs.  A diesel engine not only has cylinders, torque, and horsepower, but also has thermal efficiency.

 

Listing 4 - GasolineEngine.java

class GasolineEngine extends Engine
{
    private int numberOfSparkPlugs;
    public GasolineEngine(int s, int n, double t, double h)
    {
        super(n, t, h);
        numberOfSparkPlugs = s;
    }
    public GasolineEngine(GasolineEngine g)
    {
        super(g);
        numberOfSparkPlugs = g.numberOfSparkPlugs;
    }
    @Override public String toString()
    {
        return super.toString() + " numberOfSparkPlugs = "
             + numberOfSparkPlugs;
    }
}

 

Listing 5 - DieselEngine.java

class DieselEngine extends Engine
{
    private double thermalEfficiency;
    public DieselEngine(double e, int n, double t, double h)
    {
        super(n, t, h);
        thermalEfficiency = e;
    }
    public DieselEngine(DieselEngine d)
    {
        super(d);
        thermalEfficiency = d.thermalEfficiency;
    }
    @Override public String toString()
    {
        return super.toString() + " thermalEfficiency = "
             + thermalEfficiency;
    }
}

The engine should not be inherited by the car, it should rather be placed inside the car, making the Car class an aggregate. An aggregate class contains members that are independent objects. Each object comprises a component of the aggregate. An object component does not depend on its aggregate. The engine does not know that it is inside a car. It could be inside a boat or a locomotive instead.

 

Listing 6 - Car.java (version 3)

public class Car
{
    private Engine engine;
    public Car(int n, double t, double h)
    {
        engine = new Engine(n, t, h);
    }
}

A car also contains a transmission that is either automatic or manual and contains a certain number of gears. These two fields model the state of one of the car’s components, its transmission, rather than the state of the car itself. They describe an independent class named Transmission that can be placed in car, boat, or locomotive.

 

Listing 7 - Transmission.java

public class Transmission
{
    public enum TransmissionType { AUTOMATIC, MANUAL }
    private TransmissionType transmissionType;
    int numberOfGears;
    public Transmission(TransmissionType tt, int ng)
    {
        transmissionType = tt;
        numberOfGears    = ng;
    }
    public Transmission(Transmission t)
    {
        transmissionType = t.transmissionType;
        numberOfGears    = t.numberOfGears;
    }
    @Override public String toString()
    {
        return "Transmission: type = " + transmissionType
                + " numberOfGears = " + numberOfGears;
    }
    public void setNumberOfGears(int ng) { numberOfGears = ng; }

}

The transmission can then be added to the aggregate.

 

Listing 9 - Car.java (version 5)

public class Car
{
    private Engine engine;
    private Transmission transmission;
    public Car(int n, double t, double h,
               Transmission.TransmissionType tt, int ng)
    {
        engine = new Engine(n, t, h);
        transmission = new Transmission(tt, ng);
    }
    @Override public String toString()
    {
        return engine.toString() + " " + transmission.toString();
    }
}

A car also contains a chassis that is described by its length, width, distance of the front axle from the front of the frame, and distance of the rear axle from the back of the frame. A Chassis is an independent class that can be used in a car, boat, or locomotive.

 

Listing 10 - Chassis.java

public class Chassis
{
    double length;
    double width;
    double frontAxleDistance;
    double rearAxleDistance;
    public Chassis(double l, double w, double fad, double rad)
    {
        length            = l;
        width             = w;
        frontAxleDistance = fad;
        rearAxleDistance  = rad;
    }
    public Chassis(Chassis c)
    {
        length            = c.length;
        width             = c.width;
        frontAxleDistance = c.frontAxleDistance;
        rearAxleDistance  = c.rearAxleDistance;
    }
    @Override public String toString()
    {
        return "Chassis: length = " + length + " width = " + width
             + " front Axle distance = " + frontAxleDistance
             + " rear Axle distance = " + rearAxleDistance;
    }
    public void setRearAxleDistance(double rad)
    {
        rearAxleDistance = rad;
    }
}

 

Listing 11 - Car.java (version 6)

public class Car
{
    private Engine engine;
    private Transmission transmission;
    private Chassis chassis;
    public Car(int n, double t, double h,
               Transmission.TransmissionType tt, int ng,
               double l, double w, double fad, double rad)
    {
        engine       = new Engine(n, t, h);
        transmission = new Transmission(tt, ng);
        chassis      = new Chassis(l, w, fad, rad);
    }
    @Override public String toString()
    {
        return engine.toString() + " " + transmission.toString()
             + " " + chassis.toString();
    }
}

The Car constructor parameter list is getting very long and will increase in length as additional components are added. Instead of passing all the parameters needed to construct each object, a reference to an object can be passed instead. The version of the Car class in Listing 12 passes references to an Engine, a Transmission, and a Chassis to its constructor but simply assigns these references to their corresponding fields. These create reference copies where two references refer to the same object. When the original object is modified, the change is also reflected in the reference contained in the aggregate.

 

Listing 12 - Car.java (version 7 [incorrect])

public class Car
{
    private Engine engine;
    private Transmission transmission;
    private Chassis chassis;
    public Car(Engine e, Transmission t, Chassis c)
    {
        engine       = e; // incorrect!
        transmission = t; // incorrect!
        chassis      = c; // incorrect!
    }
    @Override public String toString()
    {
        return engine.toString() + "\n" + transmission.toString()
             + "\n" + chassis.toString();
    }
}

The main method in the following test program creates an Engine object, a Transmission object, and a Chassis object, and passes references to them to the Car constructor, which creates reference copies of these objects. The original objects are then modified using the setHorsePowersetNumberOfGears, and setRearAxleDistance mutators. However, since the aggregate contains reference copies of the original objects, the modified values of the engine horsepower, transmission number of gears, and chassis rear axle distance are displayed when the car object is printed.  

 

Listing 13 - CarTest.java

public class CarTest
{
    public static void main(String[] args)
    {
        Engine e = new Engine(6, 2000, 450);
        Transmission t = new Transmission(
                Transmission.TransmissionType.AUTOMATIC, 6);
        Chassis c = new Chassis(20, 10, 4, 5);
       
        Car car = new Car(e, t, c);
        System.out.println(car);

        e.setHorsePower(400);
        t.setNumberOfGears(5);
        c.setRearAxleDistance(4);

        System.out.println(car);
    }
}

 PROGRAM OUTPUT:

Engine: cylinders = 6 torque = 2000.0 horse power = 450.0
Transmission: type = AUTOMATIC numberOfGears = 6
Chassis: length = 20.0 width = 10.0 front Axle distance = 4.0 rear Axle distance = 5.0
Engine: cylinders = 6 torque = 2000.0 horse power = 400.0
Transmission: type = AUTOMATIC numberOfGears = 5
Chassis: length = 20.0 width = 10.0 front Axle distance = 4.0 rear Axle distance = 4.0

The constructor in the Car class in Listing 14 uses copy constructors to create separate objects for the enginetransmission, and chassis fields . Therefore, when the main method changes the original objects by calling  using the setHorsePowersetNumberOfGears, and setRearAxleDistance mutators, the objects in the aggregate are not affected. Note that the car’s engines horsepower is still 450, its transmission number of gears is still 6, and its chassis rear axle distance is still 5.

 

Listing 14 - Car.java (version 8)

public class Car
{
    private Engine engine;
    private Transmission transmission;
    private Chassis chassis;
    public Car(Engine e, Transmission t, Chassis c)
    {
        engine       = new Engine(e);
        transmission = new Transmission(t);
        chassis      = new Chassis(c);
    }
    @Override public String toString()
    {
        return engine.toString() + "\n" + transmission.toString()
             + "\n" + chassis.toString();
    }
}

PROGRAM OUTPUT:

Engine: cylinders = 6 torque = 2000.0 horse power = 450.0
Transmission: type = AUTOMATIC numberOfGears = 6
Chassis: length = 20.0 width = 10.0 front Axle distance = 4.0 rear Axle distance = 5.0
Engine: cylinders = 6 torque = 2000.0 horse power = 450.0
Transmission: type = AUTOMATIC numberOfGears = 6
Chassis: length = 20.0 width = 10.0 front Axle distance = 4.0 rear Axle distance = 5.0

The Is-A and Has-A Relationships

Inheritance implements the “Is-A” relationship while aggregation implements the “Has-A” relationship. An easy way to verify your design is to see if the following phrases make sense:

 

A car is-an engine

FALSE

A gasoline engine is-an engine

TRUE

A car has-an engine

TRUE

A car has-a transmission

TRUE

A car has-a chassis

TRUE

A mammal  has-an animal

FALSE

A mammal  is-an animal

TRUE

A gorilla is-a mammal

TRUE

A zebra is-a mammal

TRUE

A zoo is-a gorilla

FALSE

A zoo has-a gorilla

TRUE

A zoo has-a zebra

TRUE

 

Field types for Aggregate classes

In Java, an aggregate class has fields that are references to mutable objects. Primitive data types and references to immutable objects are not considered independent objects, so classes that contain them only are not treated as aggregates.

The following class is not an aggregate since it contains a double primitive field, a String field, and an Integer field. In Java, strings and wrapper classes are immutable.

In the NotAnAggregate constructor, the assignment of double parameter dbl to double field merely copies the value of dbl to d. The assignment of String reference str to String field creates a reference copy.  The assignment of Integer reference itg to Integer field also creates a reference copy. However, when the original String and Integer references  are modified in the main method, new objects are created since strings and wrapper classes are immutable, and the String and Integer fields inside the NotAnAggregate still contain “My String” and 4, respectively.

 

Listing 15 - NotAnAggregate.java

public class NotAnAggregate
{
    private double d;
    private String s;
    private Integer i;
    public NotAnAggregate(double dbl, String str, Integer itg)
    {
        d = dbl;
        s = str;
        i = itg;
    }
    @Override public String toString()
    {
        return d + " " + s + " " + i;
    }
    public static void main(String[] args)
    {
        double  d = 5.1;
        String  s = "My String";
        Integer i = 4;
       
        NotAnAggregate naa = new NotAnAggregate(d, s, i);

        d = 5.2;
        s = "Your String";
        i = 3;      

        System.out.println(naa);
    }
}

PROGRAM OUTPUT:

5.1 My String 4