8. Inheritance
Inheritance is a good way to share functionality between objects. When a class has a parent class, we say it inherits the fields and methods of its parent.
In Java, you use the extends keyword to define the parent of a class.
For example:
1 public class Griffon extends FlyingCreature {
2 }
Another way to share functionality is called Composition. This means that an object holds a reference to another object and uses it to do things. For example:
1 class Griffon {
2 Wing leftWing = new Wing()
3 Wing rightWing = new Wing()
4 def fly() {
5 leftWing.flap()
6 rightWing.flap()
7 }
8 }
This way you could also have a Bird class that also uses the Wing class for example.
8.1 Objectify
What is an object anyways? An object is an instance of a class (in Java, Groovy, and Scala).
In Java, classes have constructors, which can have multiple parameters for initializing the object. For example, see the below:
1 class FlyingCreature {
2 String name;
3 // constructor
4 public FlyingCreature(String name) {
5 this.name = name;
6 }
7 }
The constuctor of FlyingCreature has one parameter, name, which is stored in the name field.
A constructor must be called using the new keyword to create an object; for example:
1 String name = "Bob";
2 FlyingCreature fc = new FlyingCreature(name);
Once an object is created, it can be passed around (this is called “pass by reference”).
Although String is a special class, it is a class, so you can pass around instance of it as shown above.
JavaScript
In JavaScript, a constructor is a function used to define a prototype.
Inside the constructor the prototype is referred to using the this keyword.
For example, you could define a Creature in JavaScript as follows:
1 function Creature(n) {
2 this.name = n;
3 }
4 var bob = new Creature('Bob');
8.2 Parenting 101
A parent-class defines shared functionality (methods) and state (fields) that common to multiple classes.
For example, let’s create a FlyingCreature class that defines a fly() method and has a name:
1 class FlyingCreature {
2 String name;
3 public FlyingCreature(String name) {
4 this.name = name;
5 }
6 public void fly() {
7 System.out.println(name + " is flying");
8 }
9 }
10 class Griffon extends FlyingCreature {
11 public Griffon(String n) { super(n); }
12 }
13 class Dragon extends FlyingCreature {
14 public Dragon(String n) { super(n); }
15 }
16 public class Parenting {
17 public static void main(String ... args) {
18 Dragon d = new Dragon("Smaug");
19 Griffon g = new Griffon("Gilda");
20 d.fly(); // Smaug is flying
21 g.fly(); // Gilda is flying
22 }
23 }
Above there are two classes, Griffon and Dragon, that extend FlyingCreature. FlyingCreature is sometimes refered to as the base-class. Griffon and Dragon are refered to as sub-classes..
Keep in mind that you can use the parent class’s type to refer to any sub-class. For example, you can make any flying creature fly:
1 FlyingCreature creature = new Dragon("Smaug");
2 creature.fly(); // Smaug is flying
This concept is called extension. You extend the parent class (FlyingCreature in this case).
JavaScript
In JavaScript we can use prototypes to extend functionality.
For example, let’s say we have a prototype called Undead:
1 function Undead() {
2 this.dead = false;
3 this.living = false;
4 }
Now let’s create two other constructors, Zombie and Vampire:
1 function Zombie() {
2 Undead.call(this);
3 this.deseased = true;
4 this.talk = function() { alert("BRAINS!") }
5 }
6 Zombie.prototype = Object.create(Undead.prototype);
7
8 function Vampire() {
9 Undead.call(this);
10 this.pale = true;
11 this.talk = function() { alert("BLOOD!") }
12 }
13 Vampire.prototype = Object.create(Undead.prototype);
Notice how we set Zombie’s and Vampire’s prototype to an instance of the Undead prototype.
This allows zombies and vampires to inherit the properties of Undead while having different talk functions:
1 var zombie = new Zombie();
2 var vamp = new Vampire();
3 zombie.talk(); //BRAINS
4 zombie.deseased; // true
5 vamp.talk(); //BLOOD
6 vamp.pale; //true
7 vamp.dead; //false
8.3 Packages
In Java (and related languages, Groovy and Scala), a package is a name-space for classes. A name-space is a just a short-hand for a bin of names. Every modern programming language has some type of name-space feature. This is necessary due to the nature of having lots and lots of classes in typical projects.
As we learned in chapter 3, the first line of a Java file defines the package of the class; for example:
1 package com.github.modernprog;
Also, there is a common understanding that a package name corresponds to a URL (github.com/modernprog in this case). However, this is not necessary.
8.4 Public Parts
You might be wondering why the word public shows up everywhere in the examples so far. The reason has to do with encapsulation. Encapsulation is a big word that just means “A class should expose as little as possible to get the job done” (some things are meant to be private). This helps reduce complexity of code and therefore make it easier to understand and think about.
There are three different keywords in Java for varying levels of “exposure”:
-
private- Only this class can see it. -
protected- Only this class and it’s descendants can see it. -
public- Everyone can see it.
|
There’s also “default” protection (absence of keyword) which limits use to any class in the same package. |
This is why classes tend to be declared public because otherwise their usage would be very limited.
However, a class can be private, for example, when declaring a class within another class:
1 public class Griffon extends FlyingCreature {
2 private class GriffonWing {}
3 }
JavaScript
JavaScript does not have the concept of packages but instead you must rely on scope. Variables are only visible inside of the function they were created in except for global variables.
8.5 Interfaces
An interface declares method signatures that will be implemented by classes that extend the interface. This allow Java code to work on several different classes without necessarily knowing what specific class is “underneath” the interface.
For example, you could have an interface with one method:
1 public interface Beast {
2 int getNumberOfLegs(); // all interface methods are public
3 }
Then you could have several different classes that implement that interface:
1 public class Griffon extends FlyingCreature implements Beast {
2 public int getNumberOfLegs() { return 2; }
3 }
4 public class Unicorn implements Beast {
5 public int getNumberOfLegs() { return 4; }
6 }
8.6 Abstract Class
An abstract is a class that can have abstract methods but cannot have instances. It is something like an interface with functionality, however a class can only extend one super-class while it can implement multiple interfaces.
For example, to implement the above Beast interface as an abstract class, you can do the following:
1 public abstract class Beast {
2 public abstract int getNumberOfLegs();
3 }
Then you could add non-abstract methods and/or fields.
8.7 Enums
In Java, the enum keyword creates a type-safe, ordered list of values.
For example:
1 public enum BloodType {
2 A, B, AB, O, VAMPIRE, UNICORN;
3 }
An enum variable can only point to one of the values in the enum. For example:
1 BloodType type = BloodType.A;
The enum is automatically given a bunch of methods, such as:
- values() – Gives you an array of all possible values in the enum (static).
- valueOf(String) – Converts the given string into the enum value with the given name.
- name() – An instance method on the enum that gives its name.
Also, enums have special treatment in switch statements.
For example, in Java you can use an abbreviated syntax (assuming type is a BloodType):
1 switch (type) {
2 case VAMPIRE: return vampire();
3 case UNICORN: return unicorn();
4 default: return human();
5 }
8.8 Annotations
Java annotations allow you to add meta-information to Java code that can be used by the compiler, various API’s, or even your own code at runtime.
The most common annotation you will see is the @Override annotation which declares to the compiler that you are overriding a method.
For example:
1 @Override
2 public String toString() {
3 return "my own string";
4 }
This is useful because it will cause a compile-time error if you mistype the method name for example.
Other useful annotations are those in javax.annotation such as @Nonnull and @Nonnegative which declare your intentions.
Annotations such as @Autowired and @Inject are used by direct-injection frameworks like Spring and Google Guice,
repectively, to reduce “wiring” code.
8.9 Autoboxing
Although Java is an object-oriented language, this sometimes conflicts with its primitive types (int, long, float, double, etc.). For this reason, Java added autoboxing and unboxing to the language.
- Autoboxing
- The Java compiler will automatically wrap a primitive type in the corresponding object when it’s necessary. For example, when passing in parameters to a function or assigning a variable, as in the following:
Integer number = 1 - Unboxing
- This is simply the reverse of Autoboxing. The Java compiler will unwrap an object to the corresponding primitive type when possible.
For example, the following code would work:
double d = new Double(1.1) + new Double(2.2)
8.10 Summary
After reading this chapter, you should understand OOP, polymorphism, and the definition of:
- Extension and composition
- Public versus private versus protected
- Class, Abstract-class, Interface, Enum
- Annotations
- Autoboxing and unboxing
- A bit is the smallest possible amount of information. It corresponds to a 1 or 0.↩