Sample CoreJava For The Imaptient
Sample CoreJava For The Imaptient
Cay S. Horstmann
The author and publisher have taken care in the preparation of this book, but make
no expressed or implied warranty of any kind and assume no responsibility for
errors or omissions. No liability is assumed for incidental or consequential damages
in connection with or arising out of the use of the information or programs contained
herein.
For information about buying this title in bulk quantities, or for special sales
opportunities (which may include electronic versions; custom cover designs; and
content particular to your business, training goals, marketing focus, or branding
interests), please contact our corporate sales department at
[email protected] or (800) 382-3419.
For questions about sales outside the United States, please contact
[email protected].
All rights reserved. This publication is protected by copyright, and permission must
be obtained from the publisher prior to any prohibited reproduction, storage in a
retrieval system, or transmission in any form or by any means, electronic, mechanical,
photocopying, recording, or likewise. For information regarding permissions, request
forms and the appropriate contacts within the Pearson Education Global Rights &
Permissions Department, please visit www.pearson.com/permissions/.
ISBN-13: 978-0-13-805210-2
ISBN-10: 0-13-805210-7
ScoutAutomatedPrintCode
Pearson’s Commitment to Diversity, Equity, and
Inclusion
Preface xxiii
Acknowledgments xxv
About the Author xxvii
ix
x Contents
1.3.3 Initialization 16
1.3.4 Constants 16
1.4 Arithmetic Operations 17
1.4.1 Assignment 18
1.4.2 Basic Arithmetic 19
1.4.3 Mathematical Methods 20
1.4.4 Number Type Conversions 21
1.4.5 Relational and Logical Operators 22
1.4.6 Big Numbers 24
1.5 Strings 25
1.5.1 Concatenation 25
1.5.2 Substrings 26
1.5.3 String Comparison 26
1.5.4 Converting Between Numbers and Strings 28
1.5.5 The String API 28
1.5.6 Code Points and Code Units 31
1.5.7 Text Blocks 33
1.6 Input and Output 35
1.6.1 Reading Input 35
1.6.2 Formatted Output 36
1.7 Control Flow 38
1.7.1 Branches 38
1.7.2 Switches 39
1.7.3 Loops 41
1.7.4 Breaking and Continuing 43
1.7.5 Local Variable Scope 45
1.8 Arrays and Array Lists 46
1.8.1 Working with Arrays 46
1.8.2 Array Construction 47
1.8.3 Array Lists 48
1.8.4 Wrapper Classes for Primitive Types 49
1.8.5 The Enhanced for Loop 50
1.8.6 Copying Arrays and Array Lists 51
1.8.7 Array Algorithms 52
Contents xi
2 OBJECT-ORIENTED PROGRAMMING 61
2.1 Working with Objects 62
2.1.1 Accessor and Mutator Methods 64
2.1.2 Object References 65
2.2 Implementing Classes 67
2.2.1 Instance Variables 67
2.2.2 Method Headers 67
2.2.3 Method Bodies 68
2.2.4 Instance Method Invocations 68
2.2.5 The this Reference 69
2.2.6 Call by Value 70
2.3 Object Construction 71
2.3.1 Implementing Constructors 71
2.3.2 Overloading 72
2.3.3 Calling One Constructor from Another 73
2.3.4 Default Initialization 73
2.3.5 Instance Variable Initialization 74
2.3.6 Final Instance Variables 75
2.3.7 The Constructor with No Arguments 75
2.4 Records 76
2.4.1 The Record Concept 77
2.4.2 Constructors: Canonical, Custom, and Compact 78
2.5 Static Variables and Methods 79
2.5.1 Static Variables 79
2.5.2 Static Constants 80
2.5.3 Static Initialization Blocks 81
xii Contents
7 COLLECTIONS 247
7.1 An Overview of the Collections Framework 248
7.2 Iterators 252
7.3 Sets 254
Contents xvii
8 STREAMS 271
8.1 From Iterating to Stream Operations 272
8.2 Stream Creation 273
8.3 The filter, map, and flatMap Methods 276
8.4 Extracting Substreams and Combining Streams 278
8.5 Other Stream Transformations 279
8.6 Simple Reductions 280
8.7 The Optional Type 281
8.7.1 Producing an Alternative 281
8.7.2 Consuming the Value If Present 281
8.7.3 Pipelining Optional Values 282
8.7.4 How Not to Work with Optional Values 282
8.7.5 Creating Optional Values 284
8.7.6 Composing Optional Value Functions with flatMap 284
8.7.7 Turning an Optional into a Stream 285
8.8 Collecting Results 286
8.9 Collecting into Maps 287
8.10 Grouping and Partitioning 289
8.11 Downstream Collectors 289
8.12 Reduction Operations 292
8.13 Primitive Type Streams 294
xviii Contents
11 ANNOTATIONS 397
11.1 Using Annotations 398
11.1.1 Annotation Elements 398
11.1.2 Multiple and Repeated Annotations 400
11.1.3 Annotating Declarations 400
11.1.4 Annotating Type Uses 401
11.1.5 Making Receivers Explicit 402
11.2 Defining Annotations 403
11.3 Standard Annotations 406
11.3.1 Annotations for Compilation 407
11.3.2 Meta-Annotations 408
11.4 Processing Annotations at Runtime 410
11.5 Source-Level Annotation Processing 413
11.5.1 Annotation Processors 413
11.5.2 The Language Model API 414
11.5.3 Using Annotations to Generate Source Code 415
Exercises 417
13 INTERNATIONALIZATION 441
13.1 Locales 442
13.1.1 Specifying a Locale 443
13.1.2 The Default Locale 445
13.1.3 Display Names 446
13.2 Number Formats 447
13.3 Currencies 448
13.4 Date and Time Formatting 449
13.5 Collation and Normalization 451
13.6 Message Formatting 453
13.7 Resource Bundles 455
13.7.1 Organizing Resource Bundles 455
13.7.2 Bundle Classes 457
13.8 Character Encodings 458
13.9 Preferences 459
Exercises 461
Index 501
Preface
Java has seen many changes since its initial release in 1996. The classic book,
Core Java, covers, in meticulous detail, not just the language but all core li-
braries and a multitude of changes between versions, spanning two volumes
and over 2,000 pages. However, if you just want to be productive with
modern Java, there is a much faster, easier pathway for learning the language
and core libraries. In this book, I don’t retrace history and don’t dwell on
features of past versions. I show you the good parts of Java as it exists today,
so you can put your knowledge to work quickly.
As with my previous “Impatient” books, I quickly cut to the chase, showing
you what you need to know to solve a programming problem without lecturing
about the superiority of one paradigm over another. I also present the infor-
mation in small chunks, organized so that you can quickly retrieve it when
needed.
Assuming you are proficient in some other programming language, such as
C++, JavaScript, Swift, PHP, or Ruby, with this book you will learn how to
become a competent Java programmer. I cover all aspects of Java that a de-
veloper needs to know today, including the powerful concepts of lambda
expressions and streams, as well as modern constructs such as records and
sealed classes.
A key reason to use Java is to tackle concurrent programming. With parallel
algorithms and threadsafe data structures readily available in the Java library,
xxiii
xxiv Preface
Register your copy of Core Java for the Impatient, Third Edition, on the InformIT
site for convenient access to updates and/or corrections as they become
available. To start the registration process, go to informit.com/register and log
in or create an account. Enter the product ISBN (9780138052102) and click
Submit. Look on the Registered Products tab for an Access Bonus Content
link next to this product, and follow that link to access any available bonus
materials. If you would like to be notified of exclusive offers on new editions
and updates, please check the box to receive email from us.
Acknowledgments
xxv
This page intentionally left blank
About the Author
Cay S. Horstmann is the author of JavaScript for the Impatient and Scala for
the Impatient (both from Addison-Wesley), is principal author of Core Java,
Volumes I and II, Twelfth Edition (Pearson, 2022), and has written a dozen
other books for professional programmers and computer science students.
He is professor emeritus of computer science at San Jose State University and
is a Java Champion.
xxvii
Fundamental
Programming
Structures
In this chapter, you will learn how to work with files, directories, and web
pages, and how to read and write data in binary and text format. You will
also find a discussion of regular expressions, which can be useful for process-
ing input. (I couldn’t think of a better place to handle that topic, and appar-
ently neither could the Java developers—when the regular expression API
specification was proposed, it was attached to the specification request for
“new I/O” features.) Finally, this chapter shows you the object serialization
mechanism that lets you store objects as easily as you can store text or
numeric data.
The key points of this chapter are:
1. An InputStream is a source of bytes, and an OutputStream is a destination for
bytes.
2. A Reader reads characters, and a Writer writes them. Be sure to specify a
character encoding.
3. The Files class has convenience methods for reading all bytes or lines of
a file.
4. The DataInput and DataOutput interfaces have methods for writing numbers
in binary format.
5. Use a RandomAccessFile or a memory-mapped file for random access.
301
302 Chapter 9 Processing Input and Output
Here, path is an instance of the Path class that is covered in Section 9.2.1,
“Paths” (page 312). It describes a path in a file system.
If you have a URL, you can read its contents from the input stream returned
by the openStream method of the URL class:
var url = new URL("https://horstmann.com/index.html");
InputStream in = url.openStream();
Section 9.3, “HTTP Connections” (page 320) shows how to send data to a
web server.
The ByteArrayInputStream class lets you read from an array of bytes.
9.1 Input/Output Streams, Readers, and Writers 303
This method either returns the byte as an integer between 0 and 255, or returns
-1 if the end of input has been reached.
CAUTION: The Java byte type has values between -128 and 127. You
can cast the returned value into a byte after you have checked that it
is not -1.
More commonly, you will want to read the bytes in bulk. The most convenient
method is the readAllBytes method that simply reads all bytes from the stream
into a byte array:
byte[] bytes = in.readAllBytes();
TIP: If you want to read all bytes from a file, call the convenience
method
byte[] bytes = Files.readAllBytes(path);
If you want to read some, but not all bytes, provide a byte array and call the
readNBytes method:
var bytes = new byte[len];
int bytesRead = in.readNBytes(bytes, offset, n);
The method reads until either n bytes are read or no further input is available,
and returns the actual number of bytes read. If no input was available at all,
the methods return -1.
304 Chapter 9 Processing Input and Output
When you are done writing a stream, you must close it in order to commit
any buffered output. This is best done with a try-with-resources statement:
try (OutputStream out = ...) {
out.write(bytes);
}
Both streams need to be closed after the call to transferTo. It is best to use a
try-with-resources statement, as in the code example.
To write a file to an OutputStream, call
Files.copy(path, out);
0...7F 0a6a5a4a3a2a1a0
0...FFFF a15a14a13a12a11a10a9a8a7a6a5a4a3a2a1a0
In addition to the UTF encodings, there are partial encodings that cover a
character range suitable for a given user population. For example, ISO 8859-1
is a one-byte code that includes accented characters used in Western European
languages. Shift_ JIS is a variable-length code for Japanese characters. A large
number of these encodings are still in widespread use.
There is no reliable way to automatically detect the character encoding from
a stream of bytes. Some API methods let you use the “default charset”—the
character encoding that is preferred by the operating system of the computer.
Is that the same encoding that is used by your source of bytes? These bytes
may well originate from a different part of the world. Therefore, you should
always explicitly specify the encoding. For example, when reading a web
page, check the Content-Type header.
The StandardCharsets class has static variables of type Charset for the character
encodings that every Java virtual machine must support:
StandardCharsets.UTF_8
StandardCharsets.UTF_16
StandardCharsets.UTF_16BE
StandardCharsets.UTF_16LE
9.1 Input/Output Streams, Readers, and Writers 307
StandardCharsets.ISO_8859_1
StandardCharsets.US_ASCII
To obtain the Charset for another encoding, use the static forName method:
Charset shiftJIS = Charset.forName("Shift_JIS");
Use the Charset object when reading or writing text. For example, you can
turn an array of bytes into a string as
var contents = new String(bytes, StandardCharsets.UTF_8);
If you want to process the input one UTF-16 code unit at a time, you can
call the read method:
int ch = in.read();
The method returns a code unit between 0 and 65536, or -1 at the end of input.
That is not very convenient. Here are several alternatives.
With a short text file, you can read it into a string like this:
String content = Files.readString(path, charset);
To read numbers or words from a file, use a Scanner, as you have seen in
Chapter 1. For example,
var in = new Scanner(path, StandardCharsets.UTF_8);
while (in.hasNextDouble()) {
double value = in.nextDouble();
...
}
If your input does not come from a file, wrap the InputStream into a BufferedReader:
try (var reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
Stream<String> lines = reader.lines();
...
}
If a method asks for a Reader and you want it to read from a file, call
Files.newBufferedReader(path, charset).
It is more convenient to use a PrintWriter, which has the print, println, and
printf that you have always used with System.out. Using those methods, you
can print numbers and use formatted output.
If you write to a file, construct a PrintWriter like this:
var out = new PrintWriter(Files.newBufferedWriter(path, charset));
or
Files.write(path, lines, charset);
The advantage of binary I/O is that it is fixed width and efficient. For example,
writeInt always writes an integer as a big-endian 4-byte binary quantity regard-
less of the number of digits. The space needed is the same for each value of
a given type, which speeds up random access. Also, reading binary data is
faster than parsing text. The main drawback is that the resulting files cannot
be easily inspected in a text editor.
You can use the DataInputStream and DataOutputStream adapters with any stream.
For example,
DataInput in = new DataInputStream(Files.newInputStream(path));
DataOutput out = new DataOutputStream(Files.newOutputStream(path));
writing; specify the option by using the string "r" (for read access) or "rw" (for
read/write access) as the second argument in the constructor. For example,
var file = new RandomAccessFile(path.toString(), "rw");
A random-access file has a file pointer that indicates the position of the next
byte to be read or written. The seek method sets the file pointer to an arbitrary
byte position within the file. The argument to seek is a long integer between
zero and the length of the file (which you can obtain with the length method).
The getFilePointer method returns the current position of the file pointer.
The RandomAccessFile class implements both the DataInput and DataOutput interfaces.
To read and write numbers from a random-access file, use methods such as
readInt/writeInt that you saw in the preceding section. For example,
int value = file.readInt();
file.seek(file.getFilePointer() - 4);
file.writeInt(value + 1);
Then, map an area of the file (or, if it is not too large, the entire file) into
memory:
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE,
0, channel.size());
Use methods get, getInt, getDouble, and so on to read values, and the equivalent
put methods to write values.
int offset = ...;
int value = buffer.getInt(offset);
buffer.put(offset, value + 1);
At some point, and certainly when the channel is closed, these changes are
written back to the file.
NOTE: By default, the methods for reading and writing numbers use
big-endian byte order. You can change the byte order with the command
buffer.order(ByteOrder.LITTLE_ENDIAN);
312 Chapter 9 Processing Input and Output
or
FileLock lock = channel.tryLock();
The first call blocks until the lock becomes available. The second call returns
immediately, either with the lock or with null if the lock is not available. The
file remains locked until the lock or the channel is closed. It is best to use a
try-with-resources statement:
try (FileLock lock = channel.lock()) {
...
}
9.2.1 Paths
A Path is a sequence of directory names, optionally followed by a file name.
The first component of a path may be a root component, such as / or C:\. The
permissible root components depend on the file system. A path that starts
with a root component is absolute. Otherwise, it is relative. For example, here
we construct an absolute and a relative path. For the absolute path, we
assume we are running on a Unix-like file system.
Path absolute = Path.of("/", "home", "cay");
Path relative = Path.of("myapp", "conf", "user.properties");
9.2 Paths, Files, and Directories 313
The static Path.of method receives one or more strings, which it joins with
the path separator of the default file system (/ for a Unix-like file system, \
for Windows). It then parses the result, throwing an InvalidPathException if the
result is not a valid path in the given file system. The result is a Path object.
You can also provide a string with separators to the Path.of method:
Path homeDirectory = Path.of("/home/cay");
NOTE: A Path object does not have to correspond to a file that actually
exists. It is merely an abstract sequence of names. To create a file, first
make a path, then call a method to create the corresponding file—see
Section 9.2.2, “Creating Files and Directories” (page 314).
yields /home/cay/myapp/temp.
The opposite of resolve is relativize. The call p.relativize(r) yields the path q
which, when resolved with p, yields r. For example,
Path.of("/home/cay").relativize(Path.of("/home/fred/myapp"))
yields ../fred/myapp, assuming we have a file system that uses .. to denote the
parent directory.
The normalize method removes any redundant . and .. components (or what-
ever the file system may deem redundant). For example, normalizing the path
/home/cay/../fred/./myapp yields /home/fred/myapp.
The toAbsolutePath method yields the absolute path of a given path. If the path
is not already absolute, it is resolved against the “user directory”—that is, the
directory from which the JVM was invoked. For example, if you launched
314 Chapter 9 Processing Input and Output
The Path interface has methods for taking paths apart and combining them
with other paths. This code sample shows some of the most useful ones:
Path p = Path.of("/home", "cay", "myapp.properties");
Path parent = p.getParent(); // The path /home/cay
Path file = p.getFileName(); // The last element, myapp.properties
Path root = p.getRoot(); // The initial segment / (null for a relative path)
Path first = p.getName(0); // The first element
Path dir = p.subpath(1, p.getNameCount());
// All but the first element, cay/myapp.properties
The Path interface extends the Iterable<Path> element, so you can iterate over
the name components of a Path with an enhanced for loop:
for (Path component : path) {
...
}
NOTE: Occasionally, you may need to interoperate with legacy APIs that
use the File class instead of the Path interface. The Path interface has a
toFile method, and the File class has a toPath method.
All but the last component in the path must already exist. To create
intermediate directories as well, use
Files.createDirectories(path);
The call throws an exception if the file already exists. The checks for existence
and the creation are atomic. If the file doesn’t exist, it is created before anyone
else has a chance to do the same.
The call Files.exists(path) checks whether the given file or directory exists. To
test whether it is a directory or a “regular” file (that is, with data in it, not
something like a directory or symbolic link), call the static methods isDirectory
and isRegularFile of the Files class.
9.2 Paths, Files, and Directories 315
Here, dir is a Path, and prefix/suffix are strings which may be null. For
example, the call Files.createTempFile(null, ".txt") might return a path such as
/tmp/1234405522364837194.txt.
To move a file (that is, copy and delete the original), call
Files.move(fromPath, toPath);
You can specify that a move should be atomic. Then you are assured that
either the move completed successfully, or the source continues to be present.
Use the ATOMIC_MOVE option:
Files.move(fromPath, toPath, StandardCopyOption.ATOMIC_MOVE);
See Table 9-3 for a summary of the options that are available for file
operations.
Finally, to delete a file, simply call
Files.delete(path);
This method throws an exception if the file doesn’t exist, so instead you may
want to use
boolean deleted = Files.deleteIfExists(path);
The list method does not enter subdirectories. To process all descendants of
a directory, use the Files.walk method instead.
try (Stream<Path> entries = Files.walk(pathToRoot)) {
// Contains all descendants, visited in depth-first order
}
As you can see, whenever the traversal yields a directory, it is entered before
continuing with its siblings.
You can limit the depth of the tree that you want to visit by calling
Files.walk(pathToRoot, depth). Both walk methods have a varargs parameter of type
FileVisitOption..., but there is only one option you can supply: FOLLOW_LINKS to
follow symbolic links.
NOTE: If you filter the paths returned by walk and your filter criterion
involves the file attributes stored with a directory, such as size, creation
time, or type (file, directory, symbolic link), then use the find method
instead of walk. Call that method with a predicate function that accepts
a path and a BasicFileAttributes object. The only advantage is efficiency.
Since the directory is being read anyway, the attributes are readily
available.
318 Chapter 9 Processing Input and Output
This code fragment uses the Files.walk method to copy one directory to another:
Files.walk(source).forEach(p -> {
try {
Path q = target.resolve(source.relativize(p));
if (Files.isDirectory(p))
Files.createDirectory(q);
else
Files.copy(p, q);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
});
Unfortunately, you cannot easily use the Files.walk method to delete a tree of
directories since you need to first visit the children before deleting the parent.
In that case, use the walkFileTree method. It requires an instance of the FileVisitor
interface. Here is when the file visitor gets notified:
1. Before a directory is processed:
FileVisitResult preVisitDirectory(T dir, IOException ex)
• Continue the walk, but without visiting the entries in this directory:
FileVisitResult.SKIP_SUBTREE
• Continue the walk, but without visiting the siblings of this file:
FileVisitResult.SKIP_SIBLINGS
If any of the methods throws an exception, the walk is also terminated, and
that exception is thrown from the walkFileTree method.
The SimpleFileVisitor class implements this interface, continuing the iteration
at each point and rethrowing any exceptions.
Here is how you can delete a directory tree:
9.2 Paths, Files, and Directories 319
establishes a file system that contains all files in the ZIP archive. It’s an easy
matter to copy a file out of that archive if you know its name:
Files.copy(zipfs.getPath(sourceName), targetPath);
You have to work a bit harder to create a new ZIP file. Here is the magic
incantation:
Path zipPath = Path.of("myfile.zip");
var uri = new URI("jar", zipPath.toUri().toString(), null);
// Constructs the URI jar:file://myfile.zip
try (FileSystem zipfs = FileSystems.newFileSystem(uri,
Collections.singletonMap("create", "true"))) {
// To add files, copy them into the ZIP file system
Files.copy(sourcePath, zipfs.getPath("/").resolve(targetPath));
}
NOTE: There is an older API for working with ZIP archives, with classes
ZipInputStream and ZipOutputStream, but it’s not as easy to use as the one
described in this section.
320 Chapter 9 Processing Input and Output
4. If you want to read the response headers and you haven’t called
getOutputStream, call
connection.connect();
For each key, you get a list of values since there may be multiple header
fields with the same key.
9.3 HTTP Connections 321
Alternatively, if you need to configure the client, use a builder API like this:
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
That is, you get a builder, call methods to customize the item that is going
to be built, and then call the build method to finalize the building process.
This is a common pattern for constructing immutable objects.
Follow the same pattern for formulating requests. Here is a GET request:
322 Chapter 9 Processing Input and Output
The URI is the “uniform resource identifier” which is, when using HTTP, the
same as a URL. However, in Java, the URL class has methods for actually
opening a connection to a URL, whereas the URI class is only concerned with
the syntax (scheme, host, port, path, query, fragment, and so on).
When sending the request, you have to tell the client how to handle the
response. If you just want the body as a string, send the request with a
HttpResponse.BodyHandlers.ofString(), like this:
HttpResponse<String> response
= client.send(request, HttpResponse.BodyHandlers.ofString());
The HttpResponse class is a template whose type denotes the type of the body.
You get the response body string simply as
String bodyString = response.body();
There are other response body handlers that get the response as a byte array
or a file. One can hope that eventually the JDK will support JSON and provide
a JSON handler.
With a POST request, you similarly need a “body publisher” that turns the re-
quest data into the data that is being posted. There are body publishers for
strings, byte arrays, and files. Again, one can hope that the library designers
will wake up to the reality that most POST requests involve form data or JSON
objects, and provide appropriate publishers.
In the meantime, to send a form post, you need to URL-encode the request
data, just like in the preceding section.
Map<String, String> postData = ...;
boolean first = true;
var body = new StringBuilder();
for (Map.Entry<String, String> entry : postData.entrySet()) {
if (first) first = false;
else body.append("&");
body.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
body.append("=");
body.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
HttpRequest request = HttpRequest.newBuilder()
.uri(httpUrlString)
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
9.4 Regular Expressions 323
Note that, unlike with the URLConnection class, you need to specify the content
type for forms.
Similarly, for posting JSON data, you specify the content type and provide a
JSON string.
The HttpResponse object also yields the status code and the response headers.
int status = response.statusCode();
HttpHeaders responseHeaders = response.headers();
The map values are lists since in HTTP, each key can have multiple values.
If you just want the value of a particular key, and you know that there won’t
be multiple values, call the firstValue method:
Optional<String> lastModified = headerMap.firstValue("Last-Modified");
You get the response value or an empty optional if none was supplied.
TIP: To enable logging for the HttpClient, add this line to net.properties
in your JDK:
jdk.httpclient.HttpClient.log=all
Then set the logging level for the logger named jdk.httpclient.HttpClient
to INFO, for example by adding this line to the logging.properties file in
your JDK:
jdk.httpclient.HttpClient.level=INFO
For example, the regular expression Java only matches the string Java.
The symbol . matches any single character. For example, .a.a matches Java
and data.
The * symbol indicates that the preceding constructs may be repeated 0 or
more times; for a +, it is 1 or more times. A suffix of ? indicates that a con-
struct is optional (0 or 1 times). For example, be+s? matches be, bee, and bees.
You can specify other multiplicities with { } (see Table 9-4).
A | denotes an alternative: .(oo|ee)f matches beef or woof. Note the parenthe-
ses—without them, .oo|eef would be the alternative between .oo and eef.
Parentheses are also used for grouping—see Section 9.4.4, “Groups” (page 330).
A character class is a set of character alternatives enclosed in brackets, such
as [Jj], [0-9], [A-Za-z], or [^0-9]. Inside a character class, the - denotes a range
(all characters whose Unicode values fall between the two bounds). However,
a - that is the first or last character in a character class denotes itself. A ^ as
the first character in a character class denotes the complement (all characters
except those specified).
There are many predefined character classes such as \d (digits) or \p{Sc} (Unicode
currency symbols). See Tables 9-4 and 9-5.
The characters ^ and $ match the beginning and end of input.
If you need to have a literal . * + ? { | ( ) [ \ ^ $, precede it by a backslash.
Inside a character class, you only need to escape [ and \, provided you are
careful about the positions of ] - ^. For example, []^-] is a class containing
all three of them.
Alternatively, surround a string with \Q and \E. For example, \(\$0\.99\) and
\Q($0.99)\E both match the string ($0.99).
TIP: If you have a string that may contain some of the many special
characters in the regular expression syntax, you can escape them all by
calling Parse.quote(str). This simply surrounds the string with \Q and \E,
but it takes care of the special case where str may contain \E.
9.4 Regular Expressions 325
Characters
Grouping
Name Description
If you need to use the same regular expression many times, it is more efficient
to compile it. Then, create a Matcher for each input:
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) ...
If the match succeeds, you can retrieve the location of matched groups—see
Section 9.4.4, “Groups” (page 330).
If you want to test whether the input contains a match, use the find method
instead:
if (matcher.find()) ...
The result contains all strings that match the regular expression.
Use the asPredicate method to test whether a string contains a match:
List<String> sringsContainingMatch = strings.stream()
.filter(digits.asPredicate())
.toList(); // ["31st", "1999"]
In this way, you can process each match in turn. As shown in the code
fragment, you can get the matched string as well as its position in the input
string.
More elegantly, you can call the results method to get a Stream<MatchResult>. The
MatchResult interface has methods group, start, and end, just like Matcher. (In fact,
the Matcher class implements this interface.) Here is how you get a list of all
matches:
List<String> matches = pattern.matcher(input)
.results()
.map(Matcher::group)
.toList();
If you have the data in a file, then you can use the Scanner.findAll method to
get a Stream<MatchResult>, without first having to read the contents into a string.
You can pass a Pattern or a pattern string:
var in = new Scanner(path, StandardCharsets.UTF_8);
Stream<String> words = in.findAll("\\pL+")
.map(MatchResult::group);
9.4.4 Groups
It is common to use groups for extracting components of a match. For exam-
ple, suppose you have a line item in the invoice with item name, quantity,
and unit price such as
Blackwell Toaster USD29.95
After matching, you can extract the nth group from the matcher as
String contents = matcher.group(n);
We aren’t interested in group 2; it only arose from the parentheses that were
required for the repetition. For greater clarity, you can use a noncapturing
group:
(\p{Alnum}+(?:\s+\p{Alnum}+)*)\s+([A-Z]{3})([0-9.]*)
With the start and end methods, you can get the group positions in the input:
int itemStart = matcher.start("item");
int itemEnd = matcher.end("item");
NOTE: Retrieving groups by name only works with a Matcher, not with
a MatchResult.
If you don’t care about precompiling the pattern or lazy fetching, you can
just use the String.split method:
String[] tokens = input.split("\\s*,\\s*");
Or, if you don’t care about precompiling, use the replaceAll method of the
String class.
String result = input.replaceAll("\\s*,\\s*", ",");
The replacement string can contain group numbers $n or names ${name}. They
are replaced with the contents of the corresponding captured group.
String result = "3:45".replaceAll(
"(\\d{1,2}):(?<minutes>\\d{2})",
"$1 hours and ${minutes} minutes");
// Sets result to "3 hours and 45 minutes"
You can use \ to escape $ and \ in the replacement string, or you can call
the Matcher.quoteReplacement convenience method:
matcher.replaceAll(Matcher.quoteReplacement(str))
If you want to carry out a more complex operation than splicing in group
matches, then you can provide a replacement function instead of a replacement
string. The function accepts a MatchResult and yields a string. For example, here
we replace all words with at least four letters with their uppercase version:
String result = Pattern.compile("\\pL{4,}")
.matcher("Mary had a little lamb")
.replaceAll(m -> m.group().toUpperCase());
// Yields "MARY had a LITTLE LAMB"
The replaceFirst method replaces only the first occurrence of the pattern.
9.5 Serialization 333
9.4.7 Flags
Several flags change the behavior of regular expressions. You can specify them
when you compile the pattern:
Pattern pattern = Pattern.compile(regex,
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
• Pattern.MULTILINE
or m: Make ^ and $ match the beginning and end of a line,
not the entire input.
• Pattern.UNIX_LINESor d: Only '\n' is a line terminator when matching ^ and
$ in multiline mode.
• Pattern.DOTALL or s: Make the . symbol match all characters, including line
terminators.
• Pattern.COMMENTS
or x: Whitespace and comments (from # to the end of a
line) are ignored.
• Pattern.LITERAL: The pattern is taking literally and must be matched exactly,
except possibly for letter case.
• Pattern.CANON_EQ:
Take canonical equivalence of Unicode characters into
account. For example, u followed by ¨ (diaeresis) matches ü.
The last two flags cannot be specified inside a regular expression.
9.5 Serialization
In the following sections, you will learn about object serialization—a mecha-
nism for turning an object into a bunch of bytes that can be shipped some-
where else or stored on disk, and for reconstituting the object from those
bytes.
334 Chapter 9 Processing Input and Output
Retrieve the objects in the same order in which they were written, using the
readObject method.
var e1 = (Employee) in.readObject();
var e2 = (Employee) in.readObject();
When an object is written, the name of the class and the names and values
of all instance variables are saved. If the value of an instance variable belongs
to a primitive type, it is saved as binary data. If it is an object, it is again
written with the writeObject method.
When an object is read in, the process is reversed. The class name and the
names and values of the instance variables are read, and the object is
reconstituted.
There is just one catch. Suppose there were two references to the same object.
Let’s say each employee has a reference to their boss:
var peter = new Employee("Peter", 90000);
var paul = new Manager("Barney", 105000);
var mary = new Manager("Mary", 180000);
peter.setBoss(mary);
paul.setBoss(mary);
out.writeObject(peter);
out.writeObject(paul);
When reading these two objects back in, both of them need to have the same
boss, not two references to identical but distinct objects.
In order to achieve this, each object gets a serial number when it is saved.
When you pass an object reference to writeObject, the ObjectOutputStream checks
if the object reference was previously written. In that case, it just writes out
the serial number and does not duplicate the contents of the object.
In the same way, an ObjectInputStream remembers all objects it has encountered.
When reading in a reference to a repeated object, it simply yields a reference
to the previously read object.
Then, the object headers continue to be written as usual, but the instance
variables fields are no longer automatically serialized. Instead, these methods
are called.
Note the @Serial annotation. The methods for tweaking serialization don’t be-
long to interfaces. Therefore, you can’t use the @Override annotation to have
the compiler check the method declarations. The @Serial annotation is meant
to enable the same checking for serialization methods. Up to Java 17, the
javac compiler doesn’t do that checking, but it might happen in the future.
Some IDEs check the annotation.
A number of classes in the java.awt.geom package, such as Point2D.Double, are not
serializable. Now, suppose you want to serialize a class LabeledPoint that stores
a String and a Point2D.Double. First, you need to mark the Point2D.Double field as
transient to avoid a NotSerializableException.
public class LabeledPoint implements Serializable {
private String label;
private transient Point2D.Double point;
...
}
In the writeObject method, first write the object descriptor and the String field,
label, by calling the defaultWriteObject method. This is a special method of the
ObjectOutputStream class that can only be called from within a writeObject method
9.5 Serialization 337
of a serializable class. Then we write the point coordinates, using the standard
DataOutput calls.
@Serial before private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeDouble(point.getX());
out.writeDouble(point.getY());
}
Another example is the HashSet class that supplies its own readObject and
writeObject methods. Instead of saving the internal structure of the hash table,
the writeObject method simply saves the capacity, load factor, size, and elements.
The readObject method reads back the capacity and load factor, constructs a
new table, and inserts the elements.
The readObject and writeObject methods only need to save and load their
data. They do not concern themselves with superclass data or any other class
information.
The Date class uses this approach. Its writeObject method saves the milliseconds
since the “epoch” (January 1, 1970). The data structure that caches calendar
data is not saved.
Unlike the readObject and writeObject methods, these methods are fully respon-
sible for saving and restoring the entire object, including the superclass data.
When writing an object, the serialization mechanism merely records the class
of the object in the output stream. When reading an externalizable object,
the object input stream creates an object with the no-argument constructor
and then calls the readExternal method.
In this example, the LabeledPixel class extends the serializable Point class, but
it takes over the serialization of the class and superclass. The fields of the
object are not stored in the standard serialization format. Instead, the data
are placed in an opaque block.
public class LabeledPixel extends Point implements Externalizable {
private String label;
When a Person object is serialized, none of its instance variables are saved.
Instead, the writeReplace method is called and its return value is serialized and
written to the stream.
The proxy class needs to implement a readResolve method that yields a Person
instance:
class PersonProxy implements Serializable {
private int id;
NOTE: Unlike the readObject and writeObject methods, the readResolve and
writeReplace methods need not be private.
9.5.6 Versioning
Serialization was intended for sending objects from one virtual machine to
another, or for short-term persistence of state. If you use serialization for
long-term persistence, or in any situation where classes can change between
serialization and deserialization, you will need to consider what happens
when your classes evolve. Can version 2 read the old data? Can the users
who still use version 1 read the files produced by the new version?
The serialization mechanism supports a simple versioning scheme. When an
object is serialized, both the name of the class and its serialVersionUID are
9.5 Serialization 341
displays
private static final long serialVersionUID = -4932578720821218323L;
When the class implementation changes, there is a very high probability that
the hash code changes as well.
If you need to be able to read old version instances, and you are certain that
is safe to do so, run serialver on the old version of your class and add the
result to the new version.
NOTE: In this section, you saw what happens when the reader’s version
of a class has instance variables that aren’t present in the object stream.
It is also possible during class evolution for a superclass to be added.
Then a reader using the new version may read an object stream in which
the instance variables of the superclass are not set. By default, those
instance fields are set to their 0/false/null default. That may leave the
superclass in an unsafe state. The superclass can defend against that
problem by defining an initialization method
@Serial private void readObjectNoData() throws ObjectStreamException
The method should either set the same state as the no-argument
constructor or throw an InvalidObjectException. It is only called in the
unusual circumstance where an object stream is read that contains an
instance of a subclass with missing superclass data.
The object is then scheduled for validation, and the validateObject method is
called when this object and all dependent objects have been loaded. The
second parameter lets you specify a priority. Validation requests with higher
priorities are done first.
There are other security risks. Adversaries can create data structures that
consume enough resources to crash a virtual machine. More insidiously, any
class on the class path can be deserialized. Hackers have been devious about
piecing together “gadget chains”—sequences of operations in various utility
classes that use reflection and culminate in calling methods such as Runtime.exec
with a string of their choice.
Any application that receives serialized data from untrusted sources over a
network connection is vulnerable to such attacks. For example, some servers
serialize session data and deserialize whatever data are returned in the HTTP
session cookie.
You should avoid situations in which arbitrary data from untrusted sources
are deserialized. In the example of session data, the server should sign the
data, and only deserialize data with a valid signature.
A serialization filter mechanism can harden applications from such attacks. The
filters see the names of deserialized classes and several metrics (stream size,
array sizes, total number of references, longest chain of references). Based
on those data, the deserialization can be aborted.
In its simplest form, you provide a pattern describing the valid and invalid
classes. For example, if you start our sample serialization demo as
java -Djdk.serialFilter='serial.*;java.**;!*' serial.ObjectStreamTest
then the objects will be loaded. The filter allows all classes in the serial
package and all classes whose package name starts with java, but no others.
If you don’t allow java.**, or at least java.util.Date, deserialization fails.
344 Chapter 9 Processing Input and Output
You can place the filter pattern into a configuration file and specify multiple
filters for different purposes. You can also implement your own filters. See
https://docs.oracle.com/en/java/javase/17/core/serialization-filtering1.html for details.
Exercises
1. Write a utility method for copying all of an InputStream to an OutputStream,
without using any temporary files. Provide another solution, without a
loop, using operations from the Files class, using a temporary file.
2. Write a program that reads a text file and produces a file with the same
name but extension .toc, containing an alphabetized list of all words in
the input file together with a list of line numbers in which each word
occurs. Assume that the file’s encoding is UTF-8.
3. Write a program that reads a file containing text and, assuming that most
words are English, guesses whether the encoding is ASCII, ISO 8859-1,
UTF-8, or UTF-16, and if the latter, which byte ordering is used.
4. Using a Scanner is convenient, but it is a bit slower than using a
BufferedReader.
Read in a long file a line at a time, counting the number of
input lines, with (a) a Scanner and hasNextLine/nextLine, (b) a BufferedReader and
readLine, (c) a BufferedReader and lines. Which is the fastest? The most
convenient?
5. When an encoder of a Charset with partial Unicode coverage can’t encode
a character, it replaces it with a default—usually, but not always, the en-
coding of "?". Find all replacements of all available character sets that
support encoding. Use the newEncoder method to get an encoder, and call
its replacement method to get the replacement. For each unique result, report
the canonical names of the charsets that use it.
6. The BMP file format for uncompressed image files is well documented
and simple. Using random access, write a program that reflects each row
of pixels in place, without writing a new file.
7. Look up the API documentation for the MessageDigest class and write a
program that computes the SHA-512 digest of a file. Feed blocks of bytes
to the MessageDigest object with the update method, then display the result
of calling digest. Verify that your program produces the same result as the
sha512sum utility.
8. Write a utility method for producing a ZIP file containing all files from
a directory and its descendants.
Exercises 345
Set the HTTP header Authorization to the value "Basic " + encoding. Then read
and print the page contents.
10. Using a regular expression, extract all decimal integers (including negative
ones) from a string into an ArrayList<Integer> (a) using find, and (b) using
split. Note that a + or - that is not followed by a digit is a delimiter.
11. Using regular expressions, extract the directory path names (as an array
of strings), the file name, and the file extension from an absolute or
relative path such as /home/cay/myfile.txt.
12. Come up with a realistic use case for using group references in
Matcher.replaceAll and implement it.
13. Implement a method that can produce a clone of any serializable object
by serializing it into a byte array and deserializing it.
14. Implement a serializable class Point with instance variables for x and y.
Write a program that serializes an array of Point objects to a file, and
another that reads the file.
15. Continue the preceding exercise, but change the data representation of
Point so that it stores the coordinates in an array. What happens when
the new version tries to read a file generated by the old version? What
happens when you fix up the serialVersionUID? Suppose your life depended
upon making the new version compatible with the old. What could
you do?
16. Which classes in the standard Java library implement Externalizable? Which
of them use writeReplace/readResolve?
17. Unzip the API source and investigate how the LocalDate class is serialized.
Why does the class define writeExternal and readExternal methods even
though it doesn’t implement Externalizable? (Hint: Look at the Ser class.
Why does the class define a readObject method? How could it be invoked?
Index
501
502 Index
annotations copying, 51
accessing, 404, 494 declaring, 46–47
applicability of, 406–407 initializing, 46
container, 409, 412 ArrayBlockingQueue class, 371
declaration, 400–401 ArrayDeque class, 262
documented, 407–408 ArrayIndexOutOfBoundsException, 47
generating source code with, 415–417 ArrayList class, 48–49, 248
inherited, 407–408, 411 add method, 49, 64
key/value pairs in, 398–399, 405 clone method, 165–166
meta, 404–410 forEach method, 125
modifiers and, 402 get, remove methods, 49
multiple, 400 removeIf method, 124
processing: set, size methods, 49
at runtime, 410–413 arrays, 46–48
source-level, 413–417 accessing nonexisting elements in,
repeatable, 400, 407, 409–410, 412 47
standard, 406–410 allocating, 234
type use, 401–402 annotating, 401
anonymous classes, 138 casting, 185
anyMatch method (Stream), 280 checking, 185
anyOf method (CompletableFuture), 357–358 comparing, 161
Apache Commons CSV, 490 computing values of, 367
API documentation, 29–31 constructing, 46–47
generating, 95 constructor references with, 126
Applet class, 174 converting:
applications. See programs to a reference of type Object, 157
apply, applyAsXxx methods (functional to/from streams, 286, 296, 368
interfaces), 129–130 copying, 51
applyToEither method (CompletableFuture), covariant, 223
357–358 filling, 47, 52
arithmetic operations, 17–24 generating Class objects for, 171
Array class, 185–186 growing, 185–186
array lists, 48–49 hash codes of, 163
anonymous, 149 length of, 47–48, 134
checking for nulls, 227 multidimensional, 53–55, 159
constructing, 49 of bytes, 302–303
converting between, 224 of generic types, 126, 235
copying, 51 of objects, 47, 367
elements of, 49–50 of primitive types, 367
filling, 52 of strings, 331
instantiating with type variables, 234 passing into methods, 56
size of, 49 printing, 52, 55, 159
sorting, 52 serializable, 334
variables of, 49 sorting, 52, 117–119, 367–368
array variables superclass assignment in, 148
assigning values to, 48 using class literals with, 171
Index 505
Arrays class B
asList method, 265 b, B conversion characters, 37
copyOf method, 51, 186 \b (backspace), 14
deepToString method, 159 \b, \B, in regular expressions, 328
equals method, 161 BasicFileAttributes class, 317
fill method, 52 BeanInfo class, 184
hashCode method, 163 between method (Duration), 423
parallelXxx methods, 52, 367 BiConsumer interface, 129
setAll method, 127 BiFunction interface, 129, 131
sort method, 52, 119, 123–124 BigDecimal class, 14, 24, 337
stream method, 274, 294 big-endian format, 305, 310–311
toString method, 52, 159 BigInteger class, 12, 24
ArrayStoreException, 148, 223, 235 binary data, reading/writing, 310
ASCII (American Standard Code for binary numbers, 12, 14
Information Interchange), 31–32, binary trees, 254
305 BinaryOperator interface, 129
for property files, 457 binarySearch method (Collections), 252
for source files, 458 bindings, 469
ASM tool, 417 Bindings interface, 469
asMatchPredicate, asPredicate methods BiPredicate interface, 129
(Pattern), 329 BitSet class, 260–261
assert statement, 204–205 collecting streams into, 293
AssertionError, 204 methods of, 261
assertions, 204–206 bitwise operators, 23–24
checking, 401 block statement, labeled, 44
enabling/disabling, 205–206 blocking queues, 370–372
assignment operators, 18–19 BlockingQueue interface, 371
associative operations, 292 Boolean class, 49
asSubclass method (Class), 239 boolean type, 14
asynchronous computations, default value of, 73, 76
353–359 formatting for output, 37
AsyncTask class (Android), 359 reading/writing, 310
atomic operations, 364, 369, 373–375, streams of, 294
379 BooleanSupplier interface, 130
performance and, 374 bootstrap class loader, 174
AtomicXxx classes, 373 boxed method (Xxx Stream), 294
atZone method (LocalDateTime), 430 branches, 38–39
@author tag (javadoc), 96, 99 break statement, 40–41, 43–44
autoboxing, 50, 131 bridge methods, 230–231
AutoCloseable interface, 197, 222 clashes of, 237
close method, 198 BufferedReader class, 308
automatic modules, 489–491 build method (HttpClient), 321
availableCharsets method (Charset), 306 bulk operations, 370
availableProcessors method (Runtime), Byte class, 49
349 MAX_VALUE, MIN_VALUE constants, 11
average method (Xxx Stream), 295 toUnsignedInt method, 12
506 Index
critical sections, 364, 375, 382 deadlocks, 364, 376, 380, 382
Crockford, Douglas, 471 debugging
currencies, 448–449 messages for, 193
formatting, 453 overriding methods for, 151
Currency class, 448 primary arrays for, 52
current method streams, 279
of ProcessHandlex, 389 threads, 385
of ThreadLocalRandomx, 384 with anonymous subclasses, 149–150
with assertions, 204
D DecimalFormat class, 83
d number format patterns of, 453
conversion character, 37 declaration-site variance, 227
formatting symbol (date/time), 436 decomposition
D suffix, 13 of characters, 452
\d, \D, in regular expressions, 326 of classes, 56–57
daemon threads, 385 decrement operator, 20
databases, 397 decrementExact method (Math), 20
persisting objects in, 485 deep copies, 164
DataInput/Output interfaces, 310 deepToString method (Arrays), 159
read/writeXxx methods, 310–311 default label (in switch), 39–41
DataXxx Stream classes, 310 default methods, 114–116
Date class, 421, 436–437 conflicts of, 115–116, 157
DateFormat class, 449 in interfaces, 163
dates default modifier, 114, 405
computing, 428–429 defaultCharset method (Charset), 306, 458
formatting, 433–436, 442, 449–451, defaultReadObject method
453 (ObjectInputStream), 337, 341
local, 424–427 defaultWriteObject method
nonexistent, 427, 431, 450 (ObjectOutputStream), 336–337
parsing, 435 defensive programming, 204
datesUntil method (LocalDate), 426–427 deferred execution, 127–128
DateTimeFormat class, 449–451 defineClass method (ClassLoader), 492
DateTimeFormatter class, 433–436 delete method (Files), 315
format method, 433, 450 deleteIfExists method (Files), 315
legacy classes and, 437 delimiters, for scanners, 308
ofLocalizedXxx methods, 433, 449 @Deprecated annotation, 97, 406–407
ofPattern method, 435 @deprecated tag (javadoc), 97, 407
parse method, 435 Deque interface, 250, 262
toFormat method, 435 destroy, destroyForcibly methods
withLocale method, 434, 450 of Process, 389
DateTimeParseException, 450 of ProcessHandle, 390
daylight savings time, 430–433 DiagnosticCollector class, 465
DayOfWeek enumeration, 63, 426–427, 432 DiagnosticListener interface, 465
getDisplayName method, 435, 450 diamond syntax (<>)
dayOfWeekInMonth method (TemporalAdjusters), for array lists, 49
428 for constructors of generic classes, 221
512 Index
isAssignableFrom method (Class), 173 iterate method (Stream), 274, 279, 294,
isBefore method 367
of LocalDate, 426 Iterator interface
of LocalTime, 429 next, hasNext methods, 252
of ZonedDateTime, 433 remove, removeIf methods, 253
isCancelled method (Future), 351 iterator method
isDirectory method (Files), 314, 316 of Collection, 249
isDone method of ServiceLoader, 178
of CompletableFuture, 354 of Stream, 286
of Future, 351 iterators, 252–253, 286
isEmpty method converting to streams, 275, 296
of BitSet, 261 invalid, 253
of Collection, 249 traversing, 178
of Map, 257 weakly consistent, 368
isEnum method (Class), 172
isEqual method (Predicate), 129–130 J
isFinite, isInfinite methods (Double), 13 j.u.l. See java.util.logging package
isInstance method (Class), 173 JAR files, 85
isInterface method (Modifier), 173, 179 dependencies in, 497
isInterrupted method (Thread), 382 for split packages, 488
isLeapYear method (LocalDate), 426 manifest for, 490
isLocalClass method (Class), 172 modular, 488–489
isLoggable method processing order of, 87
of Filter, 213 resources in, 174, 455
of System.Logger, 209 scanning for deprecated elements, 407
isMemberClass method (Class), 172 jar program, 85
isNamePresent method (Parameter), 183 -C option, 488
isNative method (Modifier), 173, 179 -d option, 488
isNegative method (Duration), 424 --module-version option, 488
isNull method (Objects), 125 Java EE platform, 353
ISO 8601 format, 408 Java Persistence Architecture, 397
ISO 8859-1 encoding, 306, 309 Java Platform Module System, 475
isPresent method (Optional), 283–285 layers in, 489
isPrimitive method (Class), 172 migration to, 489–491
isPrivate, isProtected, isPublic methods no support for versioning in, 477, 480,
(Modifier), 173, 179 488
isRecord method (Class), 172 service loading in, 496–497
isRegularFile method (Files), 314, 316 java program, 4
isSealed method (Class), 172 --add-exports, --add-opens options, 492
isStatic, isStrict, isSynchronized methods --add-module option, 489
(Modifier), 173, 179 -cp (--class-path, -classpath) option,
isSynthetic method (Class), 172 86–87
isVolatile method (Modifier), 173, 179 -da (-disableassertions) option, 205
isZero method (Duration), 424 -ea (-enableassertions) option, 205
Iterable interface, 252–253, 314 -esa (-enablesystemassertions) option, 205
iterator method, 252 --illegal-access option, 492
Index 523