/*******************************************************************************
* 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