Discussion 5: OOP and Subtypes

Over the past two lectures, we’ve begun to discuss object-oriented programming. We introduced user-defined types as a mechanism for bundling together problem-specific states and behaviors. Next, we introduced interfaces and subtype relationships as a way to model hierarchical relationships between types and enable polymorphism within our code. In today’s discussion, you’ll explore how user-defined types are modeled and practice working with the subtyping rules in Java.

Learning Outcomes

  1. Write an instance method given its specifications that accesses the values of its object's fields.
  2. Compare and contrast static types and dynamic types.
  3. Explain the benefits of leveraging polymorphism in object-oriented code.
  4. Describe the principle of dynamic dispatch and the compile-time reference rule.

Throughout the discussion, we’ll consider writing some code that a mock insurance company could use to keep track of their policies (of course, the insurance industry is much more sophisticated than anything we’re doing here!). We can think of an insurance policy as a collection of different covered entities. Each of these entities contributes some amount to the cost of the policy, and the policy covers damages to that entity until some coverage endpoint. We can model these entities with an interface, Insurable, that supports these common behaviors.

Insurable.java

Exercise 1: Defining a Class
(a)
One thing that we may wish to insure is a house, which we'll model with a House class. Houses have an address, contain a certain number of bedrooms and bathrooms, and a current market value. Fill in the body of the constructor to initialize these fields with the corresponding parameters.

House.java

(b)
Draw a memory diagram that depicts the state of the f() call frame after the initialization of home in the following code snippet.
(c)
To add Houses to insurance policies, we'll have the House class implement Insurable. To do this, we'll need to add definitions for the three Insurable methods. Suppose that isInsured() and policyExpiration() were taken care of (including adding any additional fields to support these behaviors). Add a definition of premium() to the House class. A house's insurance premium is equal to its current value times some multiplier that the insurance company determines from the house's address (factoring in things such as climate risk, traffic, neighborhood characteristics, etc.). Assume that this multiplier can be obtained by calling a static method rateMultiplier() which accepts the address String as its only argument and returns a double.

House.java

Exercise 2: Subtype Semantics
Houses aren't the only thing we might want to insure; we might have a car that we wish to add to our policy. We'll define the following Car class that also implements the Insurable interface. To save space, we've elided the class/field specs and some methods.

Car.java

(a)
Implement the following static method totalPolicyCost() to compute the total cost of an insurance policy, which we model as an array of Insurables.
(b)
Why are we allowed to have variables of type Insurable and Insurable[] even though the interface type Insurable cannot be instantiated? What benefits can this offer in our code?
Consider the following method:
(c)
For each of the following reference type expressions, determine its static type, as well as the dynamic type of the object that it references.

home:

car:

policy[1]:

(d)
Explain why we are allowed to assign (an alias reference to the same object as) car to an entry of policy, even though their static types are different.
(e)
Which method body would we enter if we execute policy[0].premium()? Which method body would we enter if we execute policy[1].premium()? How does Java make this determination?
(f)
What will happen if we try to execute policy[1].mileage() within the simulate() method? Explain your answer.
(g)
Draw a memory diagram that depicts the state of the program immediately after entering a premium() method for the first time. The bottom call frame in your diagram should correspond to the simulate() method.
Exercise 3: More Complicated Type Hierarchies
Often, we will want one class that we write to implement multiple interfaces. This will allow the class to be used polymorphically in multiple different contexts. For example, we may wish for our Car class from above to also implement the following Vehicle interface.

Vehicle.java

Suppose that another class Boat implements Vehicle (but does not implement Insurable).
(a)
Draw the hierarchy diagram for the types House, Car, Boat, Insurable, and Vehicle.
Now, suppose that we have (correctly) initialized the following variables:
For each of the following lines, determine whether it will (1) fail to compile, (2) compile but possibly encounter an error at runtime, (3) compile and definitely encounter an error at runtime, or (4) compile and always run successfully. Briefly explain your answers. Consider each line in isolation.
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(i)
(j)