Object-Oriented Programming II

Table of Contents

1 Reading

For a good overview of the OOP concepts we've covered as well as the Java syntax and terminology, read Algorithms 1.2. If you prefer (or want supplemental) textbook readings, I would recommend Defining Classes in Java from the Java for Python Programmers book (this covers material from Friday's topic as well).

2 Access Modifiers

  • Background: Java packages
    • A package is how Java groups together related classes
      • For example, the Java package java.lang includes various built-in classes such as String and Math
      • Classes from java.lang are always available (don't need to be imported), but classes from other packages like Scanner need to be imported in order to be used (import java.util.Scanner;)
        • Alternatively, you can write java.util.Scanner everywhere and forego the import statement (not recommended)
    • Packages, like classes, are also a layer of encapsulation, meaning they have the ability to hide implementation details
  • To support abstraction, Java provides a way to specify the access level of classes, methods, and fields
    • The access level determines who, if anyone, can access a particular method or field outside of the class that it's in
    • Similarly with who can access a class outside of its package
    • Access level is also sometimes referred to as visibility
    • These access modifiers are keywords that are used
  • For methods and fields:
    • public
      • Accessible to everyone with no restrictions
      • The author of a class controls how users of that class will interact with it by deciding what methods will be public
      • Fields should (almost never) be public, as this exposes the class' underlying implementation (thus breaking abstraction)
      • Since a class' main method is how outside users run the program associated with that class, it must always be public
    • private
      • Only accessible in the class where they are declared
      • Appropriate for fields and "helper" methods that support the functionality of public methods (but don't make sense to be called directly by code using the class)
    • We will likely only ever use public and private in this course, but there are two other access levels in between these two which I will include here for completeness
    • protected
      • Accessible to all classes declared in the same package and any extension to the class whether in the same package or not
    • default (i.e., what you get when no access modifier is given)
      • Accessible to all classes declared in the same package
      • Also called package-private
  • To control visibility of classes within a package:
    • public
      • A class declared as public is visible to everyone
    • default (i.e., what you get when no access modifier is given)
      • A class that isn't declared to be public is package-private, only accessible to classes declared in the same package

3 static

  • A field or method can be associated with a class or with an object
  • That is, it might depend on the state of a particular instance (associated with an object) or be independent of any particular instance (associated with a class)
  • Let's make this concrete with an example class representing a circle
public class Circle {
    private double radius;

    public Circle(double r) {
        radius = r;
    }

    public double getRadius() {
        return radius;
    }

    public double getArea() {
        return radius * 3.14159265;
    }
}
  • So far this is nothing new, the field radius and the two accessor methods all depend on a specific instance of Circle
    • These are called instance variables or instance methods
    • If we want the area of a circle a radius 10, we have to write
      Circle temp = new Circle(10);
      double area = temp.getArea();
      
  • Now let's expand Circle in two ways: make π a named variable we can reuse and create a method to compute the area of an arbitrary circle
public class Circle {
    private static double pi = 3.14159265;
    private double radius;

    public Circle(double r) {
        radius = r;
    }

    public double getRadius() {
        return radius;
    }

    public double getArea() {
        return radius * radius * pi;
    }

    public static double computeArea(double r) {
        return r * r * pi;
    }
}
  • The static keyword makes a field or method part of the class itself rather than a property of each class instance
  • This means we can invoke Circle.computeArea to get the area of a circle with a given radius without needing to create a Circle object:
double area = Circle.computeArea(10);
  • Our field pi must be static in order to be available to computeArea—otherwise computeArea would need to create a Circle object to access it!
  • One improvement we can make is to cause pi to be immutable (meaning its value can never change)
    • If our code is assuming pi will always have the correct value, we should construct our code in such a way to guarantee this assumption
    • We can declare a variable final to give it this immutable property—it is not possible to modify a final variable after it's initialized
      public static final double PI = 3.14159265
      
    • It's conventional to make the names of static immutable variables all caps (to set them apart as constants)
  • And now that a user of the Circle class can't modify PI, we can safely make it public

4 Interfaces

  • Interface
    • A contract or template for methods a class must provide
    • Intefaces don't provide any implementation themselves
    • Note how in the Shape interface below we use interface instead of class, and the method bodies are omitted
    import edu.princeton.cs.algs4.Point2D;
    
    public interface Shape {
        public Point2D getPosition();
    
        public void setPosition(Point2D pos);
    
        public double getArea();
    }
    
  • Class hierarchy
    • A set of relationships between classes and/or interfaces
    • extends relationship
      • When one definition extends another it inherits all public methods and data
      • The original is said to be the supertype and the extension the subtype
        • So in the example below, Polygon would be a subtype of Shape (and Shape would be a supertype of Polygon)
        • Every Polygon is a Shape, but not every Shape is a Polygon
        • The Polygon interface inherits all the methods from Shape, so we don't have to specify them
          import edu.princeton.cs.algs4.Point2D;
          
          public interface Polygon extends Shape {
              public Point2D[] getVertexes();
          }
          
    • implements relationship
      • We can make use of an interface by defining a class that implements it
      • This means the class must provide an implementation for every method specified in the interface (it won't compile otherwise)
      • This means, for example, that when a class implements Polygon, we know it has all the methods specified in Shape and Polygon
        import edu.princeton.cs.algs4.Point2D;
        import edu.princeton.cs.algs4.StdRandom;
        
        class Rectangle implements Polygon {
            // instance variables
            private double width;
            private double height;
            private Point2D position;
        
            // constructor
            Rectangle(double w, double h) {
                width = w;
                height = h;
                position = new Point2D(StdRandom.uniform(), StdRandom.uniform());
            }
        
            // accessor methods
            public double getWidth() {
                return width;
            }
        
            public double getHeight() {
                return height;
            }
        
            @Override
            public Point2D getPosition() {
                return position;
            }
        
            @Override
            public void setPosition(Point2D pos) {
                position = pos;
            }
        
            @Override
            public double getArea() {  // required by Shape interface
                return width * height;
            }
        
            @Override
            public Point2D[] getVertexes() {  // required by Polygon interface
                Point2D[] verts = new Point2D[4];
                // clockwise starting with upper left
                verts[0] = new Point2D(position.x() - width / 2, position.y() - height / 2);
                verts[1] = new Point2D(position.x() + width / 2, position.y() - height / 2);
                verts[2] = new Point2D(position.x() + width / 2, position.y() + height / 2);
                verts[3] = new Point2D(position.x() - width / 2, position.y() + height / 2);
                return verts;
            }
        }
        
  • A circle is a shape, but not a polygon, so adapting our Circle class from before to use an interface would meaning implementing Shape:
    public class Circle implements Shape {
        private static final double PI = 3.14159265;
        private double radius;
        private Point2D position;
    
        public Circle(double r) {
            radius = r;
            position = new Point2D(StdRandom.uniform(), StdRandom.uniform());
        }
    
        public double getRadius() {
            return radius;
        }
    
        @Override
        public Point2D getPosition() {
            return position;
        }
    
        @Override
        public void setPosition(Point2D pos) {
            position = pos;
        }
    
        @Override
        public double getArea() {
            return radius * radius * PI;
        }
    
        static double computeArea(double r) {
            return r * r * PI;
        }
    }
    

5 Practice Problems1

  1. Which of the following is the correct syntax to indicate that class A is a subclass of B?
    1. public class B extends A {
    2. public class A : super B {
    3. public A(super B) {
    4. public class A extends B {
    5. public A implements B {
  2. Consider the following class:
    // Represents a university student.
    public class Student {
        private String name;
        private int age;
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    Also consider the following partial implementation of a subclass of Student to represent undergraduate students at a university:

    public class UndergraduateStudent extends Student {
        private int year;
        ...
    }
    

    Can the code in the UndergraduateStudent class access the name and age fields it inherits from Student? Can it call the setAge method?

  3. Consider these two classes:
    public class Car {
        public void m1() {
            System.out.println("car 1");
        }
    
        public void m2() {
            System.out.println("car 2");
        }
    
        public String toString() {
            return "vroom";
        }
    }
    
    public class Truck extends Car {
        public void m1() {
            System.out.println("truck 1");
        }
    }
    

    What is printed as a result of this code?

    Car mycar = new Car();
    Truck mytruck = new Truck();
    
    System.out.println(mycar);
    mycar.m1();
    mycar.m2();
    System.out.println(mytruck);
    mytruck.m1();
    mytruck.m2();
    
  4. Imagine that you are going to write a program to play card games. Consider a design with a Card class and 52 subclasses, one for each of the unique playing cards (for example, NineOfSpades and JackOfClubs). Is this a good design? If so, why? If not, why not, and what might be a better design?
  5. What is the difference between implementing an interface and extending a class?
  6. What’s wrong with the code for the following interface? What should be changed to make a valid interface for objects that have a border color?
    public interface BorderColor {
        private Color borderColor;
        public Color getBorderColor() {
            return borderColor;
        }
    }
    

6 Extra

  • Demo compiler/runtime errors
    • code outside a class
    • wrong main signature
    • array index exception
    • syntax error
      • missing ;
      • missing "
      • missing )
      • missing operator
    • wrong variable name
    • forgot type on declaration
    • file name doesn't match class name
    • wrong parameters
    • forgot () on a method call
    • put () on a field access
    • divide by zero
    • null pointer exception
    • type mismatch
  • Flying shapes demo
    • Note that we can have an array of type Shape[] that stores both Circle objects and Rectangle objects. This is called polymorphism. Shape is a supertype of both Circle and Rectangle, and an object can always be treated as if they were an instance of their supertype. Since the Shape interface in the flying-shapes example defines a move method, we can call it on objects of type Shape.
    • Also note on line 58 of ShapeFuntimes.java how we use instanceof to check if a particular object is an instance of Polygon (or one of its subtypes, like Rectangle), and then cast the object to a Polygon when passing it to drawStreamers.
    • ShapeFuntimes.java also demonstrates the implementation of several static helper methods that are used in both Circle and Rectangle, as well as in the main method.

Footnotes:

1

Solutions:

  1. (4) public class A extends B {
  2. The name and age fields are decalred to be private, so the UndergraduateStudent class cannot access them. It can access the public setAge method.
  3. When an object is printed, it's toString method is automatically called to get the string to be printed. Truck inherits toString and m2 from Car and does not override either like it does m1. Thus, it simply uses the versions of toString and m2 defined in Car.
    vroom
    car 1
    car 2
    vroom
    truck 1
    car 2
    
  4. This is not a good design because it uses separate classes to represent something that is more appropriately data within the Card class (i.e., the suit and value of the card). See Bailey 7.3 (p. 155–160) for a better design.
  5. Extending a class causes your class to inherit all methods and data from that class. Implementing an interface forces you to write your own code to implement all the methods in that interface.
  6. What's wrong is that interfaces can't declare fields or write bodies for methods. The following is a correct BorderColor interface:
    public interface BorderColor {
        public Color getBorderColor();
    }