1 of 39

CS61B, 2022

Lecture 9: More Inheritance!

  • Implementation Inheritance: Extends
  • Encapsulation
  • Casting
  • Higher Order Functions in Java

2 of 39

Implementation Inheritance: Extends

3 of 39

The Extends Keyword

When a class is a hyponym of an interface, we used implements.

  • Example: SLList<Blorp> implements List61B<Blorp>

If you want one class to be a hyponym of another class, you use extends.

We’d like to build RotatingSLList that can perform any SLList operation as well as:

  • rotateRight(): Moves back item the front.

Example: Suppose we have [5, 9, 15, 22].

  • After rotateRight: [22, 5, 9, 15]

List61B

SLList

AList

RotatingSLList

instead of an interface

4 of 39

RotatingSLList

Because of extends, RotatingSLList inherits all members of SLList:

  • All instance and static variables.
  • All methods.
  • All nested classes.

Constructors are not inherited.

public class RotatingSLList<Blorp> extends SLList<Blorp>{

public void rotateRight() {

Blorp oldBack = removeLast();

addFirst(oldBack);

}

}

… but members may be private and thus inaccessible! More after midterm.

5 of 39

Another Example: VengefulSLList

Suppose we want to build an SLList that:

  • Remembers all Items that have been destroyed by removeLast.
  • Has an additional method printLostItems(), which prints all deleted items.

public static void main(String[] args) {

VengefulSLList<Integer> vs1 = new VengefulSLList<Integer>();

vs1.addLast(1);

vs1.addLast(5);

vs1.addLast(10);

vs1.addLast(13); /* [1, 5, 10, 13] */

vs1.removeLast(); /* 13 gets deleted. */

vs1.removeLast(); /* 10 gets deleted. */

System.out.print("The fallen are: ");

vs1.printLostItems(); /* Should print 10 and 13. */

}

6 of 39

Another Example: VengefulSLList

public class VengefulSLList<Item> extends SLList<Item> {

private SLList<Item> deletedItems;

public VengefulSLList() {

deletedItems = new SLList<Item>();

}

@Override

public Item removeLast() {

Item oldBack = super.removeLast();

deletedItems.addLast(oldBack);

return oldBack;

}

public void printLostItems() {

deletedItems.print();

}

}

calls Superclass’s

version of removeLast()

Note: Java syntax disallows super.super. For a nice description of why, see this link.

7 of 39

Constructor Behavior Is Slightly Weird

Constructors are not inherited. However, the rules of Java say that all constructors must start with a call to one of the super class’s constructors [Link].

  • Idea: If every VengefulSLList is-an SLList, every VengefulSLList must be set up like an SLList.
    • If you didn’t call SLList constructor, sentinel would be null. Very bad.
  • You can explicitly call the constructor with the keyword super (no dot).
  • If you don’t explicitly call the constructor, Java will automatically do it for you.

public VengefulSLList() {

deletedItems = new SLList<Item>();

}

public VengefulSLList() {

super();

deletedItems = new SLList<Item>();

}

These constructors are exactly equivalent.

must come first!

8 of 39

Calling Other Constructors

If you want to use a super constructor other than the no-argument constructor, can give parameters to super.

public VengefulSLList(Item x) {

super(x);

deletedItems = new SLList<Item>();

}

public VengefulSLList(Item x) {

deletedItems = new SLList<Item>();

}

calls SLList(Item x)

Not equivalent! Code to the right makes implicit call to super(), not super(x).

9 of 39

The Object Class

As it happens, every type in Java is a descendant of the Object class.

  • VengefulSLList extends SLList.
  • SLList extends Object (implicitly).

Dog

ShowDog

IntList

String[]

Object

String

WorkingDog

SledDog

DrugDog

SLList

VengefulSLList

List61B

10 of 39

Is-a vs. Has-A

Important Note: extends should only be used for is-a (hypernymic) relationships!

Common mistake is to use it for “has-a” relationships. (a.k.a. meronymic).

  • Possible to subclass SLList to build a Set, but conceptually weird, e.g. get(i) doesn’t make sense, because sets are not ordered.

SLList

VengefulLSLList extends SLList

printLostItems()

SLList

Set extends SLList

This is an abomination.

11 of 39

Encapsulation

12 of 39

Complexity: The Enemy

When building large programs, our enemy is complexity.

