/*******************************************************************************
* Companion code for the book "Introduction to Software Design with Java",
* 2nd edition by Martin P. Robillard.
*
* Copyright (C) 2022 by Martin P. Robillard
*
* This code is licensed under a Creative Commons
* Attribution-NonCommercial-NoDerivatives 4.0 International License.
*
* See http://creativecommons.org/licenses/by-nc-nd/4.0/
*
*******************************************************************************/
package e2.chapter3;
import java.util.Comparator;
/**
* Implementation of a playing card. This class yields immutable objects.
* This version of the class also implements the Comparable interface and
* compares cards by rank, with an undefined order for cards of the same rank.
* The class also includes a static factory method to create Comparator
* objects that can compare cards according to their rank.
*/
public class Card implements {
private Rank aRank;
private Suit aSuit;
/**
* Creates a new card object.
*
* @param pRank The rank of the card.
* @param pSuit The suit of the card.
* @pre pRank != null
* @pre pSuit != null
*/
public Card(Rank pRank, Suit pSuit) {
assert pRank != null && pSuit != null;
aRank = pRank;
aSuit = pSuit;
}
/**
* @return The rank of the card.
*/
public Rank getRank() {
return aRank;
}
/**
* @return The suit of the card.
*/
public Suit getSuit() {
return aSuit;
}
@Override
public int compareTo(Card pCard) {
return aRank.compareTo(pCard.aRank);
}
/**
* Sample static factory method to create a comparator capable
* of comparing cards by rank.
*
* @return The created comparator.
*/
public static Comparator<Card> createByRankComparator() {
return new Comparator<Card>() {
@Override
public int compare(Card pCard1, Card pCard2) {
return pCard1.aRank.compareTo(pCard2.aRank);
}
};
}
}
Collections.sort
or Arrays.sort
) to allow precise control over the sort order.
Comparators can also be used to control the order of certain data
structures (such as sorted sets or
sorted maps), or to provide an ordering for
collections of objects that don't have a natural ordering.Collections.sort
or Arrays.sort
) to allow precise control over the sort order.
Comparators can also be used to control the order of certain data
structures (such as sorted sets or
sorted maps), or to provide an ordering for
collections of objects that don't have a natural ordering.
The ordering imposed by a comparator c
on a set of elements
S
is said to be consistent with equals if and only if
c.compare(e1, e2)==0
has the same boolean value as
e1.equals(e2)
for every e1
and e2
in
S
.
Caution should be exercised when using a comparator capable of imposing an
ordering inconsistent with equals to order a sorted set (or sorted map).
Suppose a sorted set (or sorted map) with an explicit comparator c
is used with elements (or keys) drawn from a set S
. If the
ordering imposed by c
on S
is inconsistent with equals,
the sorted set (or sorted map) will behave "strangely." In particular the
sorted set (or sorted map) will violate the general contract for set (or
map), which is defined in terms of equals
.
For example, suppose one adds two elements a
and b
such that
(a.equals(b) && c.compare(a, b) != 0)
to an empty TreeSet
with comparator c
.
The second add
operation will return
true (and the size of the tree set will increase) because a
and
b
are not equivalent from the tree set's perspective, even though
this is contrary to the specification of the
Set.add
method.
Note: It is generally a good idea for comparators to also implement
java.io.Serializable
, as they may be used as ordering methods in
serializable data structures (like TreeSet
, TreeMap
). In
order for the data structure to serialize successfully, the comparator (if
provided) must implement Serializable
.
For the mathematically inclined, the relation that defines the
imposed ordering that a given comparator c
imposes on a
given set of objects S
is:
{(x, y) such that c.compare(x, y) <= 0}.The quotient for this total order is:
{(x, y) such that c.compare(x, y) == 0}.It follows immediately from the contract for
compare
that the
quotient is an equivalence relation on S
, and that the
imposed ordering is a total order on S
. When we say that
the ordering imposed by c
on S
is consistent with
equals, we mean that the quotient for the ordering is the equivalence
relation defined by the objects' equals(Object)
method(s):{(x, y) such that x.equals(y)}.In other words, when the imposed ordering is consistent with equals, the equivalence classes defined by the equivalence relation of the
equals
method and the equivalence classes defined by
the quotient of the compare
method are the same.
Unlike Comparable
, a comparator may optionally permit
comparison of null arguments, while maintaining the requirements for
an equivalence relation.
This interface is a member of the Java Collections Framework.
An implicit declaration is one that creates a relation between two code elements, without being visible in the code. Other examples of implicit declarations in Java include:
Object
.public
, even if the public
keyword is not provided.An implicit declaration is one that creates a relation between two code elements, without being visible in the code. Other examples of implicit declarations in Java include:
Object
.public
, even if the public
keyword is not provided.Chapter 2, insight #1
Use classes to define how domain concepts are represented in code, as opposed to encoding instances of these concepts as values of primitive types (an antipattern called Primitive Obsession)
Chapter 2, insight #1
Use classes to define how domain concepts are represented in code, as opposed to encoding instances of these concepts as values of primitive types (an antipattern called Primitive Obsession)
This statement both declares an anonymous class and
creates an object of this class at the same time, then returns it. The name
that follows the new
keyword refers to an interface. However, the block that
follows the interface provides the implementation of this interface (the declaration
of method compare
). The result is an object of an anonymous class, whose implementation of
method compare
compares cards by rank.
This statement both declares an anonymous class and
creates an object of this class at the same time, then returns it. The name
that follows the new
keyword refers to an interface. However, the block that
follows the interface provides the implementation of this interface (the declaration
of method compare
). The result is an object of an anonymous class, whose implementation of
method compare
compares cards by rank.
We declare this class to implement Comparable
so that we can
use methods that rely on this comparable behavior from their input objects. An
example of such a method is Collections.sort
.
Chapter 3, insight #4
Use library interface types, such as Comparable
, to implement commonly expected behavior
compareTo
method is referred to as
its natural comparison method.We declare this class to implement Comparable
so that we can
use methods that rely on this comparable behavior from their input objects. An
example of such a method is Collections.sort
.
Chapter 3, insight #4
Use library interface types, such as Comparable
, to implement commonly expected behavior
compareTo
method is referred to as
its natural comparison method.
Lists (and arrays) of objects that implement this interface can be sorted
automatically by Collections.sort
(and
Arrays.sort
). Objects that implement this
interface can be used as keys in a sorted map or as
elements in a sorted set, without the need to
specify a comparator.
The natural ordering for a class C
is said to be consistent
with equals if and only if e1.compareTo(e2) == 0
has
the same boolean value as e1.equals(e2)
for every
e1
and e2
of class C
. Note that null
is not an instance of any class, and e.compareTo(null)
should
throw a NullPointerException
even though e.equals(null)
returns false
.
It is strongly recommended (though not required) that natural orderings be
consistent with equals. This is so because sorted sets (and sorted maps)
without explicit comparators behave "strangely" when they are used with
elements (or keys) whose natural ordering is inconsistent with equals. In
particular, such a sorted set (or sorted map) violates the general contract
for set (or map), which is defined in terms of the equals
method.
For example, if one adds two keys a
and b
such that
(!a.equals(b) && a.compareTo(b) == 0)
to a sorted
set that does not use an explicit comparator, the second add
operation returns false (and the size of the sorted set does not increase)
because a
and b
are equivalent from the sorted set's
perspective.
Virtually all Java core classes that implement Comparable
have natural orderings that are consistent with equals. One
exception is BigDecimal
, whose natural ordering equates
BigDecimal
objects with equal numerical values and different
representations (such as 4.0 and 4.00). For BigDecimal.equals()
to return true,
the representation and numerical value of the two
BigDecimal
objects must be the same.
For the mathematically inclined, the relation that defines the natural ordering on a given class C is:
{(x, y) such that x.compareTo(y) <= 0}.
The quotient for this total order is:
{(x, y) such that x.compareTo(y) == 0}.
It follows immediately from the contract for compareTo
that the
quotient is an equivalence relation on C
, and that the
natural ordering is a total order on C
. When we say that a
class's natural ordering is consistent with equals, we mean that the
quotient for the natural ordering is the equivalence relation defined by
the class's equals(Object)
method:{(x, y) such that x.equals(y)}.
In other words, when a class's natural ordering is consistent with
equals, the equivalence classes defined by the equivalence relation
of the equals
method and the equivalence classes defined by
the quotient of the compareTo
method are the same.
This interface is a member of the Java Collections Framework.
Observe how the implementation of this method (Card#compare
) relies
on the comparable behavior of the aRank
field. In Java, all enumerated
types implement Comparable
.
This implementation is incomplete because it compares cards by rank only, which creates an unspecified order when comparing two cards with the same rank (for example, a two of clubs with a two of diamonds.
Observe how the implementation of this method (Card#compare
) relies
on the comparable behavior of the aRank
field. In Java, all enumerated
types implement Comparable
.
This implementation is incomplete because it compares cards by rank only, which creates an unspecified order when comparing two cards with the same rank (for example, a two of clubs with a two of diamonds.