104. Subclassing
As we saw in the previous chapter, the extends keyword announces that a class is a subclass of another:
class Person {}
class SuperHero extends Person {}
Let’s put something a little more useful together:
class Person {
String name
String toString() {
"Name: $name"
}
}
class SuperHero extends Person {
String superHeroName
String toString() {
"Name: $superHeroName (alias: $name)"
}
}
SuperHero groovyMan = new SuperHero(name: 'Gary Grails', superHeroName: 'Groovy \
Man!')
println groovyMan
In the expanded example you can see the following:
- The
Personclass is much like we’ve seen previously - it provides anameproperty and atoString()method -
SuperHeroextendsPerson:- By extending
Person, the SuperHero class has (inherits) thenameproperty -
SuperHeroalso has asuperHeroNameproperty - An implementation of
toString()is provided to give more details about the hero
- By extending
In order to create a new instance of SuperHero, the Groovy-supplied constructor is called and the name and superHeroName properties set:
new SuperHero(name: 'Gary Grails', superHeroName: 'Groovy Man!')
The script will output:
Name: Groovy Man! (alias: Gary Grails)
At this point the SuperHero class demonstrates two aspects of inheritance:
- Subclasses inherit the superclass’s properties and these are accessible to the subclass’s instances
- By providing a
toStringmethod that has the same signature as the superclass,SuperHerois said tooverridethe supertype’s declaration of the method.
The next example builds just slightly on the previous:
super on a SuperHeroclass Person {
String name
String toString() {
"Name: $name"
}
}
class SuperHero extends Person {
String superHeroName
@Override
String toString() {
"Name: $superHeroName\nAlias:\n ${super.toString()}"
}
}
SuperHero groovyMan = new SuperHero(name: 'Gary Grails', superHeroName: 'Groovy \
Man!')
println groovyMan
The script will output:
Name: Groovy Man!
Alias:
Name: Gary Grails
- The
@Overrideannotation signals that thetoStringmethod provided inSuperHerois overriding a method defined by the superclass.- This is a helpful annotation as Groovy will throw a compilation error if no pre-defined method signature exists - for example, if I mistyped the method as
tooString. - In fact I could have used the
@Overrideannotation in thePersonclass as well becausetoStringis a method defined injava.lang.Object, the superclass for all Groovy classes.
- This is a helpful annotation as Groovy will throw a compilation error if no pre-defined method signature exists - for example, if I mistyped the method as
- The
SuperHeroimplementation oftoStringincludes the call tosuper.toString(). Thesuperkeyword allows subclasses to specifically access the superclass’s methods and member variables (properties and fields) 1
It is possible to access the superclass’s constructors from within a subclass constructor:
super in a constructorclass Person {
String name
Person(String name) {
this.name = name
}
}
class SuperHero extends Person {
String superHeroName
SuperHero(String name, String superHeroName) {
super(name)
this.superHeroName = superHeroName
}
}
SuperHero groovyMan = new SuperHero('Gary Grails', 'Groovy Man!')
assert groovyMan.name == 'Gary Grails'
assert groovyMan.superHeroName == 'Groovy Man!'
The call to super(name) invokes a call to the Person(String name) constructor and Groovy determines the matching constructor by the parameters being passed. Importantly, the call to the superclass constructor (super()) must occur first - before any other statements in the constructor.
Type comparison
The in operator2 makes it possible to determine if one class extends another class or implements an interface or a trait. The example below demonstrates a set of checks against the SuperHero class
class Person {}
interface SuperPower {}
trait Flying implements SuperPower {}
class SuperHero extends Person implements Flying {}
assert SuperHero in Object
assert SuperHero in Person
assert SuperHero in Flying
assert SuperHero in SuperPower
Where the example above investigates the SuperHero class, the example below is somewhat more useful as it checks to see what’s available to a variable (superGroovy):
class Person {}
interface SuperPower {}
trait Flying implements SuperPower {}
class SuperHero extends Person implements Flying {}
def superGroovy = new SuperHero()
assert superGroovy in Person
assert superGroovy in SuperHero
assert superGroovy in SuperPower
assert superGroovy in Flying
assert superGroovy.class == SuperHero
The ability to check superGroovy in Flying is useful before we push superGroovy off a building and expect him not to hit the pavement!
- We also saw
superin the chapter on traits when considering a trait that implements other traits.↩ - Refer back to the chapter on Object Operators↩