Paper 2004 Object Oriented Programming Question 1 (a) i) An object is an instance of a type. For example, given a class Dog, "rover" would be an object in the following: Dog rover = new Dog(); (In particular, "rover" would be a Dog object, or an instance of class Dog.) ii) A class is one kind of type (an interface is the other). Classes can be used to group together data with the methods that operate on them. For example: class Dog { private String name; public Dog() { this.name = name; } public void woof() { System.out.println("Bark! My name is: " + name); } } This defines a (ridiculously exciting) Dog class. Dog is now also a type. We can create instances of the Dog class (Dog objects, if you will) by writing new Dog(). For an example, see (i). iii) An interface is the other kind of type. Interfaces contain public method declarations (but not definitions) which specify the methods that concrete implementing subclasses of it must define or inherit definitions for. For example: interface X { // method declarations are implicitly public here void blah(); void diblah(); } abstract class Y implements X // must be abstract since we don't implement blah() { void diblah() {} } class Z extends Y { void blah() {} // Z inherits an implementation of diblah(), so it fulfils the requirements for implementing X // and can thus be a concrete class (although it doesn't have to be!) } Interfaces can also contain public constants, e.g. interface W { final public static int x = 5; } iv) Abstract classes are those which cannot be instantiated, for whatever reason. v) Inheritance is a language mechanism for subtyping. For example: abstract class Animal // what's a generic animal, after all? { private String name; public Animal(String name) { this.name = name; } public String get_name() { return name; } } class Dog extends Animal // then again, what's a generic dog(?), but let's not go there... { public Dog(String name) { super(name); } public void woof() { System.out.println("Woof! My name is: " + get_name()); } } Dog extends Animal. It inherits the method get_name(), which is why it can call it in its own woof() method. We can do exciting things like this: Animal rover = new Dog(); // rover, a Dog, IS-AN Animal System.out.println(rover.get_name()); // Dogs have names! etc. You get the gist, anyway... (b) i) Objects of immutable abstract types always represent the same thing. For instance, the complex number 3 + 4i remains that. We might add 5 to it and get *another* complex number, 8 + 4i, but the original number is still the same. This contrasts with mutable types, like (say) the position of a robot. It makes perfect sense to say robot.move(3,4), making the robot move to (3,4), as opposed to robot = robot.move(3,4). It's not a new robot, after all! ii) class Displacement { final private double x; final private double y; public Displacement(double x, double y) { this.x = x; this.y = y; } public Displacement add(final Displacement other) { return new Displacement(x+other.x, y+other.y); } public Displacement scale(double s) { return new Displacement(s*x, s*y); } } iii) class Rectangle { private Displacement bottomLeft; private Displacement size; public Rectangle(Displacement bottomLeft, Displacement size) { this.bottomLeft = bottomLeft; this.size = size; } public void move(final Displacement disp) { bottomLeft = bottomLeft.add(disp); } public void scale(double s) { size = size.scale(s); } } (c) abstract class Complex { public abstract double arg(); public abstract double im(); public abstract double mag(); public abstract double re(); public Complex add(final Complex other) { return new RectComplex(re()+other.re(), im()+other.im()); } public Complex multiply(final Complex other) { return new PolarComplex(mag()*other.mag(), arg()+other.arg()); } } class RectComplex extends Complex { // represents x + iy final private double x, y; public RectComplex(double x, double y) { this.x = x; this.y = y; } public double arg() { return Math.atan2(y, x); } public double im() { return y; } public double mag() { return Math.sqrt(x*x+y*y); } public double re() { return x; } } class PolarComplex extends Complex { // represents r(cos theta + i sin theta) final private double r, theta; public PolarComplex(double r, double theta) { this. r = r; this.theta = theta; } public double arg() { return theta; } public double im() { return r*Math.sin(theta); } public double mag() { return r; } public double re() { return r*Math.cos(theta); } } Question 2 TODO Question 3 TODO Question 4 (a) class Value { final private double d; private Value() { d = 0; } public Value(double d) { this.d = d; } public Value add(Value other) { return other == INVALID ? INVALID : new Value(d + other.d); } public Value sub(Value other) { return other == INVALID ? INVALID : new Value(d - other.d); } public Value mul(Value other) { return other == INVALID ? INVALID : new Value(d * other.d); } public Value div(Value other) { return other == INVALID ? INVALID : new Value(d / other.d); } final public static Value INVALID = new Value() { public Value add(Value other) { return INVALID; } public Value sub(Value other) { return INVALID; } public Value mul(Value other) { return INVALID; } public Value div(Value other) { return INVALID; } }; } (b) abstract class Expr { public abstract Value eval(State state); public abstract boolean evaluated(); } class ConstExpr extends Expr { private boolean bEvaluated = false; private Value v; public ConstExpr(double d) { v = new Value(d); } public Value eval(State state) { bEvaluated = true; return v; } public boolean evaluated() { return bEvaluated; } } class InputExpr extends Expr { private boolean bEvaluated = false; private Value v = Value.INVALID; public Value eval(State state) { bEvaluated = true; return v; } public boolean evaluated() { return bEvaluated; } public void setValue(double d) { bEvaluated = false; v = new Value(d); } } interface State { Value value(String name); } class NamedExpr extends Expr { private String name; public NamedExpr(String name) { this.name = name; } public Value eval(State state) { return state.value(name); } public boolean evaluated() { return false; } } class BinopExpr extends Expr { public enum Op { ADD, SUB, MUL, DIV } private boolean bEvaluated = false; private Expr e1, e2; private Op op; public BinopExpr(Op op, Expr e1, Expr e2) { this.op = op; this.e1 = e1; this.e2 = e2; } public Value eval(State state) { bEvaluated = true; switch(op) { case ADD: return e1.eval(state).add(e2.eval(state)); case SUB: return e1.eval(state).sub(e2.eval(state)); case MUL: return e1.eval(state).mul(e2.eval(state)); case DIV: return e1.eval(state).div(e2.eval(state)); default: throw new Error(); } } public boolean evaluated() { return e1.evaluated() && e2.evaluated() && bEvaluated; } } Note: Inbuilt constants are just added at the start of the program as instances of ConstExpr. (c) import java.util.*; class Calculator implements State { private Map exprMap = new TreeMap(); private Map valueMap = new TreeMap(); public Calculator() { declare("PI", new ConstExpr(3.141592654)); // etc. } public void declare(String name, Expr expr) { exprMap.put(name, expr); valueMap.put(name, Value.INVALID); } public void setValue(String name, double value) { InputExpr ie = (InputExpr)exprMap.get(name); // we're assuming it's called correctly! ie.setValue(value); } public void evaluate() { for(String s: exprMap.keySet()) { Expr e = exprMap.get(s); if(!e.evaluated()) valueMap.put(s, e.eval(this)); } } public Value value(String name) { Expr e = exprMap.get(name); if(!e.evaluated()) valueMap.put(name, e.eval(this)); return valueMap.get(name); } } (d) TODO