Plenty of tutorials and articles cover the most important changes in Java 8 like lambda expressions and functional streams. But furthermore many existing classes have been enhanced in the JDK 8 API with useful features and methods.
This article covers some of those smaller changes in the Java 8 API - each described with easily understood code samples. Let's take a deeper look into Strings, Numbers, Math and Files.
Two new methods are available on the String class:
chars. The first method joins any number of strings into a single string with the given delimiter:
The second method
chars creates a stream for all characters of the string, so you can use stream operations upon those characters:
Not only strings but also regex patterns now benefit from streams. Instead of splitting strings into streams for each character we can split strings for any pattern and create a stream to work upon as shown in this example:
Additionally regex patterns can be converted into predicates. Those predicates can for example be used to filter a stream of strings:
The above pattern accepts any string which ends with
@gmail.com and is then used as a Java 8
Predicate to filter a stream of email addresses.
Java 8 adds additional support for working with unsigned numbers. Numbers in Java had always been signed. Let's look at
Integer for example:
int represents a maximum of 2³² binary digits. Numbers in Java are per default signed, so the last binary digit represents the sign (0 = positive, 1 = negative). Thus the maximum positive signed
int is 2³¹ - 1 starting with the decimal zero.
You can access this value via
Java 8 adds support for parsing unsigned ints. Let's see how this works:
As you can see it's now possible to parse the maximum possible unsigned number 2³² - 1 into an integer. And you can also convert this number back into a string representing the unsigned number.
This wasn't possible before with
parseInt as this example demonstrates:
The number is not parseable as a signed int because it exceeds the maximum of 2³¹ - 1.
Do the Math
The utility class
Math has been enhanced by a couple of new methods for handling number overflows. What does that mean? We've already seen that all number types have a maximum value. So what happens when the result of an arithmetic operation doesn't fit into its size?
As you can see a so called integer overflow happens which is normally not the desired behavior.
Java 8 adds support for strict math to handle this problem.
Math has been extended by a couple of methods who all ends with
addExact. Those methods handle overflows properly by throwing an
ArithmeticException when the result of the operation doesn't fit into the number type:
The same exception might be thrown when trying to convert longs to int via
Working with Files
The utility class
Files was first introduced in Java 7 as part of Java NIO. The JDK 8 API adds a couple of additional methods which enables us to use functional streams with files. Let's deep-dive into a couple of code samples.
Files.list streams all paths for a given directory, so we can use stream operations like
sorted upon the contents of the file system.
The above example lists all files for the current working directory, then maps each path to it's string representation. The result is then filtered, sorted and finally joined into a string. If you're not yet familiar with functional streams you should read my Java 8 Stream Tutorial.
You might have noticed that the creation of the stream is wrapped into a try/with statement. Streams implement
AutoCloseable and in this case we really have to close the stream explicitly since it's backed by IO operations.
The returned stream encapsulates a DirectoryStream. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.
The next example demonstrates how to find files in a directory or it's sub-directories.
find accepts three arguments: The directory path
start is the initial starting point and
We can achieve the same behavior by utilizing the method
Files.walk. Instead of passing a search predicate this method just walks over any file.
In this example we use the stream operation
filter to achieve the same behavior as in the previous example.
Reading and writing files
Reading text files into memory and writing strings into a text file in Java 8 is finally a simple task. No messing around with readers and writers. The method
Files.readAllLines reads all lines of a given file into a list of strings. You can simply modify this list and write the lines into another file via
Please keep in mind that those methods are not very memory-efficient because the whole file will be read into memory. The larger the file the more heap-size will be used.
As an memory-efficient alternative you could use the method
Files.lines. Instead of reading all lines into memory at once, this method reads and streams each line one by one via functional streams.
If you need more fine-grained control you can instead construct a new buffered reader:
Or in case you want to write to a file simply construct a buffered writer instead:
Buffered readers also have access to functional streams. The method
lines construct a functional stream upon all lines denoted by the buffered reader:
So as you can see Java 8 provides three simple ways to read the lines of a text file, making text file handling quite convenient.
Unfortunately you have to close functional file streams explicitly with try/with statements which makes the code samples still kinda cluttered. I would have expected that functional streams auto-close when calling a terminal operation like
collect since you cannot call terminal operations twice on the same stream anyway.
I hope you've enjoyed this article. All code samples are hosted on GitHub along with plenty of other code snippets from all the Java 8 articles of my blog. If this post was kinda useful to you feel free to star the repo and follow me on Twitter.
Keep on coding!