☰ Contents Extending Classes

Overriding Methods

Student: Professor, I’m seeing something I don’t quite understand with classes.
Prof: What is it?
Student: Well, when the book or my programs print out records, they print the field names and values. But that doesn’t happen for my Point class.
Prof: Oh, interesting! Let’s have a look at your code.
Student: See, they print very differently! The second one just prints PointC@ and then a number that has nothing to do with x or y.
Prof: Right! That’s a great point. So one thing to make clear up front – when an object is added to a string with +, Java calls the toString() method on that object to get the String to concatenate. So here we are seeing that objects created from record constructors have a different toString() than those created with classes.
Student: OK, that sounds reasonable. How do I make PointC print like PointR? Do I write a toString?
Prof: That’s right.
Student: So it’s like how we needed to write our own equals()? And by default record puts in a good toString?
Prof: Yup, right again.
Student: So wait a minute. Where are the “bad” default equals() and toString() coming from?
Prof: Excellent question! And this leads perfectly into our next topic. You’re getting good at asking questions like that.
Student: Thanks, I guess. Sort of feels inevitable.
Prof: Here’s what’s going on. There is a built-in class in Java called Object (with a capital O). It has the “default” implementations of toString and equals that all classes use if they don’t define their own. If they do define their own, it is called overriding that method.
Student: That sounds kind of like an interface with default methods.
Prof: It is quite like that. Most of what Object does is effectively default methods. But it’s actually using a slightly different mechanism than implements – it is using inheritance, which has some of its own tradeoffs. I’ll give a small example where I make up my own class called Objekt with a stringify() method. The idea is to show the structure that Java has with its own built-in Object, but writing it all out ourselves:
Prof: What do you notice?
Student: Hm… I notice that we can call stringify on sc2 without having it defined at all. I guess that means it is using the one on Objekt?
Prof: That’s right.
Student: And I notice that the SubClass1 definition says @Override. What’s that? I tried deleting the whole @Override line and the program behaved the same.
Prof: Great question. @Override says “I mean to override this method!” Java will only give an error if that method isn’t defined on a class or interface being extended or implemented.
Student: Oh right, and extends is another word here, what does that mean?

Prof: When a class Child extends another class Parent, it tells Java that Child is a subclass of Parent (we call Parent the superclass). This means a few things happen:

  • Method calls and field lookups on Child objects will look for methods and fields in Parent if they aren’t found on Child
  • Child objects “count” as having the Parent type, so they can be stored in Parent variables and parameters, in elements of Parent arrays, and so on

This is a lot like interfaces, except for the fields piece, which we haven’t explored, but we can get to that soon.

Student: Wait, but how are our classes like PointC getting the defaults from Object?

Prof: Another great question – if you don’t write extends on a class definition in Java, it implicitly extends Object. It’s like Java adds that in for you. So the default toString and equals really are coming from there!

Also there’s one more definition worth sharing now: Java says subtyping is what’s happening when one type can be stored in a variable of a different type. So in Java’s terms, SubClass1 in the example above is a subtype of Objekt because it can be stored in an Objekt-typed variable. Subtyping also applies to interfaces: Circle is a subtype of Pict because of the implements relationship.

Practice

Using the classes from the Objekt example, which of these lines compile? Select all that do.
  1. Objekt o = new SubClass1();
    SubClass1 extends Objekt, so a SubClass1 object can be stored in an Objekt variable.
  2. SubClass1 s = new Objekt();
    The storing only goes one way. SubClass1 extends Objekt, not the other way around, so an Objekt can’t be stored in a SubClass1 variable. It doesn’t even have an x field.
  3. SubClass1 s = new SubClass2();
    Both classes extend Objekt, but neither one extends the other, so a SubClass2 can’t be stored in a SubClass1 variable.
  4. Object o = new SubClass2();
    Objekt has no extends, so it implicitly extends Object. A SubClass2 counts as an Objekt, and an Objekt counts as an Object.
After Objekt o = sc1;, which method body runs for o.stringify()?
  1. The one in SubClass1, so it returns "SubClass1[x=0]"
    The object stored in o is still the SubClass1 object, and its class overrides stringify. It works the same way as calling draw on a Pict variable that holds a Circle.
  2. The one in Objekt, so it returns "Objekt"
    The variable’s type is Objekt, but the object in it is a SubClass1, and the object’s class decides which stringify runs.
  3. Neither, because the line doesn’t compile
    Objekt has a stringify method, so Java allows the call on an Objekt variable.
Write a toString that makes PointC print the way PointR does.
class PointC {
double x;
double y;
PointC(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return ;
}
}

String s = "" + new PointC(4.0, 5.0);