State and Identity

A reference object has member variables that can be modified. Reference objects are also called stateful or mutable objects.

If an object has no member variables, or if the member variables can't be modified, then it is called a value or stateless object.

Example

A Point class such as one might find in a graphics library represents a point in some coordinate system:

class Point {
private:
   double xc, yc;
public:
   Point(double x, double y) {
      xc = x;
      yc = y;
   }
   double getXC() { return xc; }
   double getYC() { return yc; }
   bool operator==(const Point& other) const {
      return xc == other.xc && yc == other.yc;
   }
};

Note that once the member variables of a point have been initialized, there is no way to change them. Points are value objects. Changing one of the coordinates changes the identity of the point.

It doesn't matter if there are lots of instances of the Point class representing the point (3, 5). They can never get out of synch with each other.

However, it does become important to know when to objects represent the same point. For this reason we override the == operator.

Example

A bank account is a classic example of a reference object. Its state is its balance. The withdraw and deposit methods allow users to modify the balance. Methods that modify the state of an object are called mutators.

class Account {
private:
   double balance;
public:
   Account() { balance = 0; }
   void withdraw(double amt) {
       if (amt <= balance) balance -= amt;
   }
   void deposit(double amt) {
      balance += amt;
   }
   double getBalance() { return balance; }
};

Having multiple instances of the Account class represent the same bank account in the real world can lead to a disaster if the representatives disagree about the balance.

Example: Point in Java

class Point {
   private double xc, yc;
   public Point(double x, double y) {
      xc = x;
      yc = y;
   }
   public double getXC() { return xc; }
   public double getYC() { return yc; }
   public String toString() {
      return "(" + xc + ", " + yc + ")";
   }
   public boolean equals(Object other) {
      if (other == null) return false;
      Class otherClass = other.getClass();
      Class thisClass = this.getClass();
      if (!thisClass.equals(otherClass)) return false;
      Point otherPoint = (Point) other; // a safe cast
      return otherPoint.xc == this.xc && otherPoint.yc == this.yc;
   }
   public int hashCode() {
      return toString().hashCode();
   }
}

All classes in Java inherit the toString, equals, and hashCode methods from the Object super class. We must override all three methods in Point.

Notice that the equals method is more involved because it compares a point to any other object, not just other points. If the other object is not a Point, then equals returns false without throwing an exception.

The hashCode method is used to search hash tables. It must obey the rule:

if a.equals(b), then a.hashCode() == b.hashCode()

It is desirable for objects to have evenly distributed hash codes. We can accomplish both by reducing the hash code of a point to the hash code of the point's string representation.

Example: Point in C++

class Point
{
public:
   Point(double xc = 0, double yc = 0)
   {
      this->xc = xc;
      this->yc = yc;
   }
   double getXC() const { return xc; }
   double getYC() const { return yc; }
   string toString() const;
   bool equal(const Point& p) const
   {
      return xc == p.xc && yc == p.yc;
   }

private:
   double xc, yc;
};