slanted W3C logo

Day 22 — Aggregation

In today's lecture we look at aggregation and how it affects the use and copying of objects.

This material is in Chapter 8 of the textbook. You should read Chapter 7 on your own.

Aggregation

aggregation
a group composed of many distinct parts

In Java, a class defines an aggregation if one if its attributes is an object reference. For example, the Investment class (in type.lib) encapsulates an investment in a Stock:

      Stock stock = new Stock("HR.A");
      
      // purchase 100 shares of stock at $1.10 per share
      Investment inv = new Investment(stock, 100, 1.10);

Every Investment object has-a reference to a Stock object. The relationship has-a indicates an aggregation.

Another way to think about aggregation is that it is a whole-part relationship. Every Investment object (the whole) is made up of a Stock object (the part).

A stronger form of aggregation (called composition) exists; see Section 8.1.1 in the textbook.

Aggregation

Examples of aggregation:

Examples that are not aggregation:

UML Diagrams

A unified modeling language (UML) diagram illustrates aggregation relationships between classes:


Constructing an Aggregate

In the Investment class, the client is responsible for supplying a Stock object that is aggregated by the Investment object:

      Investment inv = new Investment(new Stock("HR.A"),
                                      100, 1.10);

In the CreditCard class, the client is not always responsible for supplying the various Date objects that are aggregated by the CreditCard object. Instead, the CreditCard constructor creates the Date objects internally:

      CreditCard visa = new CreditCard(123456, "John Doe");

Why Should the Client Care?

The client is not supposed to care how the implementer has programmed the class; why should the client care about aggregation and composition? Because the implementer's choice affects the client!

      Stock s = new Stock("HR.A");
      
      // purchase 10 shares at $2 per share
      Investment inv = new Investment(s, 10, 2.00);
      
      // mutate stock using s
      s.setSymbol("HR.Z");
      
      // what does inv return?
      Stock sinv = inv.getStock();
      output.println(sinv == s);

The above code fragment prints true indicating that sinv is a reference to a stock with symbol HR.Z.

import java.io.PrintStream;
import type.lib.Investment;
import type.lib.Stock;

public class Aggregation1
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Stock s = new Stock("HR.A");
      
      // purchase 10 shares at $2 per share
      Investment inv = new Investment(s, 10, 2.00);
      
      // mutate stock using s
      s.setSymbol("HR.Z");
      
      // what does inv return?
      Stock sinv = inv.getStock();
      output.println(sinv == s);
   }
}

Aliasing

In the previous example, the client was able to change the stock in the investment object without using a method in the Investment class. In fact, the reference s and the stock reference held by the investment both refer to the same object.

  main
 
s ⇒ 300
inv ⇒ 400
  |
300 Stock object
 
  |
400 Investment object
stock ⇒   300

When two different references refer to the same object (in this case the references are s and stock) we say that the references are aliases.

Accessors

The Investment class defines an accessor method that returns a reference to its Stock attribute. From the previous discussion, we already know that the returned reference points to the aggregated stock object; however, the implementer could have chosen to return a reference to a copy of the aggregated stock object.

The difference between the two approaches is important. If a reference to the aggregated object is returned then the client can modify the aggregated object using the reference:

      String origSymbol = "HR.A";
      Investment inv = new Investment(new Stock(origSymbol),
                                      10, 2.00);
      
      Stock sinv = inv.getStock();      
      String newSymbol = "HR.M";
      sinv.setSymbol(newSymbol);
      
      Stock sinv2 = inv.getStock();
      
      output.println(newSymbol.equals(sinv2.getSymbol()));

The above code fragment prints true indicating that getStock returns a reference to the aggregated stock object.

import java.io.PrintStream;
import type.lib.Investment;
import type.lib.Stock;

public class Aggregation2
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      String origSymbol = "HR.A";
      Investment inv = new Investment(new Stock(origSymbol),
                                      10, 2.00);
      
      Stock sinv = inv.getStock();      
      String newSymbol = "HR.M";
      sinv.setSymbol(newSymbol);
      
      Stock sinv2 = inv.getStock();
      
      output.println(newSymbol.equals(sinv2.getSymbol()));
   }
}

