Types and Nested Object References
1 What is a type?
A type tells you what you can do with a piece of data. Each operation or method is only available on certain kinds of data. For example, an operation like addition is nonsense on Dillos, while canShelter makes no sense on a number.
When you write down a type in Java, you are telling Java which operations you want to allow your code to perform on a datum. Java will raise a compilation error if you try to use an operation or method that isn’t allowed by the types.
2 So What Were Interfaces Again?
Last class, we introduced an IAnimal interface:
interface IAnimal { |
public boolean lengthBelow(int someLength); |
} |
|
class Dillo implements IAnimal { ... } |
Let’s briefly review what interfaces do.
Interfaces do two things:
Introduce a new type name (e.g., IAnimal)
State which methods Java should allow on that type (e.g., lengthBelow)
We create interfaces in order to allow multiple classes (e.g., Dillos and Boas) to fall under a common type name. We associate a class with a type name using an implements annotation on a class. If a class implements an interface, it must provide a version of every method listed in the interface. How that methods works can (and often will) differ across classes, but the name, input types, and output types must be the same across the classes.
3 Interfaces in Practice
Let’s see how this plays out when using interfaces as types. Here’s the relevant fragment of our animals and cages code (constructors omitted):
class Dillo implements IAnimal { |
int length ; |
boolean isDead ; |
|
// determines whether Dillo is dead and longer than 60 |
boolean canShelter() { |
return (this.isDead && this.length > 60); |
} |
|
// determine whether Dillo length less than given one |
public boolean lengthBelow(int someLength) { |
return this.length < someLength; |
} |
} |
|
interface IAnimal { |
public boolean lengthBelow(int someLength); |
} |
|
class Cage { |
int size; // length of cage in inches |
IAnimal resident; // any animal can live in cage |
} |
Is the following code valid (according to the compiler)?
Dillo babyDillo = new Dillo(8, false); |
new Cage(10, babyDillo) |
Yes, babyDillo is a Dillo, which in turn is an IAnimal (according to the implements statement on the Dillo class).
What about the following?
IAnimal babyDillo = new IAnimal(8, false); |
This is not valid. You can only use new on classes. Interfaces have no constructors, because they aren’t used to make data. They are only used to specify lists of methods that other classes are required to provide.
How about this one?
IAnimal adultDillo = new Dillo(24, true); |
This is fine. A Dillo is indeed an IAnimal, so these types make sense.
So which type is better? Dillo as we used on babyDillo or IAnimal as we used for adultDillo? What’s the difference?
Return to the definition of type – a type tells you what you can do with a piece of data. If we use the IAnimal type, then Java will only allow us to use methods that are known to exist in IAnimals, namely, the methods listed in the interface. So Java will not let you call canShelter on adultDillo.
But wait – isn’t there a canShelter method sitting inside adultDillo? Yes, it is there, but Java won’t let you access it because of the type you ascribed. If you used type Dillo instead, you could call canShelter. The Java compiler simply follows the types when checking your code. It doesn’t do anything clever to dig into your objects to see what is there. It just follows the types, and an IAnimal does not provide canShelter.
4 Under the Hood with Nested Objects
To help make sure people understand how fields containing objects work, let’s go under the hood again and see what the memory map looks like on the following sequence of statements:
Dillo babyDillo = new Dillo(8, false); |
Cage cage143 = new Cage(10, babyDillo) |
Cage cage1031 = new Cage(8, new Dillo(15, true)) |
The memory map is in this file. Things to note are:
There is no name directly referencing the Dillo of length 15.
If one object has another as a field value, the field contains an arrow (reference) to the other object. We don’t put a name like babyDillo in the field, even though that’s how it appears in the code. Under the hood, the names are replaced by references to the actual objects. This will matter significantly in a few lectures.
If you wanted to ask something like "is the resident of cage1031 shorter than 12?", you would write
cage1031.resident.lengthBelow(12)
Objects don’t have to be assigned to known names in order to be used in computations, as long as they are accessible from some name (which could be a parameter name inside a method).