# Liskov, Equality, OO, and Math

Months ago, I saw a code sample of an equals(…) method implementation (in Java, if memory serves). It looked a bit like this:

@Override

public boolean equals(Object o) {

// self check

if (this == o)

return true;

// null check

if (o == null)

return false;

// type check and cast

if (getClass() != o.getClass())

return false;

K other = (K) o;

if(

Object.equals(field1, other.field1)

&& Object.equals(field2, other.field2)

&& Object.equals(field3, other.field3)

){

return true;

}

return false;

}

There was some concern about checking that the two instances’ classes were equal; that is, verifying that both objects were instances of the *exact same concrete class*. The question that was raised from this was:

**Is checking the concrete types for equality a violation of the Liskov Substitution Principle?**

There are a few concepts that we need to understand if we can unpack this.

#### The Mathematics of Equality

In mathematics, two numbers are considered equal if they are… umm…

the same.

Profound, huh? For numbers, that makes a lot of sense. They’re equal if and only if they are *the same dang number*. When you consider programming languages like F#, Scala, or Clojure, where you can leverage types with structural equality, the concept of equality still holds true if the *structure *of both objects and the *values* in each are identical. However, these languages’ types that have structural equality semantics (for good reason) don’t support inheritance. The semantics of equality in these situations only care about two things:

- Are the
*structures*equal? - Are the
*values*equal?

If the answer to both questions is “yes,” then the things are equal. Types really don’t matter much here. What matters is the pure mathematical are-these-things-representing-the-same-value equality semantics.

#### Equivalence Relation

Those equality semantics, by the way, are a special case of a mathematical thing called an *equivalence relation*. According to Wikipedia, an equivalence relation is:

[…] a binary relation that is at the same time a reflexive relation, a symmetric relation and a transitive relation. (ref)

So what does that mean? Well, a **binary relation** is a relation between two things, which is to say it says whether or not two things *in a particular order* are related to each other. The order is important for binary relations, because things like “is less than” and “is divisible by” are binary relations. 5 is less than 8, but 8 is *not* less than 5. Order matters. Furthermore, the idea of a binary relation is a relation between two members of a *set* (we’ll talk more about sets later).

A **reflexive relation **is a binary relation where the relation holds true when the same value is on either side of the relation. A relation like “is divisible by” is reflexive, because a number is always divisible by itself. A relation like “is less than” is not reflexive because a number is not less than itself.

A **symmetric relation** is one that continues to hold true when the values are reversed *for any pair of values*. The “is divisible by” relation is *not *symmetric, because you can find values where the reverse isn’t true, like 6 and 3. The number 6 is divisible by 3, but 3 is not divisible by 6. However, relations like “have the same value modulo 3” are symmetric.

And finally, a **transitive relation **is a relation such that if the relation holds true for (a, b) and (b, c), then it must also hold true for (a, c). Relations like this include “is less than”, “is greater than”, and “is divisible by”. However, a relation like “is perpendicular to” on lines isn’t transitive. For instance, consider the following diagram. Line AB is perpendicular to line BC, and line BC is perpendicular to CD, but line AB is **not** perpendicular to line CD. Therefore, “is perpendicular to” is not transitive.

The notion of mathematical equality is a *special case *of an equivalence relation; that is, equality is *even stricter* than being reflexive, symmetric, and transitive.

#### Equivalence Relations and Liskov

So what does this mean for an `equals(...)`

operator in our language of choice? When you consider object-oriented languages like C#, Java, or Python, then this notion of “equality” can be fiddled with. You can define your own rules for equality. You have the flexibility to break the rules and make the `equals(...)`

method something other than strict mathematical equality. Now there are (arguably) good reasons to do so; for instance, maybe you are using an ORM where multiple copies of an object of the same type with the same primary key value could be around, and you want to see if these two different references to two different objects in memory are representing the same entity. In this case, “equals” means “has the same type and the same primary key”. Django does this, for instance. However, Django’s implementation still satisfies those three properties of an equivalence relation.

You’re free to define an `equals(...)`

method that breaks one or more of those properties of an equivalence relation, and when you do, Bad Things™ can, of course, happen. Breaking those rules makes your objects no longer capable of behaving properly as hashtable keys, for instance.

#### But What About Subclasses?

As I said before, equivalence relations pertain to members of a *set*. A set, mathematically speaking, is a well-defined collection of distinct objects. When you consider an `equals(...)`

implementation for any given class in an object-oriented language, it is *impossible* to ensure proper mathematical rigor within that `equals(...)`

method and account for the potential for yet-to-be-defined subclasses of that object. Simply put, when you create a subclass, you’re altering the contents of the set that the base class’s `equals(...)`

implementation is based on. The set is no longer well-defined.

This forces those implementations to check for type equality against the concrete types. Does this mean a violation of the Liskov substitution principle? I don’t think so. You can’t define a binary relation without a set, and a set is well-defined. By introducing the possibility of inheritance, you’re taking away the well-definedness of the set. By defining `equals(...)`

in terms of a known set, it preserves its mathematical soundness. If the expectation is that “equals” is the mathematical equivalence relation, then there is no choice than to check the concrete types.

The trouble is that leaving the same `equals(...)`

implementation with a subclass could potentially break the reflexive, symmetric, or transitive properties of that `equals(...)`

method. This is why languages with structural equality semantics don’t allow subclassing those structural types. By allowing subclasses of those structural types, it harms the semantics of the equality relation.

#### Equality and OO

Simply put, the idea of equality in an object-oriented context can often be a real mess. Consider avoiding customizing the `equals(...)`

behavior, and use a different way to compare objects for “equality”. The Liskov substitution principle misleads this conversation because for many OO languages, you’re forced to expose an `equals(...)`

method on your objects by inheritance. Be intentional about whether or not your classes need to be compared for equality by any other than the default technique (usually, reference equality). My typical rules are:

- Avoid using the
`equals(...)`

method as a part of your classes’ semantics if possible; i.e. be willing to say “these classes aren’t meant to be compared for equality via the`equals(...)`

method.” - When forced to override
`equals(...)`

, limit those cases, if possible, to value objects and not identity objects. Prevent subclasses (e.g.`final`

or`sealed`

) so that the equality semantics can be preserved: don’t let the set become ill-defined. - Consider using an external comparison operation to test two instances of a class for their relationship. Often, “equals” is better expressed as “has the same ID” or “refers to the same absolute path.” Make those relationships explicit rather than hiding those subtleties behind the
`equals(...)`

method. - Don’t try to be clever with
`equals(...)`

.