/*******************************************************************************
* 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.chapter9;
import static java.util.Comparator.*;
import java.util.Comparator;
import java.util.List;
import e2.chapter9.Suit.Color;
/**
* Implementation of a playing card with factory functions
* to create Comparators using functional-style programming.
*/
public class Card implements Comparable<Card> {
private static final Comparator<Card> = Comparator.(Card::getSuit).(Card::getRank);
// Flyweight store
private static final Card[][] CARDS = new Card[Suit.values().length][Rank.values().length];
private final Rank aRank;
private final Suit aSuit;
// Initialization of the flyweight store
static {
for( Suit suit : Suit.values() ) {
for( Rank rank : Rank.values() ) {
CARDS[suit.ordinal()][rank.ordinal()] = new Card(rank, suit);
}
}
}
// Private constructor
private Card( Rank pRank, Suit pSuit) {
aRank = pRank;
aSuit = pSuit;
}
/**
* @param pRank The rank of the requested card.
* @param pSuit The suit of the requested card.
* @return The unique Card instance with pRank and pSuit
* @pre pRank != null && pSuit != null
*/
public static Card get(Rank pRank, Suit pSuit) {
assert pRank != null && pSuit != null;
return CARDS[pSuit.ordinal()][pRank.ordinal()];
}
/**
* @return The rank of the card.
*/
public Rank getRank() {
return aRank;
}
/**
* @return The suit of the card.
*/
public Suit getSuit() {
return aSuit;
}
@Override
public String toString() {
return String.format("%s of %s", aRank, aSuit);
}
public static int compareByRank(Card pCard1, Card pCard2) {
return pCard1.getRank().compareTo(pCard2.getRank());
}
public boolean hasBlackSuit() {
return aSuit.getColor() == Color.BLACK;
}
public boolean hasRedSuit() {
return aSuit.getColor() == Color.RED;
}
// ********** Code samples for Section 9.3 **********
public static Comparator<Card> bySuitComparator() {
return (card1, card2) -> card1.getSuit().compareTo(card2.getSuit());
}
public static Comparator<Card> byRankComparator() {
return (card1, card2) -> card1.getRank().compareTo(card2.getRank());
}
/*
* This version uses basic comparison logic
*/
public static Comparator<Card> bySuitThenRankComparator1() {
return (card1, card2) -> {
if (card1.getSuit() == card2.getSuit()) {
return card1.getRank().compareTo(card2.getRank());
}
else {
return card1.getSuit().compareTo(card2.getSuit());
}
};
}
/* This version uses comparator factories */
public static Comparator<Card> bySuitThenRankComparator2() {
return (card1, card2) -> {
if(bySuitComparator().compare(card1, card2) == 0) {
return byRankComparator().compare(card1, card2);
}
else {
return bySuitComparator().compare(card1, card2);
}
};
}
public static Comparator<Card> byRankThenSuitComparator() {
return (card1, card2) -> {
if (byRankComparator().compare(card1, card2) == 0) {
return bySuitComparator().compare(card1, card2);
}
else {
return byRankComparator().compare(card1, card2);
}
};
}
/*
* This version uses the comparing method with a lambda.
*/
public static Comparator<Card> byRankComparator2() {
return Comparator.comparing();
}
/*
* Uses comparator factories combined with thenComparing
*/
public static Comparator<Card> byRankThenSuitComparator2() {
return byRankComparator().thenComparing(bySuitComparator());
}
/*
* Uses comparator factories combined with thenComparing
*/
public static Comparator<Card> bySuitThenRankComparator3() {
return bySuitComparator().thenComparing(byRankComparator());
}
public static Comparator<Card> byRankComparatorReversed() {
return (card1, card2) -> card2.getRank().compareTo(card1.getRank());
}
public static Comparator<Card> bySuitReversedThenRankComparator() {
return bySuitComparator().reversed().thenComparing(byRankComparator());
}
public static Comparator<Card> bySuitReversedThenRankReversedComparator() {
return bySuitComparator().reversed().thenComparing(byRankComparator().reversed());
}
public static void sampleSortingApplication1() {
List<Card> cards = new Deck().getCards();
cards.sort(
Comparator
.((Card card) -> card.getSuit())
.reversed()
.thenComparing(Comparator.comparing((Card card) -> card.getRank())
.reversed()));
}
public static void sampleSortingApplication2() {
List<Card> cards = new Deck().getCards();
cards.sort(comparing((Card card) -> card.getSuit())
.reversed()
.thenComparing(comparing((Card card) -> card.getRank())
.reversed()));
}
public static void sampleSortingApplication3() {
List<Card> cards = new Deck().getCards();
cards.sort(comparing(Card::getSuit)
.reversed()
.thenComparing(comparing(Card::getRank)
.reversed()));
}
public static void sampleSortingApplication4() {
List<Card> cards = new Deck().getCards();
cards.sort(comparing(Card::getSuit)
.thenComparing(Card::getRank).reversed());
}
public boolean isFaceCard() {
return getRank().ordinal() >= Rank.JACK.ordinal();
}
@Override
public int compareTo(Card o) {
return COMPARATOR.compare(this, o);
}
/**
* @return A random card.
*/
public static Card random() {
return new Deck().draw();
}
}
Comparator
. The sort is stable: this method must not
reorder equal elements.
Comparator
. The sort is stable: this method must not
reorder equal elements.
All elements in this list must be mutually comparable using the
specified comparator (that is, c.compare(e1, e2)
must not throw
a ClassCastException
for any elements e1
and e2
in the list).
If the specified comparator is null
then all elements in this
list must implement the Comparable
interface and the elements'
natural ordering should be used.
This list must be modifiable, but need not be resizable.
The implementation takes equal advantage of ascending and descending order in its input array, and can take advantage of ascending and descending order in different parts of the same input array. It is well-suited to merging two or more sorted arrays: simply concatenate the arrays and sort the resulting array.
The implementation was adapted from Tim Peters's list sort for Python ( TimSort). It uses techniques from Peter McIlroy's "Optimistic Sorting and Information Theoretic Complexity", in Proceedings of the Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474, January 1993.
c
- the Comparator
used to compare list elements.
A null
value indicates that the elements'
natural ordering should be usedClassCastException
- if the list contains elements that are not
mutually comparable using the specified comparatorUnsupportedOperationException
- if the list's list-iterator does
not support the set
operationIllegalArgumentException
- (optional)
if the comparator is found to violate the Comparator
contract
The implementor must ensure that signum
(compare(x, y)) == -signum(compare(y, x))
for
all x
and y
. (This implies that
compare(x, y)
must throw an exception if and only if
compare(y, x)
throws an exception.)
The implementor must also ensure that the relation is transitive:
((compare(x, y)>0) && (compare(y, z)>0))
implies
compare(x, z)>0
.
Finally, the implementor must ensure that compare(x,
y)==0
implies that signum(compare(x,
z))==signum(compare(y, z))
for all z
.
(compare(x, y)==0) == (x.equals(y))
. Generally speaking,
any comparator that violates this condition should clearly indicate
this fact. The recommended language is "Note: this comparator
imposes orderings that are inconsistent with equals."o1
- the first object to be compared.o2
- the second object to be compared.NullPointerException
- if an argument is null and this
comparator does not permit null argumentsClassCastException
- if the arguments' types prevent them from
being compared by this comparator.String
class represents character strings. All
string literals in Java programs, such as "abc"
, are
implemented as instances of this class.
String
class represents character strings. All
string literals in Java programs, such as "abc"
, are
implemented as instances of this class.
Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared. For example:
String str = "abc";
is equivalent to:
char data[] = {'a', 'b', 'c'}; String str = new String(data);
Here are some more examples of how strings can be used:
System.out.println("abc"); String cde = "cde"; System.out.println("abc" + cde); String c = "abc".substring(2, 3); String d = cde.substring(1, 2);
The class String
includes methods for examining
individual characters of the sequence, for comparing strings, for
searching strings, for extracting substrings, and for creating a
copy of a string with all characters translated to uppercase or to
lowercase. Case mapping is based on the Unicode Standard version
specified by the Character
class.
The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. For additional information on string concatenation and conversion, see The Java Language Specification.
Unless otherwise noted, passing a null
argument to a constructor
or method in this class will cause a NullPointerException
to be
thrown.
A String
represents a string in the UTF-16 format
in which supplementary characters are represented by surrogate
pairs (see the section Unicode
Character Representations in the Character
class for
more information).
Index values refer to char
code units, so a supplementary
character uses two positions in a String
.
The String
class provides methods for dealing with
Unicode code points (i.e., characters), in addition to those for
dealing with Unicode code units (i.e., char
values).
Unless otherwise noted, methods for comparing Strings do not take locale
into account. The Collator
class provides methods for
finer-grain, locale-sensitive String comparison.
javac
compiler
may implement the operator with StringBuffer
, StringBuilder
,
or java.lang.invoke.StringConcatFactory
depending on the JDK version. The
implementation of string conversion is typically through the method toString
,
defined by Object
and inherited by all classes in Java.compareTo
in interface Comparable<E extends Enum<E>>
o
- the object to be compared.EnumSet
and EnumMap
.Comparator
considers two elements equal, i.e.
compare(a, b) == 0
, other
is used to determine the order.
Comparator
considers two elements equal, i.e.
compare(a, b) == 0
, other
is used to determine the order.
The returned comparator is serializable if the specified comparator is also serializable.
String
based on the length
and then case-insensitive natural ordering, the comparator can be
composed using following code,
Comparator<String> cmp = Comparator.comparingInt(String::length)
.thenComparing(String.CASE_INSENSITIVE_ORDER);
other
- the other comparator to be used when this comparator
compares two objects that are equal.NullPointerException
- if the argument is null. The locale always used is the one returned by Locale.getDefault(Locale.Category)
with
FORMAT
category specified.
format
- A format stringargs
- Arguments referenced by the format specifiers in the format
string. If there are more arguments than format specifiers, the
extra arguments are ignored. The number of arguments is
variable and may be zero. The maximum number of arguments is
limited by the maximum dimension of a Java array as defined by
The Java Virtual Machine Specification.
The behaviour on a
null
argument depends on the conversion.IllegalFormatException
- If a format string contains an illegal syntax, a format
specifier that is incompatible with the given arguments,
insufficient arguments given the format string, or other
illegal conditions. For specification of all possible
formatting errors, see the Details section of the
formatter class specification.Comparable
sort key from a type T
, and returns a
Comparator<T>
that compares by that sort key.
Comparable
sort key from a type T
, and returns a
Comparator<T>
that compares by that sort key.
The returned comparator is serializable if the specified function is also serializable.
Comparator
that compares
Person
objects by their last name,
Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
T
- the type of element to be comparedU
- the type of the Comparable
sort keykeyExtractor
- the function used to extract the Comparable
sort keyNullPointerException
- if the argument is nullCollections.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.
compareTo
method is referred to as
its natural comparison method.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.
This code illustrates how lambda expression can be arbitrarily long. However, this is not necessarily a best practice, as much of the declarative nature of functional-style programming is lost.
This code illustrates how lambda expression can be arbitrarily long. However, this is not necessarily a best practice, as much of the declarative nature of functional-style programming is lost.
This implementation of a comparatory factory resuses a factory for a simpler comparator as a building block. However, not much is gained with this approach as the code is still predominantly imperative in style.
This implementation of a comparatory factory resuses a factory for a simpler comparator as a building block. However, not much is gained with this approach as the code is still predominantly imperative in style.
This method returns a comparator object. The behavior of
this object is defined using a lambda expression. Notice how the functional type of this
lambda expression is (Card, Card) -> int
, and thereby compatible
with the functional type of the abstract method of Comparator<Card>
.
This method returns a comparator object. The behavior of
this object is defined using a lambda expression. Notice how the functional type of this
lambda expression is (Card, Card) -> int
, and thereby compatible
with the functional type of the abstract method of Comparator<Card>
.
This method returns a comparator object. The behavior of
this object is defined using a lambda expression. Notice how the functional type of this
lambda expression is (Card, Card) -> int
, and thereby compatible
with the functional type of the abstract method of Comparator<Card>
.
This method returns a comparator object. The behavior of
this object is defined using a lambda expression. Notice how the functional type of this
lambda expression is (Card, Card) -> int
, and thereby compatible
with the functional type of the abstract method of Comparator<Card>
.
We define this helper method to have an implementation of a
card comparison strategy that we can publicly refer to using
a method reference. Notice how the functional type of this
method is (Card, Card) -> int
, and thereby compatible
with the functional type of the abstract method of Comparator<Card>
.
We define this helper method to have an implementation of a
card comparison strategy that we can publicly refer to using
a method reference. Notice how the functional type of this
method is (Card, Card) -> int
, and thereby compatible
with the functional type of the abstract method of Comparator<Card>
.
We create a constant comparator object using functional-style design, for easy reference to a complex comparison behavior. This comparator is our main comparator for cards: it compares cards by suit, then uses ranks to break the ties.
We create a constant comparator object using functional-style design, for easy reference to a complex comparison behavior. This comparator is our main comparator for cards: it compares cards by suit, then uses ranks to break the ties.
Here the comparison order is done within the comparison statement, by flipping the order of the arguments. This approach is limited in that it hides the intent of the code, and could look like a bug if it were not placed in a method with an explicit name.
Here the comparison order is done within the comparison statement, by flipping the order of the arguments. This approach is limited in that it hides the intent of the code, and could look like a bug if it were not placed in a method with an explicit name.
Unlike sets, lists typically allow duplicate elements. More formally,
lists typically allow pairs of elements e1
and e2
such that e1.equals(e2)
, and they typically allow multiple
null elements if they allow null elements at all. It is not inconceivable
that someone might wish to implement a list that prohibits duplicates, by
throwing runtime exceptions when the user attempts to insert them, but we
expect this usage to be rare.
The List
interface places additional stipulations, beyond those
specified in the Collection
interface, on the contracts of the
iterator
, add
, remove
, equals
, and
hashCode
methods. Declarations for other inherited methods are
also included here for convenience.
The List
interface provides four methods for positional (indexed)
access to list elements. Lists (like Java arrays) are zero based. Note
that these operations may execute in time proportional to the index value
for some implementations (the LinkedList
class, for
example). Thus, iterating over the elements in a list is typically
preferable to indexing through it if the caller does not know the
implementation.
The List
interface provides a special iterator, called a
ListIterator
, that allows element insertion and replacement, and
bidirectional access in addition to the normal operations that the
Iterator
interface provides. A method is provided to obtain a
list iterator that starts at a specified position in the list.
The List
interface provides two methods to search for a specified
object. From a performance standpoint, these methods should be used with
caution. In many implementations they will perform costly linear
searches.
The List
interface provides two methods to efficiently insert and
remove multiple elements at an arbitrary point in the list.
Note: While it is permissible for lists to contain themselves as elements,
extreme caution is advised: the equals
and hashCode
methods are no longer well defined on such a list.
Some list implementations have restrictions on the elements that
they may contain. For example, some implementations prohibit null elements,
and some have restrictions on the types of their elements. Attempting to
add an ineligible element throws an unchecked exception, typically
NullPointerException
or ClassCastException
. Attempting
to query the presence of an ineligible element may throw an exception,
or it may simply return false; some implementations will exhibit the former
behavior and some will exhibit the latter. More generally, attempting an
operation on an ineligible element whose completion would not result in
the insertion of an ineligible element into the list may throw an
exception or it may succeed, at the option of the implementation.
Such exceptions are marked as "optional" in the specification for this
interface.
The List.of
and
List.copyOf
static factory methods
provide a convenient way to create unmodifiable lists. The List
instances created by these methods have the following characteristics:
UnsupportedOperationException
to be thrown.
However, if the contained elements are themselves mutable,
this may cause the List's contents to appear to change.
null
elements. Attempts to create them with
null
elements result in NullPointerException
.
subList
views implement the
RandomAccess
interface.
This interface is a member of the Java Collections Framework.
Using reversed
, an instance factory method, makes the intent of the code explicit.
Using reversed
, an instance factory method, makes the intent of the code explicit.
This lambda expression can easily be replaced with a
method reference: Card::getRank
.
This lambda expression can easily be replaced with a
method reference: Card::getRank
.
Create a new comparator that sorts card by suit in the order defined by the enumerated type: from CLUBS to HEARTS.
Comparable
sort key from a type T
, and returns a
Comparator<T>
that compares by that sort key.
Create a new comparator that sorts card by suit in the order defined by the enumerated type: from CLUBS to HEARTS.
Comparable
sort key from a type T
, and returns a
Comparator<T>
that compares by that sort key.
The returned comparator is serializable if the specified function is also serializable.
Comparator
that compares
Person
objects by their last name,
Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
T
- the type of element to be comparedU
- the type of the Comparable
sort keykeyExtractor
- the function used to extract the Comparable
sort keyNullPointerException
- if the argument is nullThis is another comparator factory. However, this method is not static: instead, it derives a comparator from another comparator instance, but also using a key extractor.
Comparator
considers two elements equal, i.e.
compare(a, b) == 0
, other
is used to determine the order.
This is another comparator factory. However, this method is not static: instead, it derives a comparator from another comparator instance, but also using a key extractor.
Comparator
considers two elements equal, i.e.
compare(a, b) == 0
, other
is used to determine the order.
The returned comparator is serializable if the specified comparator is also serializable.
String
based on the length
and then case-insensitive natural ordering, the comparator can be
composed using following code,
Comparator<String> cmp = Comparator.comparingInt(String::length)
.thenComparing(String.CASE_INSENSITIVE_ORDER);
other
- the other comparator to be used when this comparator
compares two objects that are equal.NullPointerException
- if the argument is null.This library method is a comparator factory. It create a comparator by extracting a comparison key from the target object using the function passed as argument.
Comparable
sort key from a type T
, and returns a
Comparator<T>
that compares by that sort key.
This library method is a comparator factory. It create a comparator by extracting a comparison key from the target object using the function passed as argument.
Comparable
sort key from a type T
, and returns a
Comparator<T>
that compares by that sort key.
The returned comparator is serializable if the specified function is also serializable.
Comparator
that compares
Person
objects by their last name,
Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
T
- the type of element to be comparedU
- the type of the Comparable
sort keykeyExtractor
- the function used to extract the Comparable
sort keyNullPointerException
- if the argument is null