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.
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.
Examples of aggregation:
Examples that are not aggregation:
A unified modeling language (UML) diagram illustrates aggregation relationships between classes:
Investment
has 1 Stock
referenceCreditCard
has 2 Date
referencesPortfolio
has a variable number of Investment
referencesIn 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");
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); } }
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.
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())); } }
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()); } }
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 | |
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()); } }
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 |
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()); } }
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 | |