• Inheritance
Guess who's inheriting the money
Biology and inheritance
public class Rectangle{
  // Center coordinate
  private double x; 
  private double y;

  public void move
    (double dx, double dy){ 
     x += dx;
     y += dy;
  }
  private double width, height;  ...
}
public class Circle {
  // Center coordinate
  private double x; 
  private double y;

  public void move
    (double dx, double dy){ 
     x += dx;
     y += dy;
  }
  private double radius;  ...
}
  • Create a parent class Shape containing common code portions.

  • Relate both Rectangle and Circle to Shape.

Common Rectangle and Circle attributes:
double x;
double y;
Rectangle attributes Circle attributes
double width;
double height;
double radius;
Image layer 1
Image layer 2
  • Derived classes inherit state and behaviour.

  • Refinement, specialization.

  • is-A relationship:

    • A rectangle is a shape.

    • A circle is a shape.

public class Shape {
  private double x, y;
}
public class Rectangle extends Shape {
  private double width;
  private double height;
}
public class Circle extends Shape {
  private double radius;
}
final double x = 2, y = 3;
final Shape shape = new Shape(x, y);

final double width = 2, height = 5;
final Rectangle r = new Rectangle(x, y , width, height);

final double radius = 2.5;
final Circle circle = new Circle(x, y , radius);
/**
  * Creating a shape located at center coordinate.
  * @param x The center's x component.
  * @param y The center's y component.
  */
public Shape(double x,double y) {
  this.x = x;
  this.y = y;
}
final Rectangle r = 
   new Rectangle(x, y ,
                 width, height );

Center coordinate components belonging to superclass Shape.

width and height belonging to class Rectangle.

Solution: Nested constructor call. Coming soon ...

/**
  * Creating a rectangle at (x|y) of given width and height.
  * @param x Center's x component.
  * @param y Center's y component.
  * @param width Rectangle's width.
  * @param height Rectangle's height.
  */
public Rectangle(double x, double y,
             double width, double height) {
  super(x, y) ;
  this.width = width; this.height = height ;
}
  • Inheritance
    • ➟ Overriding equals() and hashCode()
