Skip to main content

cs2381 Notes: 04 Instance Methods

·4 mins

Let’s write a static method that calculates the area of a square.

I’m going to ignore the design recipe for a moment for speed.

// Calculate area of square.
static double area(double width) {
    return width * width;
}

Next, let’s write a method that calculates the area of a circle. Note that the Math class is available by default.

// Calculate area of circle.
static double area(double radius) {
    return Math.PI * Math.pow(radius, 2.0);
}

Problem: Can’t have two methods with the same name and the same argument types.

Digression: We can have two methods with different arguments; that’s called method overloading.

// Calculate area of rectangle.
static double area(double width, double length) {
    return width * length;
}

Solutions:

  • Give the methods different names (rectangleArea, circleArea)
  • Put the static methods in different classes (Rectangle.area, Circle.area)
    • Some languages make this the normal solution, usually called “modules”.
  • Have Rectangle and Circle objects with instance methods.
class Square {
    final double width;
    
    Square(double width) {
       this.width = width; 
    }

    double area() {
        return width * width; 
    }
}

class Circle {
    final double radius;
    
    Circle(double radius) {
       this.radius = radius; 
    }

    double area() {
        return Math.PI * Math.pow(radius, 2.0);
    }
}

Why would we want this?

  • The language will remember what our numbers represent (square or circle) and we structrually can’t call the wrong area function by mistake.
  • We get to use the same short name for both methods without any interferance.
from math import pi

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return pi * pow(self.radius, 2)

class Rectangle:
    def __init__(self, width):
        self.width = width

    def area(self):
        return self.width * self.width

shapes = [Circle(2), Rectangle(2)]

for sh in shapes:
    print("area =", sh.area())
  • This called “duck typing”. If it looks like a duck, and quacks like a duck, and has method called “quack”, we can call that method.
  • This works in a dynamic language like Python, but not in a static language like Java.
import java.util.List;

public class App {
    public static void main(String[] args) {
        Circle ci = new Circle(2);
        Square sq = new Square(2);

        List<Circle> shapes = List.of(ci, sq);
        // List shapes = List.of(ci, sq);

        for(var sh : shapes) {
            System.out.println("area = " + sh.area());
        }
    }
}
  • Doesn’t work, because Java’s Object class doesn’t have an area method and we can’t name the type of the items in the list.
  • We want “polymorphism”, where we can do the same thing to objects of different types. And Java will do that if we ask correctly.

Solution 1: Inheritence

class Shape {
    double area() { return 5.0; }
}

//abstract class Shape {
//    abstract double area();
//}

class Square extends Shape ...

class Circle extends Shape ...

main() {
        List<Shape> shapes = List.of(ci, sq);
  • All objects of type Shape have an area method that returns a double.
  • Square and Circle are both subclasses of Shape (all Circles are Shapes)
  • our List is a list of Shapes

This is the most traditional approach, but it has some drawbacks:

  • In Java, each class can only have one superclass.
  • The “is a” relationship can be tricky. Sure, Circle is a Shape, but is a Horse a Vehicle?
  • Records can’t extend any custom class.

A similar and more recent concept in Java is “interfaces”.

interface Shape {
    double area();
}

class Square implements Shape {
    public double area() { ...

class Circle implements Shape {
    public double area() { ...

main() {
        List<Shape> shapes = List.of(ci, sq);
  • A class can implement more than one interface.
  • No “is a” relationship is implied. Only that this object provides a certain interface (or collection of methods)
  • This is closer to “duck typing”; we could have a Ducklike interface that includes a quack method.
  • Records can implement interfaces.

Summary, for now:

  • Polymorphism is the biggest functional benefit we get out of instance methods, although namespacing is pretty nice too.
  • We’re going to prefer interfaces, mostly because we use a lot of records.
  • Functionally, these concepts are pretty close.

Design method for a record:

  • Let’s design through a Rectangle record with an area method, using the design method page on the web site.
  • Then let’s have it implement Shape.