Some tools for managing complexity:

  • Hierarchical abstraction.
    • Create layers of abstraction, with clear abstraction barriers!
  • “Design for change” (D. Parnas)
    • Organize program around objects.
    • Let objects decide how things are done.
    • Hide information others don’t need.

Managing complexity supremely important for large projects (e.g. project 2).

13 of 39

Modules and Encapsulation [Shewchuk]

Module: A set of methods that work together as a whole to perform some task or set of related tasks.

A module is said to be encapsulated if its implementation is completely hidden, and it can be accessed only through a documented interface.

ArrayDeque

addLast(Item x)

removeLast()

size()

...

14 of 39

A Cautionary Tale

Interesting forum questions from extra credit assignment from a few years ago.

Bottom line: Testing a Deque should usually not involve ANY assumptions about how it is implemented beyond what the public interface tells you.

15 of 39

Abstraction Barriers

As the user of an ArrayDeque, you cannot observe its internals.

  • Even when writing tests, you don’t (usually) want to peer inside.

Java is a great language for enforcing abstraction barriers with syntax.

ArrayDeque

addLast(Item x)

removeLast()

size()

...

{5, 3, 1, 7, 22}

16 of 39

Modules and Encapsulation [Shewchuk]

Module: A set of methods that work together as a whole to perform some task or set of related tasks.

A module is said to be encapsulated if its implementation is completely hidden, and it can be accessed only through a documented interface.

  • Instance variables private. Methods like resize private.
  • As we’ll see: Implementation inheritance (e.g. extends) breaks encapsulation!

ArrayDeque

addLast(Item x)

removeLast()

size()

...

17 of 39

Implementation Inheritance Breaks Encapsulation

Suppose we have a Dog class with the two methods shown.

public void bark() {

System.out.println("bark");

}

public void barkMany(int N) {

for (int i = 0; i < N; i += 1) {

bark();

}

}

Dog

bark()

barkMany(int N)

Dog.java

18 of 39

Implementation Inheritance Breaks Encapsulation

We could just as easily have implemented methods as shown below.

  • From the outside, functionality is exactly the same, it’s just a question of aesthetics.

public void bark() {

barkMany(1);

}

public void barkMany(int N) {

for (int i = 0; i < N; i += 1) {

System.out.println("bark");

}

}

Dog

bark()

barkMany(int N)

Dog.java

19 of 39

http://yellkey.com/central

What would vd.barkMany(3) output?

  1. As a dog, I say: bark bark bark
  2. bark bark bark
  3. Something else.

(assuming vd is a Verbose Dog)

bark()

Dog

bark()

barkMany(int N)

VerboseDog

barkMany(int N)

@Override

public void barkMany(int N) {

System.out.println("As a dog, I say: ");

for (int i = 0; i < N; i += 1) {

bark();

}

}

calls inherited bark method

public void bark() {

System.out.println("bark");

}

public void barkMany(int N) {

for (int i = 0; i < N; i += 1) {

bark();

}

}

Dog.java

VerboseDog.java

20 of 39

Implementation Inheritance Breaks Encapsulation

What would vd.barkMany(3) output?

  • As a dog, I say: bark bark bark
  • bark bark bark
  • Something else.

(assuming vd is a Verbose Dog)

bark()

Dog

bark()

barkMany(int N)

VerboseDog

barkMany(int N)

@Override

public void barkMany(int N) {

System.out.println("As a dog, I say: ");

for (int i = 0; i < N; i += 1) {

bark();

}

}

calls inherited bark method

public void bark() {

System.out.println("bark");

}

public void barkMany(int N) {

for (int i = 0; i < N; i += 1) {

bark();

}

}

Dog.java

VerboseDog.java

21 of 39

http://yellkey.com/state

What would vd.barkMany(3) output?

  1. As a dog, I say: bark bark bark
  2. bark bark bark
  3. Something else.

(assuming vd is a Verbose Dog)

bark()

Dog

bark()

barkMany(int N)

VerboseDog

barkMany(int N)

@Override

public void barkMany(int N) {

System.out.println("As a dog, I say: ");

for (int i = 0; i < N; i += 1) {

bark();

}

}

calls inherited bark method

public void bark() {

barkMany(1);

}

public void barkMany(int N) {

for (int i = 0; i < N; i += 1) {

System.out.println("bark");

}

}

Dog.java

VerboseDog.java

22 of 39

Implementation Inheritance Breaks Encapsulation

What would vd.barkMany(3) output?

c. Something else.

  • Gets caught in an infinite loop!

(assuming vd is a Verbose Dog)

bark()

Dog