public abstract class Shape {
  ...
  @Override  public boolean equals(final Object o) {
    if (o instanceof Shape s ) { 
      return x == s.x && y == s.y; 
    } else {
      return false; 
    } ...
public class Rectangle extends Shape {
  ...
  @Override public boolean equals(final Object o) {
    if (o instanceof Rectangle r) {
      return super.equals(o)  &&
             width == r.width && height == r.height ;
    } else {
      return false;
    } ...
  1. Let me pass, please!
  2. Why is == correctly comparing enum instances?
  • Inheritance
    • ➟ Overriding toString()
Code Output
package inherit;

public class Run {
  
  public static void main(String[] args) {
  final Shape shape =
     new Shape(2.0, 3.0); // Center coordinates
    System.out.println(shape); 
inherit.Shape@37d31475

Desired:

(2.0|3.0)
Image layer 1
Image layer 2
Image layer 3
Image layer 4
Image layer 5
Image layer 1
Image layer 2
Image layer 3
Code Output
final Rectangle r =                   // Center coordinates,
  new Rectangle(2.0, 3.0, 3.0, 4.0);  // width and height
System.out.println(r);
(2.0|3.0)

Desired:

Rectangle at (2.0|3.0), width= 3.0, height=4.0
public class Rectangle extends Shape {
  @Override public String toString() {
    return "Rectangle at " + super.toString()  +
      ", width= " + width + ", height=" + height; 
  } ...

The super keyword allows for calling the superclass Shape's toString() method inserting the (2.0|3.0) center coordinate.

Append class Rectangle's own width and height attribute values.

Image layer 1
Image layer 2
Image layer 3
Image layer 4
public class Circle extends Shape {
  /**
   * Creating a circle of given center and radius
   * @param x Center's x component.
   * @param y Center's y component.
   * @param radius The circle's radius.
   */
  public Circle(double x,double y, double radius) {
    super(x, y);
    this.radius = radius;
  }
  @Override public String toString() {
    return "Circle at " + super.toString() +", radius= " + radius;
  }
  private double radius;
}
Shape and
      toString()
  1. String vs. StringBuffer
  2. Alternate implementation of opposite directions
  • Inheritance
    • final methods
Image layer 1
Image layer 2
Image layer 3
public class Shape {
  /**
   * Move by a given translation vector
   * @param xTrans Translation's x component
   * @param yTrans Translation's y component
   */
  public void move(final int xTrans, final int yTrans) {
    x += xTrans;
    y += yTrans;
  } ...
public class Rectangle extends Shape {
  @Override public void move(int xTrans, int yTrans) {
    // I'm so dumb!
    ...
  }
public abstract class Shape {
... public final void move(final int xTrans, final int yTrans) {
      x += xTrans;
      y += yTrans;
    }...
public class Rectangle extends Shape {
  // Syntax error: 'move(int, int)' cannot override
  // 'move(int, int)' in 'inherit.Shape'; overridden method is final
  @Override public void move(int xTrans, int yTrans) {...
  • Inheritance
    • ➟ Abstract methods
public class Rectangle extents Shape {
  /**
   * Calculate the area.
   * @return The rectangle's area
   */
  public double getArea() {
    return width * height;
  } ...
public class Circle extents Shape {
  /**
   * Calculate the area.
   * @return The circle's area
   */
  public double getArea() {
    return PI * radius * radius;
  } ...
final Shape[] shapes  = {
    new Circle(1, 1, 2.) ,
    new Rectangle(1, -1, 2., 3.)};

for (final Shape s : shapes) {
  System.out.println(s.toString() + ": area = " + s.getArea()); 
}
Circle at (1.0|1.0), radius= 2.0: area = 12.566370614359172
Rectangle at (1.0|-1.0), width= 2.0, height=3.0: area = 6.0
  • No meaningful getArea() method in class Shape possible.

  • Meaningful implementations exist both in subclass Rectangle and Circle.

Solution: Abstract method getArea() in superclass Shape.

abstract public class Shape {
  /**
   * Calculate the shape's area.
   * @return The shape's area
   */
  abstract public double getArea(); ...
public class Rectangle extends Shape {
  @Override
  public double getArea() {
    return width * height;
  }...
public class Circle ... {
 @Override
  public double getArea() {
    return Math.PI *
           radius * radius;
  } ...
Image layer 1
Image layer 2
Image layer 3
Image layer 4
Image layer 5
Image layer 6
Image layer 7
What's a shape anyway?
final Shape s =
   new Shape(1., 2.); // 'Shape' is abstract; cannot be instantiated
// Error: Class 'Circle' must either be declared abstract or
//        implement abstract method 'getArea()' in 'Shape'
public class Circle extends Shape {

  public Circle(double x,double y, double radius) {
    super(x, y);
    this.radius = radius;
  }
  private double radius;
}
  • A class containing an abstract method must itself be declared abstract.

  • abstract classes are allowed to host non-abstract methods.

  • A class may be declared abstract irrespective of purely containing non-abstract methods.

  • Inheritance
    • ➟ Abstract methods
      • ➟ Geometry classes reconsidered
Moving shapes
  1. Defining a Shape class hierarchy
  2. Scaling shapes
  3. Providing toString() methods
  • Inheritance
    • protected access
package model;
public abstract class Shape {

  final protected long creationTime = System.nanoTime();
...
}
------------------------------------------------
package model.sub;
public class Rectangle extends Shape {
  static final Logger log = LogManager.getLogger(Rectangle.class);

  @Override public double getArea() {
    log.info("Rectangle creation time:" + creationTime );
    return width * height;
  } ...
}
  1. protected vs. package private
  2. protected access involving different instances
  • Inheritance
    • final classes
public final class Shape { ... }

-------------------------
public class Rectangle
  extends Shape { // Error: final class cannot be extended
...
  • Design decision.

  • Slight performance gain.

Note

Prominent Example: java.lang.String.

public static void main(String[] args) {
  final Shape[] shapes = {
    new Circle(1, 1, 2.),
    new Rectangle(1, -1, 2., 3.)};
  print(shapes);
}
static void print(final Shape[] shapes) {
  for (final Shape s : shapes) {
    if (s instanceof Rectangle) {
      System.out.println("Type Rectangle");
    } else if (s instanceof Circle) {
      System.out.println("Type Circle");
    }
  }
}
Type Circle
Type Rectangle
Rectangle r1 = new Rectangle(1, 2, 5, 4),
          r2 = new Rectangle(1, 2, 1, 7),
          r3 = new Rectangle(1, 2, 5, 4);

Circle c = new Circle(-2, 3, 5);

System.out.print(r1.equals("Hi"));//false: Differing classes Rectangle and String.
System.out.print(r1.equals(r2));  //false: Differing width and height.
System.out.print(r3.equals(r1));  //true:  Two rectangles having identical
                                  //       (x|y), width and height.
System.out.print(r1.equals(c));   //false: Differing classes Rectangle and Circle.
System.out.print(c.equals(c));    //true: Object equal to itself.

Two Shape instances shall be considered equal if:

  • Both instances are of common type i.e. either Rectangle or Circle.

  • Their center coordinates match within a threshold of 10 - 15 .

  • width and height or radius match within a threshold of 10 - 15 .

public abstract class Shape {
  private double x, y;

  protected boolean equalCenter(final Shape o) {
      return Math.abs(o.x - x) + Math.abs(o.y - y) < 1.E-15;
  }
...
public class Rectangle extends Shape {
    @Override public boolean equals(Object o) {
    if (o instanceof Rectangle r) {

      final Rectangle oRectangle = (Rectangle) o; // Cast is «legal»
      return super.equalCenter(r) &&
             Math.abs(r.width- width) +
                 Math.abs(r.height- height) < 1.E-15;
    }
    return false;
  }
 ...

For o == null the expression o instanceof Rectangle evaluates to false.

public class Circle extends Shape {
  @Override public boolean equals(final Object o) {
    if (o instanceof Circle c){
      return super.equalCenter(c) &&
        Math.abs(c.radius - radius) < 1.E-15;
    }
    return false;
  } ...
final Rectangle
    r1 = new Rectangle(2, 3, 1,4),
    r2 = new Rectangle(2, 3, 2,8),
    r3 = new Rectangle(2, 3, 1,4);

final Circle c = new Circle(2,3, 7);

System.out.println("r1.equals(r2): " + r1.equals(r2));
System.out.println("r1.equals(r3): " + r1.equals(r3));
System.out.println("c.equals(r1): " + c.equals(r1));
r1.equals(r2): false
r1.equals(r3): true
c.equals(r1): false
public class Shape {

  double x, y;
 ...
  @Override  // Promise: Subsequent method overrides Object.toString();
  public String toString() {
    return "(" + x + "|" + y + ")";
  }
}
public class Shape {

  double x, y;
 ...
  @Override  // Error: method does not override a method from a supertype
  public String toString(int value) {
    return "(" + x + "|" + y + ")";
  }
}

Explanation: The given method does not override Object.toString().