Example Notes

These notes were generated from a recording of Daniel Kluver's CSCI 1913 class held on November 3rd, 2021.

Download Raw Markdown

Post-Lecture Note Sheet

Overview

This lecture focused on object-oriented programming concepts, specifically the implementation of the equals method in Java, understanding how to design classes for inheritance, and using abstract classes. We explored practical implications with examples, particularly in a veterinary clinic context with a Cat class.

Equals Method in Java

Definition

The equals method is used to compare two objects to determine if they are logically equivalent. In Java, this method is inherited from the Object class and must be overridden in your class.

Implementation Steps

  • Method Signature

    • The method signature must be public boolean equals(Object obj). The parameter type must be Object, as this maintains the contract of the equals method inherited from Object.
  • Same Object Check

    • The first check should be to see if the current object is being compared to itself:
      if (this == obj) {
          return true;
      }
  • Null Check

    • Check if the provided object is null:
      if (obj == null) {
          return false;
      }
  • Type Check

    • Use the instanceof operator to verify that obj is of the correct type (e.g., Cat):
      if (!(obj instanceof Cat)) {
          return false;
      }
  • Type Casting

    • Cast the object to the correct type:
      Cat other = (Cat) obj;
  • Field Comparison

    • Compare relevant fields for equality (e.g., age, weight, name):
      return this.age == other.age && this.weight == other.weight && 
             this.name.equals(other.name) && this.owner.equals(other.owner);

Example

Here is an example implementation of the equals method in a Cat class:

public class Cat {
    private String name;
    private int age;
    private double weight;
    private String owner;
 
    public Cat(String name, int age, double weight, String owner) {
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.owner = owner;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || !(obj instanceof Cat)) {
            return false;
        }
        Cat other = (Cat) obj;
        return this.age == other.age &&
               Math.abs(this.weight - other.weight) < 0.01 &&
               this.name.equals(other.name) &&
               this.owner.equals(other.owner);
    }
 
    // Other methods like getters and setters...
}

Designing for Inheritance

Purpose of Inheritance

Inheritance allows a class (child or subclass) to inherit fields and methods from another class (parent or superclass). This promotes code reuse and establishes a relationship between classes.

Key Concepts

  • Child Class Responsibilities

    • A child class can inherit common properties from its parent class but also must implement or override its own behaviors where necessary.
  • Encapsulation in Inheritance

    • Use private fields in parent classes to protect the data and expose only what is necessary through public or protected methods.
  • Protected Methods

    • Defined as protected, these methods can be accessed and modified by subclasses, allowing them to customize functionality without compromising encapsulation.

Example Structure

Consider a Greeter class as a parent class that could have various types of greeters (e.g., casual, formal, or specific language types).

public abstract class Greeter {
    protected String name;
 
    public Greeter(String name) {
        this.name = name;
    }
 
    public abstract String greet();
}
 
public class CasualGreeter extends Greeter {
    public CasualGreeter(String name) {
        super(name);
    }
 
    @Override
    public String greet() {
        return "Hey, " + name + "!";
    }
}
 
public class FormalGreeter extends Greeter {
    public FormalGreeter(String name) {
        super(name);
    }
 
    @Override
    public String greet() {
        return "Good day, " + name + ".";
    }
}

Abstract Classes

Role and Characteristics

  • Purpose:

    • Abstract classes serve as templates that define attributes and methods that must be implemented by derived classes.
  • Defining Abstract Classes:

    • Use the abstract keyword in both the class declaration and for methods that do not have a body:
      public abstract class Shape {
          public abstract double area();
      }
  • Instantiation:

    • Abstract classes cannot be instantiated directly. You can create instances of subclasses that implement the abstract methods.
  • Method Implementation:

    • Subclasses must provide implementations for all abstract methods unless they themselves are abstract.

Example

Here's an abstract class representing a Shape with specific geometrical shapes extending it:

public abstract class Shape {
    public abstract double area();
}
 
public class Circle extends Shape {
    private double radius;
 
    public Circle(double radius) {
        this.radius = radius;
    }
 
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}
 
public class Rectangle extends Shape {
    private double width;
    private double height;
 
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
 
    @Override
    public double area() {
        return width * height;
    }
}

Conclusion

This lecture emphasized the importance of precise implementation when overriding methods in Java, especially the equals method, and the structuring of classes for efficient inheritance and polymorphism. Understanding these principles prepares you for crafting robust Java applications and future projects.

Next Steps

  • Complete your assignments reflecting these concepts.
  • Prepare for next lecture, which will focus on further examples and practical implementations related to the use of abstract classes and polymorphism in Java.