bark()

barkMany(int N)

VerboseDog

barkMany(int N)

@Override

public void barkMany(int N) {

System.out.println("As a dog, I say: ");

for (int i = 0; i < N; i += 1) {

bark();

}

}

calls inherited bark method

public void bark() {

barkMany(1);

}

public void barkMany(int N) {

for (int i = 0; i < N; i += 1) {

System.out.println("bark");

}

}

Dog.java

VerboseDog.java

23 of 39

Type Checking and Casting

24 of 39

Dynamic Method Selection and Type Checking Puzzle

For each line of code, determine:

  • Does that line cause a compilation error?
  • Which method does dynamic method selection use?

public static void main(String[] args) {

VengefulSLList<Integer> vsl =

new VengefulSLList<Integer>(9);

SLList<Integer> sl = vsl;

sl.addLast(50);

sl.removeLast();

sl.printLostItems();

VengefulSLList<Integer> vsl2 = sl;

}

VengefulSLList

VengefulSLList

Static Type

Dynamic Type

SLList

VengefulSLList

vsl

sl

VengefulSLList

SLList

Reminder: VengefulSLList overrides removeLast and provides a new method called printLostItems.

25 of 39

Reminder: Dynamic Method Selection

If overridden, decide which method to call based on run-time type of variable.

  • sl’s runtime type: VengefulSLList.

public static void main(String[] args) {

VengefulSLList<Integer> vsl =

new VengefulSLList<Integer>(9);

SLList<Integer> sl = vsl;

sl.addLast(50);

sl.removeLast();

}

Also called dynamic type.

VengefulSLList

VengefulSLList

Static Type

Dynamic Type

SLList

VengefulSLList

vsl

sl

VengefulSLList

SLList

VengefulSLList doesn’t override, uses SLList’s.

Uses VengefulSLList’s.

Reminder: VengefulSLList overrides removeLast and provides a new method called printLostItems.

26 of 39

Compile-Time Type Checking

Compiler allows method calls based on compile-time type of variable.

  • sl’s runtime type: VengefulSLList.
  • But cannot call printLostItems.

public static void main(String[] args) {

VengefulSLList<Integer> vsl =

new VengefulSLList<Integer>(9);

SLList<Integer> sl = vsl;

sl.addLast(50);

sl.removeLast();

sl.printLostItems();

}

Compilation error!

Also called static type.

VengefulSLList

VengefulSLList

Static Type

Dynamic Type

SLList

VengefulSLList

vsl

sl

VengefulSLList

SLList

Reminder: VengefulSLList overrides removeLast and provides a new method called printLostItems.

27 of 39

Compile-Time Type Checking

Compiler allows method calls based on compile-time type of variable.

  • sl’s runtime type: VengefulSLList.
  • But cannot call printLostItems.

Compiler also allows assignments based on compile-time types.

  • Even though sl’s runtime-type is VengefulSLList, cannot assign to vsl2.
  • Compiler plays it as safe as possible with type checking.

public static void main(String[] args) {

VengefulSLList<Integer> vsl =

new VengefulSLList<Integer>(9);

SLList<Integer> sl = vsl;

sl.addLast(50);

sl.removeLast();

sl.printLostItems();

VengefulSLList<Integer> vsl2 = sl;

}

Also called static type.

Compilation errors!

VengefulSLList

VengefulSLList

Static Type

Dynamic Type

SLList

VengefulSLList

vsl

sl

VengefulSLList

SLList

28 of 39

Compile-Time Types and Expressions

Expressions have compile-time types:

  • An expression using the new keyword has the specified compile-time type.

  • Compile-time type of right hand side (RHS) expression is VengefulSLList.
  • A VengefulSLList is-an SLList, so assignment is allowed.

  • Compile-time type of RHS expression is SLList.
  • An SLList is not necessarily a VengefulSLList, so compilation error results.

SLList<Integer> sl = new VengefulSLList<Integer>();

VengefulSLList<Integer> vsl = new SLList<Integer>();

Compilation error!

29 of 39

Compile-Time Types and Expressions

Expressions have compile-time types:

  • Method calls have compile-time type equal to their declared type.

  • Any call to maxDog will have compile-time type Dog!

public static Dog maxDog(Dog d1, Dog d2) { … }

Poodle frank = new Poodle("Frank", 5);

Poodle frankJr = new Poodle("Frank Jr.", 15);

Dog largerDog = maxDog(frank, frankJr);

