(a) k.scale(0.5) and s.getArea() are compilable. That’s because Shape s references a list of objects that implements the Shape interface, so it’s guaranteed to implement the getArea() method.

(b) Depends if it’s the same class.

(c) ScalableShape forces each class to implement methods that it does not need. Clients should not be exposed to methods it doesn’t need. findVolume as a client method that takes in ScalableShape should not be exposed to the scale method.

This is the Interface Segregation Principle which makes up the SOLID principles for Object-oriented design.


Circle c1 = new Circle(10);  // compile-time: Circle, runtime: Circle
Circle c2 = new Circle(10);
Object o1 = c1;              // compile-time: Object, runtime: Circle
Object o2 = c2;

Compile time: Java sees o1 is declared as Object → picks the native equals(Object) from the Object class

public boolean equals(Object obj) { // The native class
    return (this == obj);
}

Runtime: Java checks what o1 actually is → it’s a Circle → looks inside Circle for an overriding equals(Object) → finds the @Override one → runs that instead of the native one


The main idea is: When overriding, you can narrow the return type (return something more specific), but you can never widen it (return something more general).

*Protected: means the field/method is accessible to:

  • The class itself
  • Any subclass (even in a different package)
  • Any class in the same package

At compile time, Calling bar(new B(1)) works because bar takes an A parameter (see void bar (A a)) Since B extends A, a B is an A. So passing new B(1) to a method expecting A is perfectly valid

At run-time, B.method() returns a plain A object B b = A object. (A object is assigned to b) (This actually fails already because when Java tries to assign the returned A object into B b, it implicitly checks that the object is actually a B. It’s not, so it throws a ClassCastException) b.g() fails because b (which is an A object) does not have the g() functionality.