/**
* Copyright (C) 2022 by Martin Robillard. See https://codesample.info/about.html
*/
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();
}
}
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
.
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.
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.
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.
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>
.
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>
.
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 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 compared
U
- the type of the Comparable
sort key
keyExtractor
- the function used to extract the Comparable
sort key
NullPointerException
- if the argument is null
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.
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.
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 compared
U
- the type of the Comparable
sort key
keyExtractor
- the function used to extract the Comparable
sort key
NullPointerException
- if the argument is null
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.
The locale always used is the one returned by Locale.getDefault(Locale.Category)
with FORMAT
category specified.
format
- A format string
args
- 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.
compareTo
in interface Comparable<E extends Enum<E>>
o
- the object to be compared.
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 arguments
ClassCastException
- if the arguments' types prevent them from being compared by this comparator.
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 used
ClassCastException
- if the list contains elements that are not mutually comparable using the specified comparator
UnsupportedOperationException
- if the list's list-iterator does not support the set
operation
IllegalArgumentException
- (optional) if the comparator is found to violate the Comparator
contract
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.
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.
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 compared
U
- the type of the Comparable
sort key
keyExtractor
- the function used to extract the Comparable
sort key
NullPointerException
- if the argument is null
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.
EnumSet
and EnumMap
.
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.