/*******************************************************************************
* 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
/**
* Represents a general-purpose stack of cards. New CardStack
* instances are initially empty.
*/
public class CardStack implements Iterable<Card>
{
private final List<Card> aCards;
/**
* Creates an empty CardStack.
*/
public CardStack()
{
aCards = new ArrayList<>();
}
public Stream<Card> stream()
{
return aCards.stream();
}
/**
* Creates a CardStack that contains all the cards
* in pCard, in the iteration order, from bottom to top.
*
* @param pCards The cards to initialize the stack with.
*/
public CardStack(Iterable<Card> pCards)
{
this();
for( Card card : pCards )
{
aCards.add(card);
}
}
/**
* Pushes pCard onto the stack.
*
* @param pCard The card to push.
* @pre pCard != null;
* @pre !aCards.contains(pCard)
*/
public void push(Card pCard)
{
assert pCard != null && !aCards.contains(pCard);
aCards.add(pCard);
}
/**
* Removes the card on top of the stack and returns it.
*
* @return The card on top of the stack.
* @pre !isEmpty()
*/
public Card pop()
{
assert !isEmpty();
return aCards.remove(aCards.size()-1);
}
/**
* @return The card at the top of the stack.
* @pre !isEmpty();
*/
public Card peek()
{
assert !isEmpty();
return aCards.get(aCards.size()-1);
}
/**
* @param pIndex The index to peek in the stack.
* @return The card at the position indicated by pIndex
* @pre pIndex >= 0 && pIndex < size();
*/
public Card peek(int pIndex)
{
assert pIndex >= 0 && pIndex < size();
return aCards.get(pIndex);
}
/**
* @return The number of cards in the stack.
*/
public int size()
{
return aCards.size();
}
/**
* Removes all the cards in the stack.
*/
public void clear()
{
aCards.clear();
}
/**
* @return True if and only if the stack has no cards in it.
*/
public boolean isEmpty()
{
return aCards.size() == 0;
}
@Override
public Iterator<Card> iterator()
{
return aCards.iterator();
}
}
for statement (sometimes called the "for-each loop" statement).for statement (sometimes called the "for-each loop" statement).for statementindex - index of the element to returnIndexOutOfBoundsException - if the index is out of range
(index < 0 || index >= size())clear in interface Collection<E>UnsupportedOperationException - if the clear operation
is not supported by this listindex - the index of the element to be removedUnsupportedOperationException - if the remove operation
is not supported by this listIndexOutOfBoundsException - if the index is out of range
(index < 0 || index >= size())Integer.MAX_VALUE elements, returns
Integer.MAX_VALUE.Integer.MAX_VALUE elements, returns
Integer.MAX_VALUE.size in interface Collection<E>true if this list contains the specified element.
More formally, returns true if and only if this list contains
at least one element e such that
Objects.equals(o, e).true if this list contains the specified element.
More formally, returns true if and only if this list contains
at least one element e such that
Objects.equals(o, e).contains in interface Collection<E>o - element whose presence in this list is to be testedtrue if this list contains the specified elementClassCastException - if the type of the specified element
is incompatible with this list
(optional)NullPointerException - if the specified element is null and this
list does not permit null elements
(optional)Lists that support this operation may place limitations on what elements may be added to this list. In particular, some lists will refuse to add null elements, and others will impose restrictions on the type of elements that may be added. List classes should clearly specify in their documentation any restrictions on what elements may be added.
add in interface Collection<E>e - element to be appended to this listtrue (as specified by Collection.add(E))UnsupportedOperationException - if the add operation
is not supported by this listClassCastException - if the class of the specified element
prevents it from being added to this listNullPointerException - if the specified element is null and this
list does not permit null elementsIllegalArgumentException - if some property of this element
prevents it from being added to this listStream and IntStream:
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
In this example, widgets is a Collection<Widget>. We create
a stream of Widget objects via Collection.stream(),
filter it to produce a stream containing only the red widgets, and then
transform it into a stream of int values representing the weight of
each red widget. Then this stream is summed to produce a total weight.
In addition to Stream, which is a stream of object references,
there are primitive specializations for IntStream, LongStream,
and DoubleStream, all of which are referred to as "streams" and
conform to the characteristics and restrictions described here.
To perform a computation, stream
operations are composed into a
stream pipeline. A stream pipeline consists of a source (which
might be an array, a collection, a generator function, an I/O channel,
etc), zero or more intermediate operations (which transform a
stream into another stream, such as filter(Predicate)), and a
terminal operation (which produces a result or side-effect, such
as count() or forEach(Consumer)).
Streams are lazy; computation on the source data is only performed when the
terminal operation is initiated, and source elements are consumed only
as needed.
A stream implementation is permitted significant latitude in optimizing
the computation of the result. For example, a stream implementation is free
to elide operations (or entire stages) from a stream pipeline -- and
therefore elide invocation of behavioral parameters -- if it can prove that
it would not affect the result of the computation. This means that
side-effects of behavioral parameters may not always be executed and should
not be relied upon, unless otherwise specified (such as by the terminal
operations forEach and forEachOrdered). (For a specific
example of such an optimization, see the API note documented on the
count() operation. For more detail, see the
side-effects section of the
stream package documentation.)
Collections and streams, while bearing some superficial similarities,
have different goals. Collections are primarily concerned with the efficient
management of, and access to, their elements. By contrast, streams do not
provide a means to directly access or manipulate their elements, and are
instead concerned with declaratively describing their source and the
computational operations which will be performed in aggregate on that source.
However, if the provided stream operations do not offer the desired
functionality, the BaseStream.iterator() and BaseStream.spliterator() operations
can be used to perform a controlled traversal.
A stream pipeline, like the "widgets" example above, can be viewed as
a query on the stream source. Unless the source was explicitly
designed for concurrent modification (such as a ConcurrentHashMap),
unpredictable or erroneous behavior may result from modifying the stream
source while it is being queried.
Most stream operations accept parameters that describe user-specified
behavior, such as the lambda expression w -> w.getWeight() passed to
mapToInt in the example above. To preserve correct behavior,
these behavioral parameters:
Such parameters are always instances of a
functional interface such
as Function, and are often lambda expressions or
method references. Unless otherwise specified these parameters must be
non-null.
A stream should be operated on (invoking an intermediate or terminal stream
operation) only once. This rules out, for example, "forked" streams, where
the same source feeds two or more pipelines, or multiple traversals of the
same stream. A stream implementation may throw IllegalStateException
if it detects that the stream is being reused. However, since some stream
operations may return their receiver rather than a new stream object, it may
not be possible to detect reuse in all cases.
Streams have a BaseStream.close() method and implement AutoCloseable.
Operating on a stream after it has been closed will throw IllegalStateException.
Most stream instances do not actually need to be closed after use, as they
are backed by collections, arrays, or generating functions, which require no
special resource management. Generally, only streams whose source is an IO channel,
such as those returned by Files.lines(Path), will require closing. If a
stream does require closing, it must be opened as a resource within a try-with-resources
statement or similar control structure to ensure that it is closed promptly after its
operations have completed.
Stream pipelines may execute either sequentially or in
parallel. This
execution mode is a property of the stream. Streams are created
with an initial choice of sequential or parallel execution. (For example,
Collection.stream() creates a sequential stream,
and Collection.parallelStream() creates
a parallel one.) This choice of execution mode may be modified by the
BaseStream.sequential() or BaseStream.parallel() methods, and may be queried with
the BaseStream.isParallel() method.
Stream with this collection as its source.
Stream with this collection as its source.
This method should be overridden when the spliterator()
method cannot return a spliterator that is IMMUTABLE,
CONCURRENT, or late-binding. (See spliterator()
for details.)
Stream from the
collection's Spliterator.Stream over the elements in this collectionIterator takes the place of
Enumeration in the Java Collections Framework. Iterators
differ from enumerations in two ways:
Iterator takes the place of
Enumeration in the Java Collections Framework. Iterators
differ from enumerations in two ways:
This interface is a member of the Java Collections Framework.
Enumeration can be converted into an Iterator by
using the Enumeration.asIterator() method.
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.
List interface. Implements
all optional list operations, and permits all elements, including
null. In addition to implementing the List interface,
this class provides methods to manipulate the size of the array that is
used internally to store the list. (This class is roughly equivalent to
Vector, except that it is unsynchronized.)
List interface. Implements
all optional list operations, and permits all elements, including
null. In addition to implementing the List interface,
this class provides methods to manipulate the size of the array that is
used internally to store the list. (This class is roughly equivalent to
Vector, except that it is unsynchronized.)
The size, isEmpty, get, set,
iterator, and listIterator operations run in constant
time. The add operation runs in amortized constant time,
that is, adding n elements requires O(n) time. All of the other operations
run in linear time (roughly speaking). The constant factor is low compared
to that for the LinkedList implementation.
Each ArrayList instance has a capacity. The capacity is
the size of the array used to store the elements in the list. It is always
at least as large as the list size. As elements are added to an ArrayList,
its capacity grows automatically. The details of the growth policy are not
specified beyond the fact that adding an element has constant amortized
time cost.
An application can increase the capacity of an ArrayList instance
before adding a large number of elements using the ensureCapacity
operation. This may reduce the amount of incremental reallocation.
Note that this implementation is not synchronized.
If multiple threads access an ArrayList instance concurrently,
and at least one of the threads modifies the list structurally, it
must be synchronized externally. (A structural modification is
any operation that adds or deletes one or more elements, or explicitly
resizes the backing array; merely setting the value of an element is not
a structural modification.) This is typically accomplished by
synchronizing on some object that naturally encapsulates the list.
If no such object exists, the list should be "wrapped" using the
Collections.synchronizedList
method. This is best done at creation time, to prevent accidental
unsynchronized access to the list:
List list = Collections.synchronizedList(new ArrayList(...));
The iterators returned by this class's iterator and
listIterator methods are fail-fast:
if the list is structurally modified at any time after the iterator is
created, in any way except through the iterator's own
remove or
add methods, the iterator will throw a
ConcurrentModificationException. Thus, in the face of
concurrent modification, the iterator fails quickly and cleanly, rather
than risking arbitrary, non-deterministic behavior at an undetermined
time in the future.
Note that the fail-fast behavior of an iterator cannot be guaranteed
as it is, generally speaking, impossible to make any hard guarantees in the
presence of unsynchronized concurrent modification. Fail-fast iterators
throw ConcurrentModificationException on a best-effort basis.
Therefore, it would be wrong to write a program that depended on this
exception for its correctness: the fail-fast behavior of iterators
should be used only to detect bugs.
This class is a member of the Java Collections Framework.