Instructor: Stefan Mitsch
How do we support code sharing between classes?
What should happen when methods have the same name?
toString
x
Animal
Bird
Cat
class Animal { override def toString () = "Animal" }class Bird extends Animal { override def toString () = "Bird" }class Cat extends Animal { override def toString () = "Cat" }val xs = Array[Animal] (new Bird(), new Cat ())for (x <- xs) println (x.toString())
BirdCat
class Animal { public String toString () { return "Animal"; } }class Bird extends Animal { public String toString () { return "Bird"; } }class Cat extends Animal { public String toString () { return "Cat"; } }Animal[] xs = { new Bird(), new Cat () };for (Animal x : xs) System.out.println (x.toString());
$ javac AnimalTest.java && java AnimalTestBirdCat
class Animal { public: string to_string() { return "Animal"; } };class Bird: public Animal { public: string to_string() { return "Bird"; } };class Cat: public Animal { public: string to_string() { return "Cat"; } };Animal *xs[] = { new Bird(), new Cat() };for (Animal *x : xs) cout << x->to_string() << endl;
$ g++ -std=c++11 -o animal1 animal1.cpp && ./animal1AnimalAnimal
virtual
class Animal { public: virtual string to_string() { return "Animal";class Bird: public Animal { public: virtual string to_string() { return "Bird"; }class Cat: public Animal { public: virtual string to_string() { return "Cat"; } Animal *xs[] = { new Bird(), new Cat() };for (Animal *x : xs) cout << x->to_string() << endl;
$ g++ -std=c++11 -o animal2 animal2.cpp && ./animal2BirdCat
class Animal { public: virtual string to_string() { return "Animal";class Bird: public Animal { public: virtual string to_string() { return "Bird"; }class Cat: public Animal { public: virtual string to_string() { return "Cat"; } Animal xs[] = { Bird(), Cat() };for (Animal x : xs) cout << x.to_string() << endl;
$ g++ -std=c++11 -o animal3 animal3.cpp && ./animal3AnimalAnimal
interface Fn { int apply (int x); }class F implements Fn { public int apply (int x) { return x + 1; } }class G implements Fn { public int apply (int x) { return x + 2; } }class H implements Fn { public int apply (int x) { return x + 3; } }
Fn[] fs = { new F (), new G (), new H () };for (int i = 0; i < fs.length; i++) { System.out.format ("fs[%d].apply(20)=%d\n", i, fs[i].apply(20));}
fs[0].apply(20)=21fs[1].apply(20)=22fs[2].apply(20)=23
interface Fn { int apply (int x); }
Fn[] fs = new Fn[3];for (int i = 0; i < fs.length; i++) { int j = i + 1; // effectively final fs[i] = new Fn() { int apply (int x) { return j + x; } }}for (int i = 0; i < fs.length; i++) { System.out.format ("fs[%d].apply(20)=%d\n", i, fs[i].apply(20));}
Fn[] fs = new Fn[3];for (int i = 0; i < fs.length; i++) { int j = i + 1; // effectively final fs[i] = x -> x + j; }for (int i = 0; i < fs.length; i++) { System.out.format ("fs[%d].apply(20)=%d\n", i, fs[i].apply(20));}
class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? g () : f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}
f
A x = new A ();x.f (2);
A.f (2)A.f (1)A.f (0)A.g ()$1 ==> 0
g ()
f (x-1)
this
class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}
A
B
g
class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}class B extends A { int g () { System.out.println ("B.g ()"); return 0; }}
A x = new B ();x.f (2);
A.f (2)A.f (1)A.f (0)B.g ()$1 ==> 0
Let programmers specify how classes can be instantiated? Let programmers decide which methods can/must/cannot be overridden?
abstract class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}class B extends A { int g () { System.out.println ("B.g ()"); return 0; }}
A x = new A ();
| Error:| A is abstract; cannot be instantiated| A x = new A ();| ^------^
abstract class A { int f (int x) { // ... } abstract int g ();}class B extends A { }
| Error:| B is not abstract and does not override abstract method g() in A| class B extends A {| ^------------------...
abstract class A { final int f (int x) { // ... } abstract int g ();}class B extends A { int f (int x) { System.out.format ("B.f (%d)%n", x); return 0; } int g () { System.out.println ("B.g ()"); return 0; } }
Compile error
| Error:| f(int) in B cannot override f(int) in A| overridden method is final| int f (int x) { System.out.format ("B.f (%d)%n", x); return 0; }| ^--------------------------------------------------------------^
abstract class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } abstract int g ();}class B extends A { int f (int x) { System.out.format ("B.f (%d)%n", x); return 0; } int g () { System.out.println ("B.g ()"); return 0; } }
A x = new B (); x.f (2);
B.f (2)$1 ==> 0
class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}class B extends A { int f (int x) { System.out.format ("B.f (%d)%n", x); return 0; } int g () { System.out.println ("B.g ()"); return 0; } }
A.f
class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}class B extends A { int f (int x) { System.out.format ("B.f (%d)%n", x); return this.f(x); } int g () { System.out.println ("B.g ()"); return 0; } }
B.f (2)B.f (2)B.f (2)B.f (2)... // infinite loop
super
class A { int f (int x) { System.out.format ("A.f (%d)%n", x); return (x == 0) ? this.g () : this.f (x - 1); } int g () { System.out.println ("A.g ()"); return 0; }}class B extends A { int f (int x) { System.out.format ("B.f (%d)%n", x); return super.f(x); } int g () { System.out.println ("B.g ()"); return 0; } }
B.f (2) A.f (0) A.f (2) B.g () B.f (1) $1 ==> 0A.f (1)B.f (0)
var A = { f (x) { console.log ("A.f (" + x + ")") return (x == 0) ? this.g () : this.f (x - 1); }, g () { console.log ("A.g ()"); return 0; }}var B = { f (x) { console.log ("B.f (" + x + ")"); return super.f (x); }, g () { console.log ("B.g ()"); return 0; }}
Object.setPrototypeOf(B, A);B.f (2);
class Animal { public: virtual string to_string() { return "Animal"; } };class Bird: public Animal { public: virtual string to_string() { return "Bird"; } };class Cat: public Animal { public: virtual string to_string() { return "Cat"; } };
Animal* a = new Bird ();cout << a->to_string() << endl;a = new Cat ();cout << a->to_string() << endl;
void print(Animal& a) { cout << a.to_string() << endl; }Bird b = Bird ();Cat c = Cat ();print(b);print(c);
Bird* b = new Bird ();cout << b->to_string() << endl;b = new Cat ();cout << b->to_string() << endl;
class A: def f () = { println("A.f"); this.g () } def g () = { println("A.g"); this.h () } def h () = println("A.h")class B extends A: override def f () = { println("B.f"); super.f () } override def h () = println("B.h") def i () = println("B.i")
val a:A = new A()a.f()a.g()a.h()a.i()
val a:A = new B()a.f()a.g()a.h()a.i()
val b:B = new B()b.f()b.g()b.h()b.i()