Poodle largerPoodle = maxDog(frank, frankJr);

Compilation error!

RHS has compile-time type Dog.

Example:

30 of 39

Casting

Java has a special syntax for specifying the compile-time type of any expression.

  • Put desired type in parenthesis before the expression.
  • Examples:
    • Compile-time type Dog:

    • Compile-time type Poodle:

Tells compiler to pretend it sees a particular type.

maxDog(frank, frankJr);

(Poodle) maxDog(frank, frankJr);

Poodle frank = new Poodle("Frank", 5);

Poodle frankJr = new Poodle("Frank Jr.", 15);

Dog largerDog = maxDog(frank, frankJr);

Poodle largerPoodle = (Poodle) maxDog(frank, frankJr);

Compilation OK!

RHS has compile-time type Poodle.

31 of 39

Casting

Casting is a powerful but dangerous tool.

  • Tells Java to treat an expression as having a different compile-time type.
  • In example below, effectively tells the compiler to ignore its type checking duties.
  • Does not actually change anything: sunglasses don’t make the world dark.

�If we run the code above, we get a ClassCastException at runtime.

  • So much for .class files being verifiably type checked...

Poodle frank = new Poodle("Frank", 5);

Malamute frankSr = new Malamute("Frank Sr.", 100);

Poodle largerPoodle = (Poodle) maxDog(frank, frankSr);

32 of 39

Higher Order Functions

(A First Look)

33 of 39

Higher Order Functions

Higher Order Function: A function that treats another function as data.

  • e.g. takes a function as input.

Example in Python:

def tenX(x):

return 10*x

def do_twice(f, x):

return f(f(x))

print(do_twice(tenX, 2))

200

34 of 39

Higher Order Functions in Java 7

Old School (Java 7 and earlier)

  • Fundamental issue: Memory boxes (variables) cannot contain pointers to functions.

Can use an interface instead. Let’s try it out.

IntUnaryFunction

apply(int)

TenX

apply(int)

def tenX(x):

return 10*x

def do_twice(f, x):

return f(f(x))

print(do_twice(tenX, 2))

35 of 39

Higher Order Functions in Java 7

Old School (Java 7 and earlier)

  • Fundamental issue: Memory boxes (variables) cannot contain pointers to functions.

Can use an interface instead: Java code below is equivalent to given python code.

def tenX(x):

return 10*x

public class TenX implements IntUnaryFunction {

public int apply(int x) {

return 10 * x;

}

}

public interface IntUnaryFunction {

int apply(int x);

}

36 of 39

Example: Higher Order Functions Using Interfaces in Java

def tenX(x):

return 10*x

def do_twice(f, x):

return f(f(x))

print(do_twice(tenX, 2))

public class HoFDemo {

public static int do_twice(IntUnaryFunction f, int x) {

return f.apply(f.apply(x));

}

public static void main(String[] args) {

System.out.println(do_twice(new TenX(), 2));

}

}

public class TenX implements IntUnaryFunction {

public int apply(int x) {

return 10 * x;

}

}

public interface IntUnaryFunction {

int apply(int x);

}

37 of 39

Example: Higher Order Functions in Java 8 or Later

In Java 8, new types were introduced: now can can hold references to methods.

  • You’re welcome to use these features, but we won’t teach them.
  • Why? The old way is still widely used, e.g. Comparators (see next lecture).

public class Java8HoFDemo {

public static int tenX(int x) {

return 10*x;

}

public static int doTwice(Function<Integer, Integer> f, int x) {

return f.apply(f.apply(x));

}

public static void main(String[] args) {

int result = doTwice(Java8HoFDemo::tenX, 2);

System.out.println(result);

}

}

38 of 39

Implementation Inheritance Cheatsheet

VengefulSLList extends SLList means a VenglefulSLList is-an SLList. Inherits all members!

  • Variables, methods, nested classes.
  • Not constructors.
  • Subclass constructor must invoke superclass constructor first.
  • Use super to invoke overridden superclass methods and constructors.

Invocation of overridden methods follows two simple rules:

  • Compiler plays it safe and only lets us do things allowed by static type.
  • For overridden methods the actual method invoked is based on dynamic type of invoking expression, e.g. Dog.maxDog(d1, d2).bark();
  • Can use casting to overrule compiler type checking.

Does not apply to overloaded methods!

39 of 39

Citations

https://wikids-life.wikispaces.com/file/view/LadybirdInheritance.jpg/160451153/604x297/LadybirdInheritance.jpg