Final and Private Modifiers

class P {
    private final int x;

    P(int x) {
        this.x = x;
    }

    P foo() {
        return new P(this.x + 1);
    }

    P bar(P p) { 
        p.x = p.x + 1; // this mutation cannot happen as 'final'
        return p;
    }
}

class Q { 
    P baz(P p) { 
        return new P(p.x + 1); // this cannot happen as x in class P is 'private'
    }
}
error: cannot assign a value to final variable x
error: x has private access in P

Tell-Don’t-Ask

record DoublePair(double fst, double snd) {} 
// does not violate as it's a record class that holds immutable data (has final & private modifiers). It's a shortcut for a class.

class Point {
    private final DoublePair coord;

    Point(double x, double y) {
        this.coord = new DoublePair(x, y);
    }

    private double getX() { // does not violate as it's private
        return this.coord.fst();
    }

    private double getY() {
        return this.coord.snd();
    }

    double distanceTo(Point otherPoint) {
        double dx = this.getX() - otherPoint.getX();
        double dy = this.getY() - otherPoint.getY();
        return Math.sqrt(dx * dx + dy * dy);
    }

    public String toString() {
        return "(" + this.getX() + ", " + this.getY() + ")";
    }
}

Method Overloading

  • compile-time polymorphism
  1. Same method name
  2. Different type signature
  3. Return type does not matter
(a) class A1 - ✅
void f(int x) {}
void f(boolean y) {}
  • Different parameter types: int vs boolean
  • This is valid overloading
(b) class A2 - ❌ ****
void f(int x) {}
void f(int y) {}
  • Same signature: both take int
  • Parameter name (x vs y) doesn’t matter - only the type matters
  • Error: “method f(int) is already defined”
(c) class A3 - ❌
private void f(int x) {}
void f(int y) {}
  • Wait… both take int!
  • Error: “method f(int) is already defined”
  • Access modifier (private vs default) doesn’t make them different methods
(d) class A4 - ❌
int f(int x) { return x; }
void f(int y) {}
  • Both have signature f(int)
  • Different return types (int vs void) doesn’t matter
  • Error: “method f(int) is already defined”
(e) class A5 - ✅
void f(int x, String s) {}
void f(String s, int y) {}
  • Different parameter order: (int, String) vs (String, int)
  • These are different signatures, so valid overloading ✅

Method Overriding

  • run-time polymorphism
  1. A method in sub-class can override the same method in its super-class
  2. Same method name and same parameters’ types signature (number, type, and order)
  3. Covariant return type

Format

@Override
ReturnType methodName(parameters) {
    // new implementation
}
Task
class FormattedText {
    private final String text;
    private final boolean isUnderlined;

    FormattedText(String text) {
        this.text = text;
        this.isUnderlined = false;
    }

    /*
     * Overloaded constructor, but made private to prevent
     * clients from calling it directly.
     */
    private FormattedText(String text, boolean isUnderlined) {
        this.text = text;
        this.isUnderlined = isUnderlined;
    }

    FormattedText toggleUnderline() {
        return new FormattedText(this.text, !this.isUnderlined);
    }

    @Override
    public String toString() {
        return this.text + (this.isUnderlined ? "(underlined)" : "");
    }
}

When we do FormattedText(“cs2030”).toggleUnderline(), it becomes underlined. For plain text, when we toggleUnderline(), how do we make sure it doesn’t actually underline.

class PlainText extends FormattedText {
    
    PlainText(String text) {
        super(text, false);  // Always start NOT underlined
    }
    
    @Override
    FormattedText toggleUnderline() {
        // Return a new PlainText with the same text, still NOT underlined
        return new PlainText(this.text);
    }
}

Static vs Instance

Instance method belong to an object and can access its object’s fileds/date. Static methods belong to the class as a whole and cannot access instance-specific data


void

For methods that return nothing. More for side-effects methods.