Accessors

If an accessor returns a reference to a copy of an aggregated object, then the client will not be able to modify the aggregated object using the reference:

      CreditCard card = new CreditCard(123456, "John Doe");
      
      Date issueDate = card.getIssueDate();
      issueDate.setTime(0);    // Jan 1, 1970 GMT
      
      output.println(card.getIssueDate());

The above code fragment prints today's date. This means that the accessor returned a reference to a copy of the aggregated date.

import java.io.PrintStream;
import java.util.Date;
import type.lib.CreditCard;

public class Aggregation3
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      CreditCard card = new CreditCard(123456, "John Doe");
      
      Date issueDate = card.getIssueDate();
      issueDate.setTime(0);    // Jan 1, 1970 GMT
      
      output.println(card.getIssueDate());
   }
}

Copying Objects: Aliasing

Sometimes a client will want to create a copy of an object. The cleanest way to copy an object is to use what is called a copy constructor, but many existing classes do not provide them. In these cases, the client has to do some extra work.

The simplest form of copying an object is to create an alias:

      Investment inv = new Investment(new Stock("HR.A"),
                                      10, 2.00);
      Investment alias = inv;
 
inv ⇒ 400
alias ⇒ 400
 
  |
400 Investment object
stock ⇒   500
  |
500 Stock object
 

Copying Objects: Shallow Copy

A shallow copy of an object creates a new object with attributes that are identical to the original object. If the original object is an aggregation, then the copied object aggregates the same objects as the original.

      Investment inv = new Investment(new Stock("HR.A"),
                                      10, 2.00);
      Investment shallow =
          new Investment(inv.getStock(),
                         inv.getQty(),
                         inv.getBookValue());

How can the client show that shallow is (probably) a shallow copy of inv?

      output.println(inv.getStock() == shallow.getStock());

The above code fragment prints true indicating that stock aggregated by inv is the same object as the stock aggregated by shallow.

Note that if getStock returns a copy of a stock then there is no way for the client to make a shallow copy of inv in this example.


import java.io.PrintStream; import type.lib.Investment; import type.lib.Stock; public class ShallowCopy { public static void main(String[] args) { PrintStream output = System.out; Investment inv = new Investment(new Stock("HR.A"), 10, 2.00); Investment shallow = new Investment(inv.getStock(), inv.getQty(), inv.getBookValue()); output.println(inv.getStock() == shallow.getStock()); } }

Copying Objects: Shallow Copy

The memory diagram for the previous example is:

 
inv ⇒ 400
shallow ⇒ 600
 
  |
400 Investment object
stock ⇒   500
  |
500 Stock object
 
  |
  |
600 Investment object
stock ⇒   500

Copying Objects: Deep Copy

A deep copy of an object creates a new object with attributes that are copies of those of the original object. If the original object is an aggregation, then the copied object aggregates different objects as the original.

      Investment inv = new Investment(new Stock("HR.A"),
                                      10, 2.00);
                                      
      Stock s = new Stock(inv.getStock().getSymbol());
      Investment deep =  new Investment(s,
                                        inv.getQty(),
                                        inv.getBookValue());

How can the client show that deep is (probably) a deep copy of inv?

      output.println(inv.getStock() == deep.getStock());

The above code fragment prints false indicating that stock aggregated by inv is a different object from the stock aggregated by deep.

import java.io.PrintStream;
import type.lib.Investment;
import type.lib.Stock;

public class DeepCopy
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Investment inv = new Investment(new Stock("HR.A"),
                                      10, 2.00);
      Stock s = new Stock(inv.getStock().getSymbol());
      Investment deep =  new Investment(s,
                                        inv.getQty(),
                                        inv.getBookValue());

      output.println(inv.getStock() == deep.getStock());
   }
}

Copying Objects: Deep Copy

The memory diagram for the previous example is:

 
inv ⇒ 400
deep ⇒ 600
 
  |
400 Investment object
stock ⇒   500
  |
500 Stock object
 
  |
  |
600 Investment object
stock ⇒   700
  |
700 Stock object
 

To Do For Next Lecture