Movatterモバイル変換


[0]ホーム

URL:


Packt
Search iconClose icon
Search icon CANCEL
Subscription
0
Cart icon
Your Cart(0 item)
Close icon
You have no products in your basket yet
Save more on your purchases!discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Profile icon
Account
Close icon

Change country

Modal Close icon
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timerSALE ENDS IN
0Days
:
00Hours
:
00Minutes
:
00Seconds
Home> Programming> Programming Language> Java Coding Problems
Java Coding Problems
Java Coding Problems

Java Coding Problems: Become an expert Java programmer by solving over 250 brand-new, modern, real-world problems , Second Edition

Arrow left icon
Profile Icon Anghel Leonard
Arrow right icon
S$53.98S$59.99
Full star iconFull star iconFull star iconFull star iconHalf star icon4.5(14 Ratings)
eBookMar 2024798 pages2nd Edition
eBook
S$53.98 S$59.99
Paperback
S$74.99
Subscription
Free Trial
Arrow left icon
Profile Icon Anghel Leonard
Arrow right icon
S$53.98S$59.99
Full star iconFull star iconFull star iconFull star iconHalf star icon4.5(14 Ratings)
eBookMar 2024798 pages2nd Edition
eBook
S$53.98 S$59.99
Paperback
S$74.99
Subscription
Free Trial
eBook
S$53.98 S$59.99
Paperback
S$74.99
Subscription
Free Trial

What do you get with eBook?

Product feature iconInstant access to your Digital eBook purchase
Product feature icon Download this book inEPUB andPDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature iconDRM FREE - Read whenever, wherever and however you want
Product feature iconAI Assistant (beta) to help accelerate your learning
OR

Contact Details

Modal Close icon
Payment Processing...
tickCompleted

Billing Address

Table of content iconView table of contentsPreview book icon Preview Book

Java Coding Problems

Objects, Immutability, Switch Expressions, and Pattern Matching

This chapter includes 30 problems, tackling, among others, some less-known features ofjava.util.Objects, some interesting aspects of immutability, the newest features ofswitch expressions, and deep coverage of the cool pattern matching capabilities ofinstanceof andswitch expressions.

At the end of this chapter, you’ll be up to date with all these topics, which are non-optional in any Java developer’s arsenal.

Problems

Use the following problems to test your programming prowess onObjects, immutability,switch expressions, and pattern matching. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Explaining and exemplifying UTF-8, UTF-16, and UTF-32: Provide a detailed explanation of what UTF-8, UTF-16, and UTF-32 are. Include several snippets of code to show how these work in Java.
  2. Checking a sub-range in the range from 0 to length: Write a program that checks whether the given sub-range [given start,given start+given end) is within the bounds of the range from [0,given length). If the given sub-range is not in the [0,given length) range, then throw anIndexOutOfBoundsException.
  3. Returning an identity string: Write a program that returns a string representation of an object without calling the overriddentoString() orhashCode().
  4. Hooking unnamed classes and instance main methods: Give a quick introduction to JDK 21 unnamed classes and instance main methods.
  5. Adding code snippets in Java API documentation: Provide examples of adding code snippets in Java API documentation via the new@snippet tag.
  6. Invoking default methods fromProxy instances: Write several programs that invoke interfacedefault methods fromProxy instances in JDK 8, JDK 9, and JDK 16.
  7. Converting between bytes and hex-encoded strings: Provide several snippets of code for converting between bytes and hex-encoded strings (including byte arrays).
  8. Exemplify the initialization-on-demand holder design pattern: Write a program that implements the initialization-on-demand holder design pattern in the classical way (before JDK 16) and another program that implements this design pattern based on the fact that, from JDK 16+, Java inner classes can have static members and static initializers.
  9. Adding nested classes in anonymous classes: Write a meaningful example that uses nested classes in anonymous classes (pre-JDK 16, and JDK 16+).
  10. Exemplify erasure vs. overloading: Explain in a nutshell what type erasure in Java and polymorphic overloading are, and exemplify how they work together.
  11. Xlinting default constructors: Explain and exemplify the JDK 16+ hint for classes with default constructors,-Xlint:missing-explicit-ctor.
  12. Working with the receiver parameter: Explain the role of the Java receiver parameter and exemplify its usage in code.
  13. Implementing an immutable stack: Provide a program that creates an immutable stack implementation from zero (implementisEmpty(),push(),pop(), andpeek() operations).
  14. Revealing a common mistake with Strings: Write a simple use case of strings that contain a common mistake (for instance, related to theString immutability characteristic).
  15. Using the enhanced NullPointerException: Exemplify, from your experience, the top 5 causes ofNullPointerException and explain how JDK 14 improves NPE messages.
  16. Using yield in switch expressions: Explain and exemplify the usage of theyield keyword withswitch expressions in JDK 13+.
  17. Tackling the case null clause in switch: Write a bunch of examples to show different approaches for handlingnull values inswitch expressions (including JDK 17+ approaches).
  18. Taking on the hard way to discover equals(): Explain and exemplify howequals() is different from the== operator.
  19. Hooking instanceof in a nutshell: Provide a brief overview with snippets of code to highlight the main aspect of theinstanceof operator.
  20. Introducing pattern matching: Provide a theoretical dissertation including the main aspects and terminology forpattern matching in Java.
  21. Introducing type pattern matching for instanceof: Provide the theoretical and practical support for using thetype pattern matching forinstanceof.
  22. Handling the scope of a binding variable in type patterns for instanceof: Explain in detail, including snippets of code, the scope ofbinding variables intype patterns forinstanceof.
  23. Rewriting equals() via type patterns for instanceof: Exemplify in code the implementation ofequals() (including for generic classes) before and aftertype patterns forinstanceof have been introduced.
  24. Tackling type patterns for instanceof and generics: Provide several examples that use the combotype patterns forinstanceof and generics.
  25. Tackling type patterns for instanceof and streams: Can we usetype patterns forinstanceof and the Stream API together? If yes, provide at least an example.
  26. Introducing type pattern matching for switch:Type patterns are available forinstanceof but are also available forswitch. Provide here the theoretical headlines and an example of this topic.
  27. Adding guarded pattern labels in switch: Provide a brief coverage ofguarded pattern labels inswitch for JDK 17 and 21.
  28. Dealing with pattern label dominance in switch: Pattern label dominance inswitch is a cool feature, so exemplify it here in a comprehensive approach with plenty of examples.
  29. Dealing with completeness (type coverage) in pattern labels for switch: This is another cool topic forswitch expressions. Explain and exemplify it in detail (theory ad examples).
  30. Understanding the unconditional patterns and nulls in switch expressions: Explain hownull values are handled by unconditional patterns ofswitch expressions before and after JDK 19.

The following sections describe solutions to the preceding problems. Remember that there usually isn’t a single correct way to solve a particular problem. Also remember that the explanations shown here include only the most interesting and important details needed to solve the problems. Download the example solutions to see additional details and to experiment with the programs athttps://github.com/PacktPublishing/Java-Coding-Problems-Second-Edition/tree/main/Chapter02.

38. Explain and exemplifying UTF-8, UTF-16, and UTF-32

Character encoding/decoding is important for browsers, databases, text editors, filesystems, networking, and so on, so it’s a major topic for any programmer. Check out thefollowing figure:

Figure 2.1.png

Figure 2.1: Representing text with different char sets

InFigure 2.1, we see several Chinese characters represented in UTF-8, UTF-16, and ANSI on a computer screen. But, what are these? What is ANSI? What is UTF-8 and how did we get to it? Why don’t these characters look normal in ANSI?

Well, the story may begin with computers trying to represent characters (such as letters from the alphabet or digits or punctuation marks). The computers understand/process everything from the real world as a binary representation, so as a sequence of 0 and 1. This means that every character (for instance, A, 5, +, and so on) has to be mapped to a sequence of 0 and 1.

The process of mapping a character to a sequence of 0 and 1 is known ascharacter encodingor simplyencoding. The reverse process of un-mapping a sequence of 0 and 1 to a character is known ascharacter decoding or simplydecoding. Ideally, an encoding-decoding cycle should return the same character; otherwise, we obtain something that we don’t understand or we cannot use.

For instance, the Chinese character,, should be encoded in the computer’s memory as a sequence of 0 and 1. Next, when this sequence is decoded, we expect back the same Chinese letter,. InFigure 2.1, this happens in the left and middle screenshots, while in the right screenshot, the returned character is…. A Chinese speaker will not understand this (actually, nobody will), so something went wrong!

Of course, we don’t have only Chinese characters to represent. We have many other sets of characters grouped inalphabets, emoticons, and so on. A set of characters has well-defined content (for instance, an alphabet has a certainnumber of well-defined characters) and is known as acharacter set or, in short, acharset.

Having a charset, the problem is to define a set of rules (a standard) that clearly explains how the characters of this charset should be encoded/decoded in the computer memory. Without having a clear set of rules, the encoding and decoding may lead to errors or indecipherablecharacters. Such a standard is known as anencoding scheme.

One of the first encoding schemes was ASCII.

Introducing ASCII encoding scheme (or single-byte encoding)

ASCII stands for American Standard Code for Information Interchange. This encoding scheme relies on a 7-bit binary system. In otherwords, each character that is part of the ASCII charset (http://ee.hawaii.edu/~tep/EE160/Book/chap4/subsection2.1.1.1.html) should be representable (encoded) on 7 bits. A 7-bit number can be a decimal between 0 and 127, as in the next figure:

Figure 2.2.png

Figure 2.2: ASCII charset encoding

So, ASCII is an encoding scheme based on a 7-bit system that supports 128 different characters. But, we know that computers operate on bytes (octets) and a byte has 8 bits. This means that ASCII is asingle-byte encoding scheme that leaves a bit free for each byte. See the following figure:

Figure 2.3.png

Figure 2.3: The highlighted bit is left free in ASCII encoding

In ASCII encoding, the letter A is 65, the letter B is 66, and so on. In Java, we can easily check this via the existing API, as in the following simple code:

intdecimalA="A".charAt(0);// 65StringbinaryA= Integer.toBinaryString(decimalA);// 1000001

Or, let’s see the encoding of the textHello World. This time, we added the free bit as well, so the result will be 01001000 01100101 01101100 01101100 01101111 0100000 01010111 01101111 01110010 01101100 01100100:

char[] chars ="Hello World".toCharArray();for(char ch : chars) {  System.out.print("0" + Integer.toBinaryString(ch) +" ");}

If we perform a match, then we see that 01001000 isH, 01100101 ise, 01101100 isl, 01101111 iso, 0100000 is space, 01010111 isW, 01110010 isr, and 01100100 isd. So, besides letters, the ASCII encoding can represent the English alphabet (upper and lower case), digits, space, punctuation marks, and some special characters.

Besides the core ASCII for English, we also have ASCII extensions, which are basically variations of the original ASCII to support other alphabets. Most probably, you’ve heard about the ISO-8859-1 (known as ISO Latin 1), which is a famous ASCII extension. But, even with ASCII extensions, there are still a lot of characters in the world that cannot be encoded yet. There are countries that have a lot more characters than ASCII can encode, and even countries that don’t use alphabets. So, ASCII has its limitations.

I know what you are thinking … let’s use that free bit (27+127). Yes, but even so, we can go up to 256 characters. Stillnot enough! It is time to encode characters using more than 1 byte.

Introducing multi-byte encoding

In different parts of the world, people started tocreate multi-byte encoding schemes (commonly, 2 bytes). For instance, speaker of the Chinese language, which has a lot of characters, created Shift-JIS and Big5, which use 1 or 2 bytes to represent characters.

But, what happens when most of the countries come up with their own multi-byte encoding schemes trying to cover their special characters, symbols, and so on? Obviously, this leads to a huge incompatibility between the encoding schemes used in different countries. Even worse, some countries have multiple encoding schemes that are totally incompatible with each other. For instance, Japan has three different incompatible encoding schemes, which means that encoding a document with one of these encoding schemes and decoding with another will lead to a garbled document.

However, this incompatibility was not such a big issue before the Internet, since which documents have been massively shared all around the globe using computers. At that moment, the incompatibility between the encoding schemes conceived in isolation (for instance, countries and geographical regions) started to be painful.

It was the perfect moment for the Unicode Consortium to be created.

Unicode

In a nutshell, Unicode (https://unicode-table.com/en/) is a universal encoding standard capable of encoding/decodingevery possible character in the world (we are talking about hundreds of thousands of characters).

Unicode needs more bytes torepresent all these characters. But, Unicode didn’t get involved in this representation. It just assigned a number to each character. This number is named acode point. For instance, the letterA in Unicode is associated with the code point 65 in decimal, and we refer to it as U+0041. This is the constant U+ followed by 65 in hexadecimal. As you can see, in Unicode,A is 65, exactly as in the ASCII encoding. In other words, Unicode is backward compatible with ASCII. As you’ll see soon, this is big, so keep it in mind!

Early versions of Unicode contain characters having code points less than 65,535 (0xFFFF). Java represents these characters via the 16-bitchar data type. For instance, the French (e with circumflex) is associated with the Unicode 234 decimal or U+00EA hexadecimal. In Java, we can usecharAt() to reveal this for any Unicode character less than 65,535:

inte="ê".charAt(0);// 234Stringhexe= Integer.toHexString(e);// ea

We also may see the binary representation of this character:

Stringbinarye= Integer.toBinaryString(e);// 11101010 = 234

Later, Unicodeadded more and more characters up to 1,114,112 (0x10FFFF). Obviously, the 16-bit Javachar was not enough to represent these characters, and callingcharAt() was not useful anymore.

Important note

Java 19+ supports Unicode 14.0. Thejava.lang.Character API supports Level 14 of theUnicode Character Database (UCD). In numbers, we have 47 new emojis, 838 new characters, and 5new scripts. Java 20+ supports Unicode 15.0, which means 4,489 new characters forjava.lang.Character.

In addition, JDK 21 has added a set of methods especially for working with emojis based on their code point. Among these methods, we haveboolean isEmoji(int codePoint),boolean isEmojiPresentation(int codePoint),boolean isEmojiModifier(int codePoint),boolean isEmojiModifierBase(intcodePoint),boolean isEmojiComponent(int codePoint), andboolean isExtendedPictographic(int codePoint). In the bundled code, you can find a small application showing you how to fetch all available emojis and check if a given string contains emoji. So, we can easily obtain the code point of a character viaCharacter.codePointAt() and pass it as an argument to these methods to determine whether the character is an emoji or not.

However, Unicode doesn’t get involved in how these code points are encoded into bits. This is the job of special encoding schemes withinUnicode, such as theUnicode Transformation Format (UTF) schemes. Most commonly, we use UTF-32, UTF-16, and UTF-8.

UTF-32

UTF-32 is an encoding scheme for Unicode that represents every code point on 4 bytes (32 bits). For instance, the letterA (having code point 65), which can be encoded on a 7-bit system, is encoded in UTF-32 as in the following figure next to the other two characters:

Figure 2.4.png

Figure 2.4: Three characters sample encoded in UTF-32

As you can see inFigure 2.4, UTF-32 uses 4 bytes (fixed length) to represent every character. In the case of the letterA, we see that UTF-32 wasted 3 bytes of memory. This means that converting an ASCII file to UTF-32 will increase its size by 4 times (for instance, a 1KB ASCII file is a 4KB UTF-32 file). Because of this shortcoming, UTF-32 is not very popular.

Java doesn’t support UTF-32 as a standard charset but it relies onsurrogate pairs (introduced in the next section).

UTF-16

UTF-16 is an encoding scheme forUnicode that represents every code point on 2 or 4 bytes (not on 3 bytes). UTF-16 has a variable length and uses an optionalByte-Order Mark (BOM), but itis recommended to use UTF-16BE (BE stands for Big-Endian byte order), or UTF-16LE (LE stands for Little-Endian byte order). While more details about Big-Endian vs. Little-Endian are available athttps://en.wikipedia.org/wiki/Endianness, the following figure reveals how the orders of bytes differ in UTF-16BE (left side) vs. UTF-16LE (right side) for three characters:

Figure 2.5.png

Figure 2.5: UTF-16BE (left side) vs. UTF-16LE (right side)

Since the figure is self-explanatory, let’s move forward. Now, we have to tackle a trickier aspect of UTF-16. Weknow that in UTF-32, we take thecode point and transform it into a 32-bit number and that’s it. But, in UTF-16, we can’t do that every time because we have code points that don’t accommodate 16 bits. This being said, UTF-16 uses the so-called 16-bitcode units. It can use 1 or 2code units percode point. There are three types of code units, as follows:

  • A code point needs a single code unit: these are 16-bit code units (covering U+0000 to U+D7FF, and U+E000 to U+FFFF)
  • A code point needs 2 code units:
    • The first code unit is namedhigh surrogate and it covers 1,024 values (U+D800 to U+DBFF)
    • The second code unit is namedlow surrogate and it covers 1,024 values (U+DC00 to U+DFFF)

Ahigh surrogate followed by alow surrogate is named asurrogate pair. Surrogate pairs are needed to represent the so-calledsupplementary Unicode characters or characters having a code point larger than 65,535 (0xFFFF).

Characters such as the letter A (65) or the Chinese (26263) have a code point that can be represented via a single code unit. The following figure shows these characters in UTF-16BE:

Figure 2.6.png

Figure 2.6: UTF-16 encoding of A and

This was easy! Now, let’s consider the following figure (encoding of Unicode,Smiling Face with Heart-Shaped Eyes):

Figure 2.7.png

Figure 2.7: UTF-16 encoding using a surrogate pair

The character from this figure has a code point of 128525 (or, 1 F60D) and is represented on 4 bytes.

Check the first byte: the sequence of 6 bits, 110110, identifies a high surrogate.

Check the third byte: the sequence of 6 bits, 110111, identifies a low surrogate.

These 12 bits (identifying the high and low surrogates) can be dropped and we keep the rest of the 20 bits: 00001111011000001101. We can compute this number as 20+ 22+ 23+ 29+ 210+ 212+ 213+ 214+ 215= 1 + 4 + 8 + 512 + 1024 + 4096 + 8192 + 16384 + 32768 = 62989 (or, the hexadecimal, F60D).

Finally, we have to compute F60D + 0x10000 = 1 F60D, or in decimal 62989 + 65536 = 128525 (the code point of this Unicode character). We have to add 0x10000 because the characters that use 2 code units(a surrogate pair) are always of form 1 F…

Java supports UTF-16, UTF-16BE, and UTF-16LE. Actually, UTF-16 is the native character encoding for Java.

UTF-8

UTF-8 is an encoding scheme forUnicode that represents every code point on 1, 2, 3, or 4 bytes. Having this 1- to 4-byte flexibility, UTF-8 uses space in a very efficient way.

Important note

UTF-8 is the most popular encoding scheme that dominates the Internet and applications.

For instance, we know that the code point of the letter A is 65 and it can be encoded using a 7-bit binary representation. The following figure represents this letter encoded in UTF-8:

Figure 2.8.png

Figure 2.8: Letter A encoded in UTF-8

This is very cool! UTF-8 has used a single byte to encode A. The first (leftmost) 0 signals that this is a single-byte encoding. Next, let’s see the Chinese character,:

Figure 2.9.png

Figure 2.9: Chinese character,, encoded in UTF-8

The code point of is 26263, so UTF-8 uses 3 bytes to represent it. The first byte contains 4 bits (1110) that signal that this is a 3-byte encoding. The next two bytes start with 2 bits of 10. All these 8 bitscan be dropped and we keep only the remaining 16 bits, which gives us the expected code point.

Finally, let’s tackle the following figure:

Figure 2.10.png

Figure 2.10: UTF-8 encoding with 4 bytes

This time, the first byte signals that this is a 4-byte encoding via 11110. The remaining 3 bytes start with 10. All these 11 bits can be dropped and we keep only the remaining 21 bits, 000011111011000001101, which gives us the expected code point, 128525.

In the following figure you can see the UTF-8 template used for encoding Unicode characters:

Figure 2.11.png

Figure 2.11: UTF-8 template used for encoding Unicode characters

Did you know that 8 zeros in a row (00000000 – U+0000) are interpreted as NULL? A NULL represents the end of thestring, so sending it “accidentally” will be a problem because the remaining string will not be processed. Fortunately, UTF-8 prevents this issue, and sending a NULL can be done only if we effectively send the U+0000 code point.

Java and Unicode

As long as we use characters withcode points less than 65,535 (0xFFFF), we can rely on thecharAt() method to obtain the code point. Here are some examples:

intcp1="A".charAt(0);// 65Stringhcp1= Integer.toHexString(cp1);// 41Stringbcp1= Integer.toBinaryString(cp1);// 1000001intcp2="".charAt(0);// 26263Stringhcp2= Integer.toHexString(cp2);// 6697Stringbcp2= Integer.toBinaryString(cp2);// 1101100000111101

Based on these examples, we may write a helper method that returns the binary representation of strings having code points less than 65,535 (0xFFFF) as follows (you already saw the imperative version of the following functional code earlier):

publicstatic StringstrToBinary(String str) {Stringbinary= str.chars()     .mapToObj(Integer::toBinaryString)     .map(t ->"0" +  t)     .collect(Collectors.joining(" "));return binary;}

If you run this code against a Unicode character having a code point greater than 65,535 (0xFFFF), then you’ll get the wrong result. You’ll not get an exception or any kind of warning.

So,charAt() covers only a subset of Unicode characters. For covering all Unicode characters, Java provides an API that consists of several methods. For instance, if we replacecharAt() withcodePointAt(), then we obtain the correct code point in all cases, as you can see in the following figure:

Figure 2.12.png

Figure 2.12: charAt() vs. codePointAt()

Check out the last example,c2. SincecodePointAt() returns the correct code point (128525), we can obtain the binary representation as follows:

Stringuc= Integer.toBinaryString(c2);// 11111011000001101

So, if we need a method that returnsthe binary encoding of any Unicode character, then we can replace thechars() call with thecodePoints() call. ThecodePoints() method returns the code points of the given sequence:

publicstatic StringcodePointToBinary(String str) {Stringbinary= str.codePoints()      .mapToObj(Integer::toBinaryString)      .collect(Collectors.joining(" "));return binary;}

ThecodePoints() method is just one of the methods provided by Java to work around code points. The Java API also includescodePointAt(),offsetByCodePoints(),codePointCount(),codePointBefore(),codePointOf(), and so on. You can find several examples of them in the bundled code next to this one for obtaining aString from a given code point:

Stringstr1= String.valueOf(Character.toChars(65));// AStringstr2= String.valueOf(Character.toChars(128525));

ThetoChars() method gets a code point and returns the UTF-16 representation via achar[]. The string returned by the first example (str1) has a length of 1 and is the letter A. The second example returns a string of length 2 since the character having the code point 128525 needs a surrogate pair. The returnedchar[] contains both the high and low surrogates.

Finally, let’s have a helper method that allows us to obtain the binary representation of a string for a given encoding scheme:

publicstatic StringstringToBinaryEncoding(      String str, String encoding) {finalCharsetcharset= Charset.forName(encoding);finalbyte[] strBytes = str.getBytes(charset);finalStringBuilderstrBinary=newStringBuilder();for (byte strByte : strBytes) {for (inti=0; i <8; i++) {        strBinary.append((strByte &128) ==0 ?0 :1);        strByte <<=1;      }      strBinary.append(" ");   }return strBinary.toString().trim();}

Using this method is quitesimple, as you can see in the following examples:

// 00000000 00000000 00000000 01000001Stringr= Charsets.stringToBinaryEncoding("A","UTF-32");// 10010111 01100110Stringr= Charsets.stringToBinaryEncoding("",               StandardCharsets.UTF_16LE.name());

You can practice more examples in the bundled code.

JDK 18 defaults the charset to UTF-8

Before JDK 18, the default charset was determined based on the operating system charset and locale (for instance, on a Windows machine, it could be windows-1252). Starting with JDK 18, the default charset is UTF-8 (Charset.defaultCharset() returns the string, UTF-8). Or, having aPrintStream instance, we can find out the used charset via thecharset() method (starting with JDK 18).

But, the default charset can be explicitly set via thefile.encoding andnative.encoding system properties at the command line. For instance, you may need to perform such modification to compile legacy code developed before JDK 18:

// the default charset is computed from native.encodingjava -Dfile-encoding = COMPAT// the default charset is windows-1252java -Dfile-encoding = windows-1252

So, since JDK 18, classes that use encoding (for instance,FileReader/FileWriter,InputStreamReader/OutputStreamWriter,PrintStream,Formatter,Scanner, andURLEncoder/URLDecoder) can take advantage of UTF-8 out of the box. For instance, using UTF-8 before JDK 18 for reading a file can be accomplished by explicitly specifying this charset encoding scheme as follows:

try (BufferedReaderbr=newBufferedReader(newFileReader(   chineseUtf8File.toFile(), StandardCharsets.UTF_8))) {   ...}

Accomplishing the same thing in JDK 18+ doesn’t require explicitly specifying the charset encoding scheme:

try (BufferedReaderbr=newBufferedReader(newFileReader(chineseUtf8File.toFile()))) {   ...}

However, forSystem.out andSystem.err, JDK 18+ still uses the default system charset. So, if you are usingSystem.out/err and you see question marks (?) instead of the expected characters, then most probably you should set UTF-8 via the new properties-Dstdout.encoding and-Dstderr.encoding:

-Dstderr.encoding=utf8 -Dstdout.encoding=utf8

Or, you can set them as environment variables to set them globally:

_JAVA_OPTIONS="-Dstdout.encoding=utf8 -Dstderr.encoding=utf8"

In the bundled code you can see more examples.

39. Checking a sub-range in the range from 0 to length

Checking that a given sub-range is in the range from 0 to the given length is a common check in a lot of problems. For instance, let’s consider that we have to write a function responsible for checking if the clientcan increase the pressure in a water pipe. The client gives us the current average pressure (avgPressure), the maximum pressure (maxPressure), and the amount of extra pressure that should be applied (unitsOfPressure).

But, before we can apply our secret algorithm, we have to check that the inputs are correct. So, we have to ensure that none of the following cases happens:

  • avgPressure is less than 0
  • unitsOfPressure is less than 0
  • maxPressure is less than 0
  • The range [avgPressure,avgPressure +unitsOfPressure) is out of bounds represented bymaxPressure

So, in code lines, our function may look as follows:

publicstaticbooleanisPressureSupported(int avgPressure,int unitsOfPressure,int maxPressure) {if(avgPresure <0 || unitsOfPressure <0 || maxPressure <0    || (avgPresure + unitsOfPressure) > maxPressure) {thrownewIndexOutOfBoundsException("One or more parameters are out of bounds");  }// the secret algorithmreturn (avgPressure + unitsOfPressure) <    (maxPressure - maxPressure/4);}

Writing composite conditions such as ours is prone to accidental mistakes. It is better to rely on the Java API whenever possible. And, for this use case, it is possible! Starting with JDK 9, injava.util.Objects, we have the methodcheckFromIndexSize(int fromIndex, int size, int length), and starting with JDK 16, we also have a flavor forlong arguments,checkFromIndexSize(int fromIndex, int size, int length). If we consider thatavgPressure isfromIndex,unitsOfPressure issize, andmaxPressure islength, thencheckFromIndexSize() performs the arguments validation and throws anIndexOutOfBoundsException if something goes wrong. So, we write the code as follows:

publicstaticbooleanisPressureSupported(int avgPressure,int unitsOfPressure,int maxPressure) {  Objects.checkFromIndexSize(    avgPressure, unitsOfPressure, maxPressure);// the secret algorithmreturn (avgPressure + unitsOfPressure) <   (maxPressure - maxPressure/4);}

In the code bundle, you can see one more example of usingcheckFromIndexSize().

BesidescheckFromIndexSize(), injava.util.Objects, we can find several other companions that cover common composite conditions such ascheckIndex(int index, int length) – JDK 9,checkIndex(long index, long length) – JDK 16,checkFromToIndex(int fromIndex, int toIndex, int length) – JDK 9, andcheckFromToIndex(long fromIndex, long toIndex, long length) – JDK 16.

And, by the way, if we switch the context to strings, then JDK 21 provides an overload of the well-knownString.indexOf(), capable of searching a character/substring in a given string between a given begin index and end index. The signature isindexOf(String str, int beginIndex, int endIndex) and it returns the index of the first occurrence ofstr, or -1 ifstr is not found. Basically, this is a neat version ofs.substring(beginIndex, endIndex).indexOf(str) + beginIndex.

40. Returning an identity string

So, what’s anidentity string? An identity string is a string built from an object without calling the overriddentoString() orhashCode(). It is equivalent to the following concatenation:

object.getClass().getName() +"@"   + Integer.toHexString(System.identityHashCode(object))

Starting with JDK 19, this string is wrapped inObjects.toIdentityString(Object object). Consider the following class (object):

publicclassMyPoint {privatefinalint x;privatefinalint y;privatefinalint z;  ...@Overridepublic StringtoString() {return"MyPoint{" +"x=" + x +", y=" + y                       +", z=" + z +'}';  }  }

By callingtoIdentityString(), we obtain something as follows:

MyPointp=newMyPoint(1,2,3);// modern.challenge.MyPoint@76ed5528Objects.toIdentityString(p);

Obviously, the overriddenMyPoint.toString() method was not called. If we print out the hash code ofp, we get76ed5528, which is exactly whattoIdentityString() returned. Now, let’s overridehashCode() as well:

@OverridepublicinthashCode() {inthash=7;  hash =23 * hash +this.x;  hash =23 * hash +this.y;  hash =23 * hash +this.z;return hash;}

This time,toIdentityString() returns the same thing, while ourhashCode() returns14ef3.

41. Hooking unnamed classes and instance main methods

Imagine that you have to initiate a student in Java. The classical approach of introducing Java is to show the student aHello World! Example, as follows:

publicclassHelloWorld {publicstaticvoidmain(String[] args) {     System.out.println("Hello World!");  }}

This is the simplest Java example but it is not simple to explain to the student whatpublic orstatic orString[] are. The ceremony involved in this simple example may scare the student –if this is a simple example, then how is it a more complex one?

Fortunately, starting with JDK 21 (JEP 445), we haveinstance main methods, which is a preview feature that allows us to shorten the previous example as follows:

publicclassHelloWorld {voidmain() {     System.out.println("Hello World!");  }}

We can even go further and remove theexplicit class declaration as well. This feature is known asunnamed classes. An unnamed class resides in the unnamed package that resides in the unnamed module:

voidmain() {   System.out.println("Hello World!");}

Java will generate the class on our behalf. The name of the class will be the same as the name of the source file.

That’s all we need to introduce Java to a student. I strongly encourage you to read JEP 445 (and the new JEPs that will continue this JDK 21 preview feature work) to discover all the aspects involved in these features.

42. Adding code snippets in Java API documentation

I’m sure that you are familiar with generatingJava API documentation (Javadoc) for your projects. We can do it via thejavadoc tool from the command line, via IDE support, via the Maven plugin (maven-javadoc-plugin), and so on.

A common case in writing the Javadoc consists of adding snippets of code to exemplify the usage of a non-trivial class or method. Before JDK 18, adding snippets of code in documentation can be done via{@code...} or the<pre> tag. The added code is treated as plain text, is not validated for correctness, and is not discoverable by other tools. Let’s quickly see an example:

/** * A telemeter with laser ranging from 0 to 60 ft including * calculation of surfaces and volumes with high-precision * * <pre>{@code *     Telemeter.Calibrate.at(0.00001); *     Telemeter telemeter = new Telemeter(0.15, 2, "IP54"); * }</pre> */publicclassTelemeter {   ...

In the bundled code, you can see the full example. The Javadoc is generated at build time via the Maven plugin (maven-javadoc-plugin), so simply trigger a build.

Starting with JDK 18 (JEP 413 -Code Snippets in Java API Documentation), we have brand new support for adding snippets of code in documentation via the{@snippet...} tag. The code added via@snippet can be discovered and validated by third-party tools (not by thejavadoc tool itself).

For instance, the previous snippet can be added via@snippet as follows:

/** * A telemeter with laser ranging from 0 to 60 ft including * calculation of surfaces and volumes with high-precision * * {@snippet : *     Telemeter.Calibrate.at(0.00001); *     Telemeter telemeter = new Telemeter(0.15, 2, "IP54"); * } */publicclassTelemeter {   ...

A screenshot of the output is in the following figure:

Figure 2.13.png

Figure 2.13: Simple output from @snippet

The effective code starts from the newline placed after the colon (:) and ends before the closing right curly bracket (}). The code indentation is treated as in code blocks, so the compiler removes the incidental white spaces and we can indent the code with respect to the closing right curly bracket (}). Check out the following figure:

Figure 2.14.png

Figure 2.14: Indentation of code snippets

In the top example, the closing right curly bracket is aligned under the opening left curly bracket, while in the bottom example, we shifted the closing right curly bracket to the right.

Adding attributes

We can specify attributes for a@snippet vianame=value pairs. For instance, we can provide a tip about the programming language of our snippet via thelang attribute. The value of the attribute is available to external tools and is present in the generated HTML. Here are two examples:

 * {@snippet lang="java" : *     Telemeter.Calibrate.at(0.00001); *Telemetertelemeter=newTelemeter(0.15,2,"IP54"); * }

In the generated HTML, you’ll easily identify this attribute as:

<codeclass="language-java"></code>

If the code is a structured text such as aproperties file, then you can follow this example:

 * {@snippet lang="properties" : *   telemeter.precision.default=42 *   telemeter.clazz.default=2 * }

In the generated HTML, you’ll have:

<codeclass="language-properties"></code>

Next, let’s see how can we alterwhat is displayed in a snippet.

Using markup comments and regions

We can visually alter a snippet of code viamarkup comments. A markup comment occurs at the end of the line and it contains one or moremarkup tags of the form@name args, whereargs are commonlyname=value pairs. Common markup comments include highlighting, linking, and content (text) modifications.

Highlighting

Highlighting a whole line can be done via@highlight without arguments, as in the following figure:

Figure 2.15.png

Figure 2.15: Highlighting a whole line of code

As you can see in this figure, the first line of code was bolded.

If we want to highlight multiple lines, then we can defineregions. A region can be treated as anonymous or have an explicit name. An anonymous region is demarcated by the wordregion placed as an argument of the markup tag and the@end tag placed at the end of the region. Here is an example for highlighting two regions (an anonymous one and a named one (R1)):

Figure 2.16.png

Figure 2.16: Highlighting a block of code using regions

Regular expressions allowus to highlight a certain part of the code. For instance, highlighting everything that occurs between quotes can be done via@highlight regex='".*"'. Or, highlighting only the wordCalibrate can be done via thesubstring="Calibrate" argument, as in the following figure:

Figure 2.17.png

Figure 2.17: Highlighting only the word “Calibrate”

Next, let’s talk about adding links in code.

Linking

Adding links in code can be done via the@link tag. The common arguments aresubstring="…" andtarget="…". For instance, the following snippet provides a link for the textCalibrate that navigates in documentation to the description of theCalibrate.at() method:

Figure 2.18.png

Figure 2.18: Adding links in code

Next, let’s see how we canmodify the code’s text.

Modifying the code’s text

Sometimes we may need to alter the code’s text. For instance, instead ofTelemeter.Calibrate.at(0.00001, "HIGH");, we want to render in documentationTelemeter.Calibrate.at(eps, "HIGH");. So, we need to replace0.00001 witheps. This is the perfect job for the@replace tag. Common arguments includesubstring="…" (or,regex="…") andreplacement="...". Here is the snippet:

Figure 2.19.png

Figure 2.19: Replacing the code’s text

If you need to perform multiple replacements in a block of code, then rely on regions. In the following example, we apply a regular expression to a block of code:

Figure 2.20.png

Figure 2.20: Applying multiple replacements via a simple regex and an anonymous region

If you need to perform more replacements on the same line, then just chain multiple@replace tags (this statement applies to all tags such as@highlight,@link, and so on).

Using external snippets

So far, we have used only inlinedsnippets. But, there are scenarios when using inlined snippets is not a convenient approach (for instance, if we need to repeat some parts of the documentation) or it is not possible to use them (for instance, if we want to embed/*…*/ comments, which cannot be added in inlined snippets).

For such cases, we can use external snippets. Without any further configurations, JDK automatically recognizes external snippets if they are placed in a subfolder of the package (folder) containing the snippet tag. This subfolder should be namedsnippet-files and it can contain external snippets as Java sources, plain text files, or properties files. In the following figure, we have a single external file namedMainSnippet.txt:

Figure 2.21.png

Figure 2.21: External snippets in snippet-files

If the external snippet is not a Java file, then it can be loaded via{@snippet file …} as follows:

{@snippet file = MainSnippet.txt}{@snippet file ="MainSnippet.txt"}{@snippet file ='MainSnippet.txt'}

But, we can also customize the place and folder name of external snippets. For instance, let’s place the external snippets in a folder namedsnippet-src, as follows:

Figure 2.22.png

Figure 2.22: External snippets in a custom folder and place

This time, we have to instruct the compiler where to find the external snippets. This is done by passing the--snippet-path option tojavadoc. Of course, you can pass it via the command line, via your IDE, or viamaven-javadoc-plugin, as follows:

<additionalJOption>  --snippet-path C:\...\src\snippet-src</additionalJOption>

This path is relative to yourmachine, so feel free to adjust it accordingly inpom.xml.

Next,AtSnippet.txt andParamDefaultSnippet.properties can be loaded exactly as you saw earlier forMainSnippet.txt. However, loading Java sources, such asDistanceSnippet.java, can be done via{@snippet class…}, as follows:

{@snippet class = DistanceSnippet}{@snippet class ="DistanceSnippet"}{@snippet class ='DistanceSnippet'}

But, do not add explicitly the.java extension because you’ll get an error such asfile not found on source path or snippet path: DistanceSnippet/java.java:

{@snippet class = DistanceSnippet.java}

When using Java sources as external snippets, pay attention to the following note.

Important note

Even if the predefinedsnippet-files name is an invalid name for a Java package, some systems may treat this folder as being part of the package hierarchy. In such cases, if you place Java sources in this folder, you’ll get an error such asIllegal package name: “foo.buzz.snippet-files”. If you find yourself in this scenario, then simply use another folder name and location for the documentation external snippets written in Java sources.

Regions in external snippets

The external snippets support regions via@start region=… and@end region=…. For instance, inAtSnippet.txt, we have the following region:

// This is an example used in the documentation// @start region=only-code   Telemeter.Calibrate.at(0.00001,"HIGH");// @end region=only-code

Now, if we load the region as:

{@snippet file = AtSnippet.txt region=only-code}

We obtain only the code from the region without the text,// This is an example used in the documentation.

Here is another example of a properties file with two regions:

# @start region=distsc=[0,0]ec=[0,0]interpolation=false# @end region=dist# @start region=ateps=0.1type=null# @end region=at

The regiondist is used to show the default values for the arguments of thedistance() method in the documentation:

Figure 2.23.png

Figure 2.23: Using the dist region

And, theat region is used to show the default values for the arguments of theat() method in the documentation:

Figure 2.24.png

Figure 2.24: Using the “at” region

In external snippets, we can use the same tags as in the inlined snippets. For instance, in the following figure, you can see the complete source ofAtSnippet.txt:

Figure 2.25.png

Figure 2.25: Source of AtSnippet.txt

Notice the presence of@highlight and@replace.

Important note

Starting with JDK 19, the Javadoc search feature was also improved. In other words, JDK 19+ can generate a standalone search page for searching in the Javadoc API documentation. Moreover, the search syntax has been enhanced to support multiple search words.

You can practice these examples in the bundled code.

43. Invoking default methods from Proxy instances

Starting with JDK 8, we can definedefault methods in interfaces. For instance, let’s consider the following interfaces (for brevity, all methods from these interfaces are declared asdefault):

Figure 2.26.png

Figure 2.26: Interfaces: Printable, Writable, Draft, and Book

Next, let’s assume that we want to use the Java Reflection API to invoke these default methods. As a quick reminder, theProxy class goal is used to provide support for creating dynamic implementations of interfaces at runtime.

That being said, let’s see how we can use the Proxy API for calling ourdefault methods.

JDK 8

Calling adefault method of an interface in JDK 8 relies on a little trick. Basically, we create from scratch apackage-private constructor from the Lookup API. Next, we make this constructor accessible – this means that Java will not check the access modifiers to this constructor and, therefore, will not throw anIllegalAccessException when we try to use it. Finally, we use this constructor to wrap an instance of an interface (for instance,Printable) and use reflective access to thedefault methods declared in this interface.

So, in code lines, we can invoke the default methodPrintable.print() as follows:

// invoke Printable.print(String)Printablepproxy= (Printable) Proxy.newProxyInstance(  Printable.class.getClassLoader(),newClass<?>[]{Printable.class}, (o, m, p) -> {if (m.isDefault()) {      Constructor<Lookup> cntr = Lookup.class        .getDeclaredConstructor(Class.class);      cntr.setAccessible(true);return cntr.newInstance(Printable.class)                 .in(Printable.class)                 .unreflectSpecial(m, Printable.class)                 .bindTo(o)                 .invokeWithArguments(p);      }returnnull;  });// invoke Printable.print()pproxy.print("Chapter 2");

Next, let’s focus on theWritable andDraft interfaces.Draft extendsWritable and overrides thedefault write()method. Now, every time we explicitly invoke theWritable.write() method, we expect that theDraft.write() method is invoked automatically behind the scenes. A possible implementation looks as follows:

// invoke Draft.write(String) and Writable.write(String)Writabledpproxy= (Writable) Proxy.newProxyInstance( Writable.class.getClassLoader(),newClass<?>[]{Writable.class, Draft.class}, (o, m, p) -> {if (m.isDefault() && m.getName().equals("write")) {    Constructor<Lookup> cntr = Lookup.class     .getDeclaredConstructor(Class.class);    cntr.setAccessible(true);     cntr.newInstance(Draft.class)        .in(Draft.class)        .findSpecial(Draft.class,"write",           MethodType.methodType(void.class, String.class),            Draft.class)        .bindTo(o)        .invokeWithArguments(p);return cntr.newInstance(Writable.class)        .in(Writable.class)        .findSpecial(Writable.class,"write",           MethodType.methodType(void.class, String.class),            Writable.class)        .bindTo(o)        .invokeWithArguments(p);    }returnnull;  });// invoke Writable.write(String)dpproxy.write("Chapter 1");

Finally, let’s focus on thePrintable andBook interfaces.Book extendsPrintable and doesn’t define any methods. So, when we call the inheritedprint() method, we expect that thePrintable.print() method is invoked. While you can check this solution in the bundled code, let’s focus on the same tasks using JDK 9+.

JDK 9+, pre-JDK 16

As you just saw, before JDK 9, the Java Reflection API provides access to non-public class members. This means that external reflective code (for instance, third-party libraries) can have deep access toJDK internals. But, starting with JDK 9, this is not possible because the new module system relies on strong encapsulation.

For a smooth transition from JDK 8 to JDK 9, we can use the--illegal-access option. The values of this option range fromdeny (sustains strong encapsulation, so no illegal reflective code is permitted) topermit (the most relaxed level of strong encapsulation, allowing access to platform modules only from unnamed modules). Betweenpermit (which is the default in JDK 9) anddeny, we have two more values:warn anddebug. However,--illegal-access=permit; support was removed in JDK 17.

In this context, the previous code may not work in JDK 9+, or it might still work but you’ll see a warning such asWARNING: An illegal reflective access operation has occurred.

But, we can “fix” our code to avoid illegal reflective access viaMethodHandles. Among its goodies, this class exposes lookup methods for creating method handles for fields and methods. Once we have aLookup, we can rely on itsfindSpecial() method to gain access to thedefault methods of an interface.

Based onMethodHandles, we can invoke the default methodPrintable.print() as follows:

// invoke Printable.print(String doc)Printablepproxy= (Printable) Proxy.newProxyInstance(    Printable.class.getClassLoader(),newClass<?>[]{Printable.class}, (o, m, p) -> {if (m.isDefault()) {return MethodHandles.lookup()         .findSpecial(Printable.class,"print",             MethodType.methodType(void.class, String.class),            Printable.class)         .bindTo(o)         .invokeWithArguments(p);      }returnnull;  });// invoke Printable.print()pproxy.print("Chapter 2");

While in the bundled code, youcan see more examples; let’s tackle the same topic startingwith JDK 16.

JDK 16+

Starting with JDK 16, we can simplify the previous code thanks to the new static method,InvocationHandler.invokeDefault(). As its name suggests, this method is useful for invokingdefault methods. In code lines, our previous examples for callingPrintable.print() can be simplified viainvokeDefault() as follows:

// invoke Printable.print(String doc)Printablepproxy= (Printable) Proxy.newProxyInstance(  Printable.class.getClassLoader(),newClass<?>[]{Printable.class}, (o, m, p) -> {if (m.isDefault()) {return InvocationHandler.invokeDefault(o, m, p);      }returnnull;  });// invoke Printable.print()pproxy.print("Chapter 2");

In the next example, every time we explicitly invoke theWritable.write() method, we expect that theDraft.write() method is invoked automatically behind the scenes:

// invoke Draft.write(String) and Writable.write(String)Writabledpproxy= (Writable) Proxy.newProxyInstance( Writable.class.getClassLoader(),newClass<?>[]{Writable.class, Draft.class}, (o, m, p) -> {if (m.isDefault() && m.getName().equals("write")) {MethodwriteInDraft= Draft.class.getMethod(     m.getName(), m.getParameterTypes());    InvocationHandler.invokeDefault(o, writeInDraft, p);return InvocationHandler.invokeDefault(o, m, p);   }returnnull; });// invoke Writable.write(String)dpproxy.write("Chapter 1");

In the bundled code, you can practice more examples.

44. Converting between bytes and hex-encoded strings

Converting bytes to hexadecimal (and vice versa) is a common operation in applications that manipulate fluxes of files/messages, performencoding/decoding tasks, process images, and so on.

A Java byte is a number in the [-128, +127] range and is represented using 1 signed byte (8 bits). A hexadecimal (base 16) is a system based on 16 digits (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F). In other words, those 8 bits of a byte value accommodate exactly 2 hexadecimal characters in the range 00 to FF. The decimal <-> binary <-> hexadecimal mapping is resumed in the following figure:

Figure 2.27.png

Figure 2.27: Decimal to binary to hexadecimal conversion

For instance, 122 in binary is 01111010. Since 0111 is in hexadecimal 7, and 1010 is A, this results in 122 being 7A in hexadecimal (also written as 0x7A).

How about a negative byte? We know from the previous chapter that Java represents a negative number astwo’s complement of the positive number. This means that -122 in binary is 10000110 (retain the first 7 bits of positive 122 = 1111010, flip(1111010) = 0000101, add(0000001) = 00000110, and append sign bit 1, 10000110) and in hexadecimal, is 0x86.

Converting a negative number tohexadecimal can be done in several ways, but we can easily obtain the lower 4 bits as 10000110 & 0xF = 0110, and the higher four bits as (10000110>> 4) & 0xF = 1000 & 0xF = 1000 (here, the 0xF (binary, 1111) mask is useful only for negative numbers). Since, 0110 = 6 and 1000 = 8, we see that 10000110 is in hexadecimal 0x86.

If you need a deep coverage of bits manipulation in Java or you simply face issues in understanding the current topic, then please consider the bookThe Complete Coding Interview Guide in Java, especiallyChapter 9.

So, in code lines, we can rely on this simple algorithm andCharacter.forDigit(int d, int r), which returns the character representation for the given digit (d) in the given radix (r):

publicstatic StringbyteToHexString(byte v) {inthigher= (v >>4) &0xF;intlower= v &0xF;Stringresult= String.valueOf(newchar[]{      Character.forDigit(higher,16),      Character.forDigit(lower,16)}    );return result;}

There are many other ways to solve this problem (in the bundled code, you can see another flavor of this solution). For example, if we know that theInteger.toHexString(int n) method returns a string that represents the unsigned integer in base 16 of the given argument, then all we need is to apply the 0xFF (binary, 11111111) mask for negatives as:

publicstatic StringbyteToHexString(byte v) {return Integer.toHexString(v &0xFF);}

If there is an approach that we should avoid, then that is the one based onString.format(). TheString.format("%02x ", byte_nr) approach is concise but very slow!

How about the reverse process? Converting a given hexadecimal string (for instance, 7d, 09, and so on) to a byte is quite easy. Just take the first (d1) and second (d2) character of the given string and apply the relation,(byte) ((d1 << 4) + d2):

publicstaticbytehexToByte(String s) {intd1= Character.digit(s.charAt(0),16);intd2= Character.digit(s.charAt(1),16);return (byte) ((d1 <<4) + d2);}

More examples are available in the bundled code. If you rely on third-party libraries, then check Apache Commons Codec (Hex.encodeHexString()), Guava (BaseEncoding), Spring Security (Hex.encode()), Bouncy Castle (Hex.toHexString()), and so on.

JDK 17+

Starting with JDK 17, we can use thejava.util.HexFormat class. This class has plenty of static methods for handlinghexadecimal numbers, includingString toHexDigits(byte value) andbyte[]parseHex(CharSequence string). So, we can convert a byte to a hexadecimal string as follows:

publicstatic StringbyteToHexString(byte v) {HexFormathex= HexFormat.of();return hex.toHexDigits(v);}

And, vice versa as follows:

publicstaticbytehexToByte(String s) {HexFormathex= HexFormat.of();return hex.parseHex(s)[0];}

In the bundled code, you can also see the extrapolation of these solutions for converting an array ofbytes (byte[]) to aString, and vice versa.

45. Exemplify the initialization-on-demand holder design pattern

Before we tackle the solution of implementing the initialization-on-demand holder design pattern, let’s quickly recap a few ingredients of this solution.

Static vs. non-static blocks

In Java, we can haveinitialization non-static blocksandstatic blocks. An initialization non-static block (or simply, a non-static block) is automatically called every single time we instantiate the class. On theother hand, an initialization static block (or simply, a static block) is called a single time when the class itself is initialized. No matter how many subsequent instances of that class we create, the static block will never get executed again. In code lines:

publicclassA {  {    System.out.println("Non-static initializer ...");  }static {    System.out.println("Static initializer ...");  }}

Next, let’s run the following test code to create three instances ofA:

Aa1=newA();Aa2=newA();Aa3=newA();

The output reveals that the static initializer is called only once, while the non-static initializer is called three times:

Static initializer ...Non-static initializer ...Non-static initializer ...Non-static initializer ...

Moreover, the static initializer is called before the non-static one. Next, let’s talk about nested classes.

Nested classes

Let’s look at a quickexample:

publicclassA {privatestaticclassB { ... }}

Nested classes can be static or non-static. A non-static nested class is referred to as aninner class; further, it can be alocal inner class(declared in a method) or an anonymous inner class(class with no name). On the other hand, a nested class that is declared static is referred to as astatic nested class. The following figure clarifies these statements:

Figure 2.28.png

Figure 2.28: Java nested classes

SinceB is a static class declared inA, we say thatB is a static nested class.

Tackling the initialization-on-demand holder design pattern

The initialization-on-demand holder design pattern refers to a thread-safe lazy-loaded singleton (single instance) implementation. BeforeJDK 16, we can exemplify this design pattern in code as follows (we want a single thread-safe instance ofConnection):

publicclassConnection {// singletonprivateConnection() {  }privatestaticclassLazyConnection{// holderstaticfinalConnectionINSTANCE=newConnection();static {      System.out.println("Initializing connection ..."         + INSTANCE);    }  }publicstatic Connectionget() {return LazyConnection.INSTANCE;  }}

No matter how many times a thread (multiple threads) callsConnection.get(), we always get the same instance ofConnection. This is the instance created when we calledget() for the first time (first thread), and Java has initialized theLazyConnection class and its statics. In other words, if we never callget(), then theLazyConnection class and its statics are never initialized (this is why we name it lazy initialization). And, this is thread-safe because static initializers can be constructed (here,INSTANCE) and referenced without explicit synchronization since they are run before any thread can use the class (here,LazyConnection).

JDK 16+

Until JDK 16, an inner class couldcontain static members as constant variables but it couldn’t contain static initializers. In other words, the following code would not compile because of the static initializer:

publicclassA {publicclassB {    {      System.out.println("Non-static initializer ...");    }static {      System.out.println("Static initializer ...");    }  }}

But, starting with JDK 16, the previous code is compiled without issues. In other words, starting with JDK 16, Java inner classes can have static members and static initializers.

This allows us to tackle the initialization-on-demand holder design pattern from another angle. We can replace the static nested class,LazyConnection, with a local inner class as follows:

publicclassConnection {// singletonprivateConnection() {  }publicstatic Connectionget() {classLazyConnection{// holderstaticfinalConnectionINSTANCE=newConnection();static {        System.out.println("Initializing connection ..."           + INSTANCE);      }    }return LazyConnection.INSTANCE;  }}

Now, theLazyConnection is visible only in its containing method,get(). As long as we don’t call theget() method, the connection will not be initialized.

46. Adding nested classes in anonymous classes

In the previous problem, we had a brief overview of nested classes. As a quick reminder, an anonymous class (or, anonymous inner class) is like a local inner class without a name. Their purpose is toprovide a more concise and expressive code. However, the code readability may be affected (look ugly), but it may be worth it if you can perform some specific task without having to do a full-blown class. For instance, an anonymous class is useful for altering the behavior of an existing method without spinning a new class. Java uses them typically for event handling and listeners (in GUI applications). Probably the most famous example of an anonymous class is this one from Java code:

button.addActionListener(newActionListener() {publicvoidactionPerformed(ActionEvent e) {    ...  }}

Nevertheless, while local inner classes are actually class declarations, anonymous classes are expressions. To createan anonymous class, we have to extend an existing class or implement an interface, as shown in the following figure:

Figure 2.28.png

Figure 2.29: Anonymous class via class extension and interface implementation

Because they don’t have names, anonymous classes must be declared and instantiated in a single expression. The resulting instance can be assigned to a variable that can be referred to later. The standard syntax for expressions looks like calling a regular Java constructor having the class in a code block ending with a semi-colon (;). The presence of a semi-colon is a hint that an anonymous class is an expression that must be part of a statement.

Finally, anonymous classes cannot have explicit constructors, be abstract, have a single instance, implement multiple interfaces, or be extended.

Next, let’s tackle a few examples of nesting classes in anonymous classes. For instance, let’s consider the following interface of a printing service:

publicinterfacePrinter {publicvoidprint(String quality);}

We use thePrinter interface all over the place in our printing service, but we also want to have a helper method that is compact and simply tests our printer functions without requiring further actions or an extra class. We decided to hide this code in a static method namedprinterTest(), as follows:

publicstaticvoidprinterTest() {Printerprinter=newPrinter() {@Overridepublicvoidprint(String quality) {if ("best".equals(quality)) {Toolstools=newTools();      tools.enableLaserGuidance();      tools.setHighResolution();    }    System.out.println("Printing photo-test ...");  }classTools {privatevoidenableLaserGuidance() {      System.out.println("Adding laser guidance ...");    }privatevoidsetHighResolution() {      System.out.println("Set high resolution ...");    }  }};

Testing thebest quality print requires some extra settings wrapped in the innerTools class. As you can see, the innerTools class is nested in the anonymous class. Another approach consists of moving theTools class inside theprint() method. So,Tools becomes a local inner class as follows:

Printerprinter=newPrinter() {@Overridepublicvoidprint(String quality) {classTools {privatevoidenableLaserGuidance() {        System.out.println("Adding laser guidance ...");      }privatevoidsetHighResolution() {        System.out.println("Set high resolution ...");      }    }if ("best".equals(quality)) {Toolstools=newTools();      tools.enableLaserGuidance();      tools.setHighResolution();    }    System.out.println("Printing photo-test ...");  }};

The problem with this approach is that theTools class cannot be used outside ofprint(). So, this strictencapsulation will restrict us from adding a new method (next toprint()) that also needs theTools class.

JDK 16+

But, remember from the previous problem that, starting with JDK 16, Java inner classes can have static members and static initializers. This means that we can drop theTools class and rely on two static methods as follows:

Printerprinter=newPrinter() {@Overridepublicvoidprint(String quality) {if ("best".equals(quality)) {      enableLaserGuidance();      setHighResolution();    }    System.out.println("Printing your photos ...");  }privatestaticvoidenableLaserGuidance() {    System.out.println("Adding laser guidance ...");  }privatestaticvoidsetHighResolution() {    System.out.println("Set high resolution ...");  }};

If you find it more convenientto pick up these helpers in a static class, then do it:

Printerprinter=newPrinter() {@Overridepublicvoidprint(String quality) {if ("best".equals(quality)) {      Tools.enableLaserGuidance();      Tools.setHighResolution();    }    System.out.println("Printing photo-test ...");  }privatefinalstaticclassTools {privatestaticvoidenableLaserGuidance() {      System.out.println("Adding laser guidance ...");    }privatestaticvoidsetHighResolution() {      System.out.println("Set high resolution ...");    }  }};

You can practice these examples in the bundled code.

47. Exemplify erasure vs. overloading

Before we join them in anexample, let’s quickly tackle erasure and overloading separately.

Erasure in a nutshell

Java usestype erasure at compile time inorder to enforce type constraints and backward compatibility with old bytecode. Basically, at compilation time, all type arguments are replaced byObject (any generic must be convertible toObject) or type bounds (extends orsuper). Next, at runtime, the type erased by the compiler will be replaced by our type. A common case of type erasure implies generics.

Erasure of generic types

Practically, the compiler erases the unbound types (such asE,T,U, and so on) with the boundedObject. This enforces type safety, as in the following example ofclass type erasure:

publicclassImmutableStack<E>implementsStack<E> {privatefinal E head;privatefinal Stack<E> tail;  ...

The compiler applies type erasure to replaceE withObject:

publicclassImmutableStack<Object>implementsStack<Object> {privatefinal Object head;privatefinal Stack<Object> tail;  ...

If theE parameter is bound, then the compiler uses the first bound class. For instance, in a class such asclass Node<T extends Comparable<T>> {...}, the compiler will replaceT withComparable. In the same manner, in a class such asclass Computation<T extends Number> {...}, all occurrences ofT would be replaced by the compiler with the upper boundNumber.

Check out the following case, which is a classical case ofmethod type erasure:

publicstatic <T, RextendsT> List<T> listOf(T t, R r) {  List<T> list =newArrayList<>();  list.add(t);  list.add(r);return list;}// use this methodList<Object> list = listOf(1,"one");

How does this work? When we calllistOf(1, "one"), we are actually passing two different types to the generic parametersT andR. The compiler type erasure has replacedT withObject. In this way, we can insert different types in theArrayList and the code works just fine.

Erasure and bridge methods

Bridge methods are created by the compiler to cover corner cases. Specifically, when the compiler encounters an implementation of a parameterized interface or an extension of a parameterized class, itmay need to generate a bridge method (also known as a synthetic method) as part of the type erasure phase. For instance, let’s consider the following parameterized class:

publicclassPuzzle<E> {public E piece;publicPuzzle(E piece) {    this.piece = piece;  }publicvoidsetPiece(E piece) {    this.piece = piece;  }}

And, an extension of this class:

publicclassFunPuzzleextendsPuzzle<String> {publicFunPuzzle(String piece) {super(piece);  }@OverridepublicvoidsetPiece(String piece) {    super.setPiece(piece);  }}

Type erasure modifiesPuzzle.setPiece(E) asPuzzle.setPiece(Object). This means that theFunPuzzle.setPiece(String) method does not override thePuzzle.setPiece(Object) method. Since the signatures of the methods are not compatible, the compiler must accommodate the polymorphism of generic types via a bridge (synthetic) method meant to guarantee that sub-typing works as expected. Let’s highlight this method in the code:

/* Decompiler 8ms, total 3470ms, lines 18 */package modern.challenge;publicclassFunPuzzleextendsPuzzle<String> {publicFunPuzzle(String piece) {super(piece);   }publicvoidsetPiece(String piece) {super.setPiece(piece);   }// $FF: synthetic method// $FF: bridge methodpublicvoidsetPiece(Object var1) {this.setPiece((String)var1);   }}

Now, whenever you see a bridge method in the stack trace, you will know what it is and why it is there.

Type erasure and heap pollution

Have you ever seen an unchecked warning? I’m sure you have! It’s one of those things that is common to all Java developers. Theymay occur at compile-time as the result of type checking, or at runtime as a result of a cast or method call. In both cases, we talk about the fact that the compiler cannot validate the correctness of an operation, which implies some parameterized types. Not every unchecked warning is dangerous, but there are cases when we have to consider and deal with them.

A particular case is represented byheap pollution. If a parameterized variable of a certain type points to an object that is notof that type, then we are prone to deal with a code that leads to heap pollution. A good candidate for such scenarios involves methods withvarargs arguments.

Check out this code:

publicstatic <T>voidlistOf(List<T> list, T... ts) {  list.addAll(Arrays.asList(ts));    }

ThelistOf() declaration will cause this warning:Possible heap pollution from parameterized vararg type T. So, what’s happening here?

The story begins when the compiler replaces the formalT... parameter into an array. After applying type erasure, theT... parameter becomesT[], and finallyObject[]. Consequently, we opened a gate to possible heap pollution. But, our code just added the elements ofObject[] into aList<Object>, so we are in the safe area.

In other words, if you know that thebody of thevarargs method is not prone to generate a specific exception (for example,ClassCastException) or to use thevarargs parameter in an improper operation, then we can instruct the compiler to suppress these warnings. We can do it via the@SafeVarargs annotation as follows:

@SafeVarargspublicstatic <T>voidlistOf(List<T> list, T... ts) {...}

The@SafeVarargs is a hint that sustains that the annotated method will use thevarargs formal parameter only in proper operations. More common, but less recommended, is to use@SuppressWarnings({"unchecked", "varargs"}), which simply suppresses such warnings without claiming that thevarargs formal parameter is not used in improper operations.

Now, let’s tackle this code:

publicstaticvoidmain(String[] args) {  List<Integer> ints =newArrayList<>();  Main.listOf(ints,1,2,3);  Main.listsOfYeak(ints);}publicstaticvoidlistsOfYeak(List<Integer>... lists) {  Object[] listsAsArray = lists;       listsAsArray[0] = Arrays.asList(4,5,6);IntegersomeInt= lists[0].get(0);     listsAsArray[0] = Arrays.asList("a","b","c");IntegersomeIntYeak= lists[0].get(0);// ClassCastException}

This time, the type erasure transforms theList<Integer>... intoList[], which is a subtype ofObject[]. This allows us to do the assignment:Object[] listsAsArray = lists;. But, check out the last two lines of code where we create aList<String> and store it inlistsAsArray[0]. In the lastline, we try to access the firstInteger fromlists[0], which obviouslyleads to aClassCastException. This is an improper operation of usingvarargs, so it is not advisable to use@SafeVarargs in this case. We should have taken the following warnings seriously:

// unchecked generic array creation for varargs parameter// of type java.util.List<java.lang.Integer>[]Main.listsOfYeak(ints);// Possible heap pollution from parameterized vararg// type java.util.List<java.lang.Integer>publicstaticvoidlistsOfYeak(List<Integer>... lists) { ... }

Now, that you are familiar with type erasure, let’s briefly cover polymorphic overloading.

Polymorphic overloading in a nutshell

Sinceoverloading (also known as “ad hoc” polymorphism) is a core concept ofObject-Oriented Programming (OOP), I’m sure you are familiar with Java method overloading, so I’ll not insist on the basic theory of this concept.

Also, I’m aware that some people don’t agree that overloading can be a form of polymorphism, but that is another topic that we will not tackle here.

We will be more practical and jump into a suite of quizzes meant to highlight some interesting aspects of overloading. More precisely, we will discusstype dominance. So, let’s tackle the first quiz (wordie is an initially empty string):

staticvoidkaboom(byte b) { wordie +="a";}staticvoidkaboom(short s) { wordie +="b";}   kaboom(1);

What will happen? If you answered that the compiler will point out that there is no suitable method found forkaboom(1), then you’re right. The compiler looks for a method that gets an integer argument,kaboom(int). Okay, that was easy! Here is the next one:

staticvoidkaboom(byte b) { wordie +="a";}staticvoidkaboom(short s) { wordie +="b";}staticvoidkaboom(long l) { wordie +="d";}staticvoidkaboom(Integer i) { wordie +="i";}   kaboom(1);

We know that the first twokaboom() instances are useless. How aboutkaboom(long) andkaboom(Integer)? You are right,kaboom(long) will be called. If we removekaboom(long), thenkaboom(Integer) is called.

Important note

In primitive overloading, the compiler starts by searching for a one-to-one match. If this attempt fails, then the compiler searches for an overloading flavor taking a primitive broader domain than the primitive current domain (for instance, for anint, it looks forint,long,float, ordouble). If this fails as well, then the compiler checks for overloading taking boxed types (Integer,Float, and so on).

Following the previous statements, let’s have this one:

staticvoidkaboom(Integer i) { wordie +="i";}staticvoidkaboom(Long l) { wordie +="j";} kaboom(1);

This time,wordie will bei. Thekaboom(Integer) is called since there is nokaboom(int/long/float/double). If we had akaboom(double), then that method has higher precedence thankaboom(Integer). Interesting, right?! On the other hand, if we removekaboom(Integer), then don’t expect thatkaboom(Long) will be called. Any otherkaboom(boxed type) with a broader/narrow domain thanInteger will not be called. This is happening because the compiler follows the inheritance path based on an IS-A relationship, so afterkaboom(Integer), it looks forkaboom(Number), sinceInteger is aNumber.

Important note

In boxed type overloading, the compiler starts by searching for a one-to-one match. If this attempt fails, then the compiler will not consider any overloading flavor taking a boxed type with a broader domain than the current domain (of course, a narrow domain is ignored as well). It looks forNumber as being the superclass of all boxed types. IfNumber is not found, the compiler goes up in the hierarchy until it reaches thejava.lang.Object, which is the end of the road.

Okay, let’s complicatethings a little bit:

staticvoidkaboom(Object... ov) { wordie +="o";}staticvoidkaboom(Number n) { wordie +="p";}staticvoidkaboom(Number... nv) { wordie +="q";}  kaboom(1);

So, which method will be called this time? I know, you thinkkaboom(Number), right? At least, my simple logic pushes me to think that this is a common-sense choice. And it is correct!

If we removekaboom(Number), then the compiler will call thevarargs method,kaboom(Number...). This makes sense sincekaboom(1) uses a single argument, sokaboom(Number) should have higher precedence thankaboom(Number...). This logic reverses if we callkaboom(1,2,3) sincekaboom(Number) is no longer representing a valid overloading for this call, andkaboom(Number...) is the right choice.

But, this logic applies becauseNumber is the superclass of all boxed classes (Integer,Double,Float, and so on).

How about now?

staticvoidkaboom(Object... ov) { wordie +="o";}staticvoidkaboom(File... fv) { wordie +="s";}   kaboom(1);

This time, the compiler will “bypass”kaboom(File...) and will callkaboom(Object...). Based on the same logic, a call ofkaboom(1, 2, 3) will callkaboom(Object...) since there is nokaboom(Number...).

Important note

In overloading, if the call has a single argument, then the method with a single argument has higher precedence than itsvarargs counterpart. On the other hand, if the call has more arguments of the same type, then thevarargs method is called since the one-argument method is not suitable anymore. When the call has a single argument but only thevarargs overloading is available, then this method is called.

This leads us to the following example:

staticvoidkaboom(Number... nv) { wordie +="q";}staticvoidkaboom(File... fv) { wordie +="s";}   kaboom();

This time,kaboom() has no arguments and the compiler cannot find a unique match. This means that thereference tokaboom() is ambiguous since both methods match (kaboom(java.lang.Number...) inmodern.challenge.Main and methodkaboom(java.io.File...) inmodern.challenge.Main).

In the bundled code, you can play even more with polymorphic overloading and test your knowledge. Moreover, try to challenge yourself and introduce generics in the equation as well.

Erasure vs. overloading

Okay, based on the previousexperience, check out this code:

voidprint(List<A> listOfA) {  System.out.println("Printing A: " + listOfA);}voidprint(List<B> listofB) {  System.out.println("Printing B: " + listofB);}

What will happen? Well, this is a case where overloading and type erasure collide. The type erasure will replaceList<A> withList<Object> andList<B> withList<Object> as well. So, overloading is not possible and we get an error such asname clash: print(java.util.List<modern.challenge.B>) and print (java.util.List<modern.challenge.A>) have the same erasure.

In order to solve this issue, we can add a dummy argument to one of these two methods:

voidprint(List<A> listOfA, Void... v) {  System.out.println("Printing A: " + listOfA);}

Now, we can have the same call for both methods:

newMain().print(List.of(newA(),newA()));newMain().print(List.of(newB(),newB()));

Done! You can practicethese examples in the bundled code.

48. Xlinting default constructors

We know that a Java class with no explicit constructor automatically gets an “invisible” default constructor for setting defaultvalues of the instance variables. The followingHouse class falls in this scenario:

publicclassHouse {private String location;privatefloat price;  ...}

If this is exactly what we wanted, then it is no problem. But, if we are concerned about the fact that the default constructors are exposed by classes to publicly exported packages, then we have to consider using JDK 16+.

JDK 16+ added a dedicatedlintmeant to warn us about the classes that have default constructors. In order to take advantage of thislint, we have to follow two steps:

  • Export the package containing that class
  • Compile with-Xlint:missing-explicit-ctor (or-Xlint,-Xlint:all)

In our case, we export the packagemodern.challenge inmodule-info as follows:

module P48_XlintDefaultConstructor {exports modern.challenge;}

Once you compile the code with-Xlint:missing-explicit-ctor, you’ll see a warning like in the following figure:

Figure 2.30.png

Figure 2.30: The warning produced by -Xlint:missing-explicit-ctor

Now, you can easily findout which classes have default constructors.

49. Working with the receiver parameter

Starting with JDK 8, we can enrich anyof our instance methods with the optionalreceiver parameter. This is a purely syntactic parameter of enclosing type exposed via thethis keyword. The following two snippets of code are identical:

publicclassTruck {publicvoidrevision1(Truckthis) {TruckthisTruck=this;    System.out.println("Truck: " + thisTruck);  }publicvoidrevision2() {TruckthisTruck=this;    System.out.println("Truck: " + thisTruck);  }}

Do not conclude thatrevision2() is an overloading ofrevision1(), or vice versa. Both methods have the same output, the same signature, and produce the same bytecode.

The receiver parameter can be used in inner classes as well. Here is an example:

publicclassPaymentService {classInvoiceCalculation {final PaymentService paymentService;    InvoiceCalculation(PaymentService PaymentService.this) {      paymentService = PaymentService.this;    }  }}

Okay, but why use the receiver parameter? Well, JDK 8 introduced so-calledtype annotations, which are exactly as thename suggests: annotations that can be applied to types. In this context, the receiver parameter was added for annotating the type of object for which the method is called. Check out the following code:

@Target(ElementType.TYPE_USE)public@interface ValidAddress {}public StringgetAddress(@ValidAddress Personthis) { ... }

Or, check this more elaborate example:

publicclassParcel {publicvoidorder(@New Parcelthis) {...}publicvoidshipping(@Ordered Parcelthis) {...}publicvoiddeliver(@Shipped Parcelthis) {...}publicvoidcashit(@Delivered Parcelthis) {...}publicvoiddone(@Cashed Parcelthis) {...}}

Every client of aParcel must call thesemethods in a precise sequence drawn via type annotations and receiver parameters. In other words, an order can be placed only if it is a new order, it can be shipped only if the order was placed, it can be delivered only if it was shipped, it can be paid only if it was delivered, and it can be closed only if it was paid.

At this moment, this strict sequence is pointed out only by these hypothetical annotations. But, this is the right road to implement further a static analysis tool that will understand the meaning of these annotations and trigger warnings every time a client ofParcel doesn’t follow this precise sequence.

50. Implementing an immutable stack

A common coding challenge in interviews is this: Implement an immutable stack in Java.

Being an abstract data type, a stack needs at least this contract:

publicinterfaceStack<T>extendsIterable<T> {booleanisEmpty();  Stack<T>push(T value);  Stack<T>pop();  Tpeek();    }

Having this contract, we can focus on the immutable implementation. Generally speaking, an immutable data structure stays the same until an operation attempts to change it (for instance, to add, put, remove, delete, push, and so on). If an operation attempts to alter the content of an immutable data structure, a new instance of that data structure must be created and used by that operation, while the previous instance remains unchanged.

Now, in our context, we have two operations that can alter the stack content: push and pop. The push operation should return a new stack containing the pushed element, while the pop operation should return the previous stack. But, in order to accomplish this, we need to start from somewhere, so we need an empty initial stack. This is a singleton stack that can be implemented as follows:

privatestaticclassEmptyStack<U>implementsStack<U> {  @Overridepublic Stack<U>push(U u) {returnnewImmutableStack<>(u,this);    }@Overridepublic Stack<U>pop() {thrownewUnsupportedOperationException("Unsupported operation on an empty stack");    }@Overridepublic Upeek() {thrownewUnsupportedOperationException ("Unsupported operation on an empty stack");    }@OverridepublicbooleanisEmpty() {returntrue;    }@Overridepublic Iterator<U>iterator() {returnnewStackIterator<>(this);  }}

TheStackIterator is a trivial implementation of the JavaIterator. Nothing fancy here:

privatestaticclassStackIterator<U>implementsIterator<U> {private Stack<U> stack;publicStackIterator(final Stack<U> stack) {this.stack = stack;  }@OverridepublicbooleanhasNext() {return !this.stack.isEmpty();  }@Overridepublic Unext() {Ue=this.stack.peek();this.stack =this.stack.pop();return e;  }@Overridepublicvoidremove() {  }}

So far, we have theIterator and an empty stack singleton. Finally, we can implement the logic of the immutable stack as follows:

publicclassImmutableStack<E>implementsStack<E> {privatefinal E head;privatefinal Stack<E> tail;privateImmutableStack(final E head,final Stack<E> tail) {this.head = head;this.tail = tail;  }publicstatic <U> Stack<U>empty(final Class<U> type) {returnnewEmptyStack<>();  }@Overridepublic Stack<E>push(E e) {returnnewImmutableStack<>(e,this);  }@Overridepublic Stack<E>pop() {returnthis.tail;  }@Overridepublic Epeek() {returnthis.head;  }@OverridepublicbooleanisEmpty() {returnfalse;  }@Overridepublic Iterator<E>iterator() {returnnewStackIterator<>(this);  }// iterator code// empty stack singleton code}

Creating a stack starts bycalling theImmutableStack.empty() method, as follows:

Stack<String> s = ImmutableStack.empty(String.class);

In the bundled code, you can how this stack can be used further.

51. Revealing a common mistake with Strings

Everybody knows thatString is an immutable class.

Even so, we are still prone to accidentally write code that ignores the fact thatString is immutable. Check out this code:

Stringstr="start";str = stopIt(str);publicstatic StringstopIt(String str) {  str.replace(str,"stop");return str;}

Somehow, it is logical to think that thereplace() call has replaced the textstart withstop and nowstr isstop. This is the cognitive power of words (replace is a verb that clearly induces the idea that the text was replaced). But,String is immutable! Oh… we already know that! This means thatreplace() cannot alter the originalstr. There are many such silly mistakes that we areprone to accidentally make, so pay extra attention to such simple things, since they can waste your time in the debugging stage.

The solution is obvious and self-explanatory:

publicstatic StringstopIt(String str) {  str =  str.replace(str,"stop");return str;}

Or, simply:

publicstatic StringstopIt(String str) {return str.replace(str,"stop");}

Don’t forget thatString is immutable!

52. Using the enhanced NullPointerException

Take your time to dissect the following trivial code and try to identify the parts that are prone to cause aNullPointerException (these parts are marked as numbered warnings, which will be explained afterthe snippet):

publicfinalclassChainSaw {privatestaticfinal List<String> MODELS    = List.of("T300","T450","T700","T800","T900");privatefinal String model;privatefinal String power;privatefinalint speed;publicboolean started;privateChainSaw(String model, String power,int speed) {this.model = model;this.power = power;this.speed = speed;  }publicstatic ChainSaw initChainSaw(String model) {for (String m : MODELS) {if (model.endsWith(m)) {WARNING3!returnnewChainSaw(model,null,WARNING5!          (int) (Math.random() *100));      }    }returnnull;WARNING1,2!  }publicintperformance(ChainSaw[] css) {intscore=0;for (ChainSaw cs : css) {WARNING3!      score += Integer.compare(this.speed,cs.speed);WARNING4!    }return score;  }publicvoidstart() {if (!started) {      System.out.println("Started ...");      started =true;    }  }publicvoidstop() {if (started) {      System.out.println("Stopped ...");      started =false;    }  }public StringgetPower() {return power;WARNING5!  }@Overridepublic StringtoString() {return"ChainSaw{" +"model=" + model       +", speed=" + speed +", started=" + started +'}';  } }

You noticed the warnings? Of course, you did! There are five major scenarios behind mostNullPointerException (NPEs) and each of them is present in the previous class. Prior to JDK 14, an NPE doesn’t contain detailed information about the cause. Look at this exception:

Exception in thread "main" java.lang.NullPointerException    at modern.challenge.Main.main(Main.java:21)

This message is just a starting point for the debugging process. We don’t know the root cause of this NPE or which variable isnull. But, starting with JDK 14 (JEP 358), we have really helpful NPE messages. For example, in JDK 14+, the previous message looks as follows:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "modern.challenge.Strings.reverse()" because "str" is null    at modern.challenge.Main.main(Main.java:21)

The highlighted part of the message gives us important information about the root cause of this NPE. Now, we know that thestr variable isnull, so no need to debug further. We can just focus on how to fix this issue.

Next, let’s tackle each of the five major root causes of NPEs.

WARNING 1! NPE when calling an instance method via a null object

Consider the following codewritten by a client ofChainSaw:

ChainSawcs= ChainSaw.initChainSaw("QW-T650");cs.start();// 'cs' is null

The client passes a chainsaw model that is not supported by this class, so theinitChainSaw() method returnsnull. This is really bad because every time the client uses thecs variable, they will get back an NPE as follows:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "modern.challenge.ChainSaw.start()" because "cs" is null    at modern.challenge.Main.main(Main.java:9)

Instead of returningnull, it is better to throw an explicit exception that informs the client that they cannot continue because we don’t have this chainsaw model (we can go for the classicalIllegalArgumentException or, the more suggestive one in this case (but quite uncommon fornull value handling),UnsupportedOperationException). This may be the proper fix in this case, but it is not universally true. There are cases when it is better to return an empty object (for example, an empty string, collection, or array) or a default object (for example, an object with minimalist settings) that doesn’t break the client code. Since JDK 8, we can useOptional as well. Of course, there are cases when returningnull makes sense but that is more common in APIs and special situations.

WARNING 2! NPE when accessing (or modifying) the field of a null object

Consider the following code written by a client ofChainSaw:

ChainSawcs= ChainSaw.initChainSaw("QW-T650");booleanisStarted= cs.started;// 'cs' is null

Practically, the NPE, in this case, has the same root cause as the previous case. We try to access thestarted field ofChainSaw. Since this is a primitiveboolean, it was initialized by JVM withfalse, but we cannot “see” that since we try to access this field through anull variable represented bycs.

WARNING 3! NPE when null is passed in the method argument

Consider the following code written by a client ofChainSaw:

ChainSawcs= ChainSaw.initChainSaw(null);

You are not a good citizen if you want anull ChainSaw, but who am I to judge? It is possible for this to happen and will lead to the following NPE:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.endsWith(String)" because "model" is null   at modern.challenge.ChainSaw.initChainSaw(ChainSaw.java:25)   at modern.challenge.Main.main(Main.java:16)

The message is crystal clear. We attempt to call theString.endWith() method with anull argument represented by themodel variable. To fix this issue, we have to add a guard condition to ensure that the passedmodel argument is notnull (and eventually, not empty). In this case, we can throw anIllegalArgumentException to inform the client that we are here and we are guarding. Another approach may consist of replacing the givennull with a dummy model that passes through our code without issues (for instance, since the model is aString, we can reassign an empty string,““). However, personally, I don’t recommend this approach, not even for small methods. You never know how the code will evolve and such dummy reassignments can lead to brittle code.

WARNING 4! NPE when accessing the index value of a null array/collection

Consider the following code written by a client ofChainSaw:

ChainSawmyChainSaw= ChainSaw.initChainSaw("QWE-T800");ChainSaw[] friendsChainSaw =newChainSaw[]{  ChainSaw.initChainSaw("Q22-T450"),  ChainSaw.initChainSaw("QRT-T300"),  ChainSaw.initChainSaw("Q-T900"),null,// ops!  ChainSaw.initChainSaw("QMM-T850"),// model is not supported  ChainSaw.initChainSaw("ASR-T900")};intscore= myChainSaw.performance(friendsChainSaw);

Creating an array ofChainSaw wasquite challenging in this example. We accidentally slipped anull value (actually, we did it intentionally) and an unsupported model. In return, we get the following NPE:

Exception in thread "main" java.lang.NullPointerException: Cannot read field "speed" because "cs" is null    at modern.challenge.ChainSaw.performance(ChainSaw.java:37)    at modern.challenge.Main.main(Main.java:31)

The message informs us that thecs variable isnull. This is happening at line 37 inChainSaw, so in the for loop of theperformance() method. While looping the given array, our code iterated over thenull value, which doesn’t have thespeed field. Pay attention to this kind of scenario: even if the given array/collection itself is notnull, it doesn’t mean that it cannot containnull items. So, adding a guarding check before handling each item can save us from an NPE in this case. Depending on the context, we can throw anIllegalArgumentException when the loop passes over the firstnull or simply ignorenull values and don’t break the flow (in general, this is more suitable). Of course, using a collection that doesn’t acceptnull values is also a good approach (Apache Commons Collectionand Guava have such collections).

WARNING 5! NPE when accessing a field via a getter

Consider the following code written by a client ofChainSaw:

ChainSawcs= ChainSaw.initChainSaw("T5A-T800");Stringpower= cs.getPower();System.out.println(power.concat(" Watts"));

And, the associated NPE:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.concat(String)" because "power" is null    at modern.challenge.Main.main(Main.java:37)

Practically, the gettergetPower() returnednull since thepower field isnull. Why? The answer is in the linereturn new ChainSaw(model, null, (int) (Math.random() * 100)); of theinitChainSaw() method. Because we didn’t decide yet on the algorithm for calculating the power of a chainsaw, we passednull to theChainSaw constructor. Further, the constructor simply sets thepower field asthis.power = power. If it was a public constructor, then most probably we would have added some guarded conditions, but being a private constructor, it is better to fix the issue right from the root and not pass thatnull. Since thepower is aString, we can simply pass an empty string or a suggestive string such asUNKNOWN_POWER. We also may leave a TODO comment in code such as// TODO (JIRA ####): replace UNKNOWN_POWER with code. This will remind us to fix this in the next release. Meanwhile, the code has eliminated the NPE risk.

Okay, after we fixed all these five NPE risks, the code has become the following (the added code is highlighted):

publicfinalclassChainSaw {  privatestaticfinalStringUNKNOWN_POWER="UNKNOWN";privatestaticfinal List<String> MODELS    = List.of("T300","T450","T700","T800","T900");privatefinal String model;privatefinal String power;privatefinalint speed;publicboolean started;privateChainSaw(String model, String power,int speed) {this.model = model;this.power = power;this.speed = speed;  }publicstatic ChainSawinitChainSaw(String model) {if (model ==null || model.isBlank()) {thrownewIllegalArgumentException("The given model               cannot be null/empty");    }for (String m : MODELS) {if (model.endsWith(m)) {// TO DO (JIRA ####): replace UNKNOWN_POWER with codereturnnewChainSaw(model,UNKNOWN_POWER,          (int) (Math.random() *100));        }    }thrownewUnsupportedOperationException("Model " + model +" is not supported");  }publicintperformance(ChainSaw[] css) {if (css ==null) {thrownewIllegalArgumentException("The given models cannot be null");    }intscore=0;for (ChainSaw cs : css) {if (cs !=null) {        score += Integer.compare(this.speed, cs.speed);      }    }return score;  }publicvoidstart() {if (!started) {      System.out.println("Started ...");      started =true;    }  }publicvoidstop() {if (started) {      System.out.println("Stopped ...");      started =false;    }  }public StringgetPower() {return power;  }@Overridepublic StringtoString() {return"ChainSaw{" +"model=" + model      +", speed=" + speed +", started=" + started +'}';  }}

Done! Now, our code is NPE-free. Atleast until reality contradicts us and a new NPE occurs.

53. Using yield in switch expressions

Here, we’re going to look at howswitch expressions have evolved in JDK 13+.

Java SE 13 added the newyield statement, whichcan be used instead of thebreak statement in switch expressions.

We know that a JDK 12+switch expression canbe written as follows (playerType is a Java enum):

returnswitch (playerType) {case TENNIS ->newTennisPlayer();case FOOTBALL ->newFootballPlayer();  ...};

Moreover, we know that a label’s arrow can point to a curly-braces block as well (this works only in JDK 12,not in JDK 13+):

returnswitch (playerType) {case TENNIS -> {    System.out.println("Creating a TennisPlayer ...");breaknewTennisPlayer();  }case FOOTBALL -> {    System.out.println("Creating a FootballPlayer ...");breaknewFootballPlayer();  }  ...};

Sincebreak can be confusing because it can be used in old-schoolswitch statements and in the newswitch expressions, JDK 13 added theyield statement to be used instead ofbreak. Theyield statementtakes one argument representing the value produced by the current case. The previous examples can be written from JDK 13+ as follows:

returnswitch (playerType) {case TENNIS:yieldnewTennisPlayer();case FOOTBALL:yieldnewFootballPlayer();  ...};returnswitch (playerType) {case TENNIS -> {    System.out.println("Creating a TennisPlayer ...");yieldnewTennisPlayer();  }case FOOTBALL -> {    System.out.println("Creating a FootballPlayer ...");yieldnewFootballPlayer();  }  ...};

In other words, starting with JDK 13+, aswitch expression can rely onyield but not onbreak, and aswitch statement can rely onbreak but not onyield.

54. Tackling the case null clause in switch

Before JDK 17, anull case in aswitch was commonly coded as a guarding condition outside theswitch, as in the following example:

privatestatic PlayercreatePlayer(PlayerTypes playerType) {// handling null values in a condition outside switchif (playerType ==null) {thrownewIllegalArgumentException("Player type cannot be null");  }returnswitch (playerType) {case TENNIS ->newTennisPlayer();case FOOTBALL ->newFootballPlayer();    ...  };}

Starting with JDK 17+ (JEP 427), we can treat anull case as any other common case. For instance, here we have anull case that is responsible for handling the scenarios when the passed argument isnull:

privatestatic PlayercreatePlayer(PlayerTypes playerType) {returnswitch (playerType) {case TENNIS ->newTennisPlayer();case FOOTBALL ->newFootballPlayer();case SNOOKER ->newSnookerPlayer();    casenull ->thrownewNullPointerException("Player type cannot be null");case UNKNOWN ->thrownewUnknownPlayerException("Player type is unknown");// default is not mandatorydefault ->thrownewIllegalArgumentException("Invalid player type: " + playerType);  };}

In certain contexts,null anddefault have the same meaning, so we can chain them in the samecase statement:

privatestatic PlayercreatePlayer(PlayerTypes playerType) {returnswitch (playerType) {case TENNIS ->newTennisPlayer();case FOOTBALL ->newFootballPlayer();    ...casenull,default ->thrownewIllegalArgumentException("Invalid player type: " + playerType);  };}

Or you might find it more readable like this:

...case TENNIS:yieldnewTennisPlayer();case FOOTBALL:yieldnewFootballPlayer();...casenull,default:thrownewIllegalArgumentException("Invalid player type: " + playerType);...

Personally, I suggest you think twice before patching yourswitch expressions withcase null, especially if you plan to do it only for silently sweeping these values. Overall, your code may become brittle and exposed to unexpected behaviors/results that ignore the presence ofnull values. In the bundled code, you can test the complete examples.

55. Taking on the hard way to discover equals()

Check out the following code:

Integerx1=14;Integery1=14;Integerx2=129;Integery2=129;List<Integer> listOfInt1 =newArrayList<>( Arrays.asList(x1, y1, x2, y2));listOfInt1.removeIf(t -> t == x1 || t == x2);List<Integer> listOfInt2 =newArrayList<>( Arrays.asList(x1, y1, x2, y2));listOfInt2.removeIf(t -> t.equals(x1) || t.equals(x2));

So, initially,listOfInt1 andlistOfInt2 have the same items, [x1=14,y1=14,x2=129,y2=129]. But, what will containlistOfInt1/listOfInt2 after executing the code based onremoveIf() and==, respectivelyequals()?

The first list will remain with a single item, [129]. Whent isx1, we know thatx1 == x1, so 14 is removed. But, why isx2 removed? Whent isy1, we know thaty1 == x1 should befalse since, via==, we compare the object’s references in memory, not their values. Obviously,y1 andx1 should have different references in the memory… or shouldn’t they ? Actually, Java has an internal rule to cache integers in -127 … 128. Sincex1=14 is cached,y1=14 uses the cache so no newInteger is created. This is whyy1 == x1 andy1 is removed as well. Next,t isx2, andx2 == x2, sox2 is removed. Finally,t isy2, buty2 == x2 returnsfalse, since 129 > 128 is not cached, sox2 andy2 have different references in memory.

On the other hand, when we useequals(), which is the recommended approach for comparing the object’s values, the resulting list is empty. Whent isx1,x1 =x1, so 14 is removed. Whent isy1,y1 =x1, soy1 is removed as well. Next,t isx2, andx2=x2, sox2 is removed. Finally,t isy2, andy2 =x2, soy2 is removed as well.

56. Hooking instanceof in a nutshell

Having an object (o) and a type (t), we can use theinstanceof operator to test ifo is of typet by writingo instanceof t. This is aboolean operator that is very useful to ensure the success of a subsequent casting operation. For instance, check the following:

interfaceFurniture {};classPlywood {};classWardrobeextendsPlywoodimplementsFurniture {};

instanceof returnstrue if we test the object (for instance,Wardrobe) against the type itself:

Wardrobewardrobe=newWardrobe();if(wardrobeinstanceof Wardrobe) { }// truePlywoodplywood=newPlywood();if(plywoodinstanceof Plywood) { }// true

instanceof returns true if the tested object (for instance,Wardrobe) is an instance of a subclass of the type (for instancePlywood):

Wardrobewardrobe=newWardrobe();if(wardrobeinstanceof Plywood) {}// true

instanceof returnstrue if the tested object (for instance,Wardrobe) implements the interface represented by the type (for instance,Furniture):

Wardrobewardrobe=newWardrobe();if(wardrobeinstanceof Furniture) {}// true

Based on this, consider the following note:

Important note

The logic behindinstanceof relies on the IS-A relationship (this is detailed inThe Complete Coding Interview Guide in Java,Chapter 6,What is inheritance?). In a nutshell, this relationship is based on interface implementation or class inheritance. For instance,wardrobe instanceof Plywood returnstrue becauseWardrobe extendsPlywood, soWardrobe IS APlywood. Similarly,Wardrobe IS AFurniture. On the other hand,Plywood IS-not-AFurniture, soplywood instanceof Furniture returnsfalse. In this context, since every Java class extendsObject, we know thatfoo instanceof Object returnstrue as long asfoo is an instance of a Java class. In addition,null instanceof Object (or any other object) returnsfalse, so this operator doesn’t require an explicitnull check.

Finally, keep inmind thatinstanceof works only with reified types (reified type information is available at runtime), which include:

  • Primitive types (int,float)
  • Raw types (List,Set)
  • Non-generic classes/interfaces (String)
  • Generic types with unbounded wildcards (List<?>, Map<?, ?>)
  • Arrays of reifiable types (String[], Map<?, ?>[], Set<?>[])

This means that we cannot use theinstanceof operator (or casts) with parameterized types because the type erasures alter all type parameters in generic code, so we cannot say which parameterized type for a generic type is in use at runtime.

57. Introducing pattern matching

JDK 16 has introduced one of the major and complex features of Java, referred to aspattern matching. The future is wide open for this topic.

In a nutshell,pattern matchingdefines a synthetic expression for checking/testing whether a given variable has certain properties. If those properties are met, then automatically extract one or more parts of that variable into other variables. From this point forward, we can use those extracted variables.

A pattern matching instance (pay attention, this has nothing to do with design patterns) is a structure made of several components as follows (this is basically the pattern matching terminology):

  • Thetarget operand or the argument of the predicate: This is a variable (or an expression) that we aim to match.
  • Thepredicate (ortest): This is a check that takes place at runtime and aims to determine if the giventarget operand does or doesn’t have one or more properties (we match thetarget operand against the properties).
  • One or more variables arereferred to aspattern variables orbinding variables: these variables are automatically extracted from thetarget operand if and only if thepredicate/test succeeds.
  • Finally, we have thepattern itself, which is represented by thepredicate +binding variables.
Figure 2.31.png

Figure 2.31: Pattern matching components

So, we can say that Java pattern matching is a synthetic expression of a complex solution composed of four components: target operand, predicate/test, binding variable(s), and pattern = predicate + binding variable(s).

The scope of binding variables in pattern matching

The compiler decides the scope (visibility) of the binding variables, so we don’t have to bother with such aspects viaspecial modifiers or other tricks. In the case of predicates that always pass (like anif(true) {}), the compiler scopes the binding variables exactly as for the Javalocal variables.

But, most patterns make sense precisely because the predicate may fail. In such cases, the compiler applies a technique calledflow scoping. That is actually a combination of theregular scoping anddefinitive assignment.

Thedefinitive assignment is a techniqueused by the compiler based on the structure of statements and expressions to ensure that a local variable (or blankfinal field) is definitely assigned before it is accessed by the code. In a pattern-matching context, a binding variable is assigned only if the predicate passes, so thedefinitive assignmentaim is to find out the precise place when this is happening. Next, the regular block scope represents the codewhere the binding variable is in scope.

Do you want this as a simple important note? Here it is.

Important note

In pattern matching, the binding variable is flow-scoped. In other words, the scope of a binding variable covers only the block where the predicate passed.

We will cover this topic inProblem 59.

Guarded patterns

So far, we know that a pattern relies on a predicate/test for deciding whether the binding variables should be extracted from the target operand or not. In addition, sometimes we need to refine this predicate by appending to it extraboolean checks based on the extracted binding variables. We name this aguarded pattern. In other words, if the predicate evaluates totrue, then the binding variables are extracted and they enter in furtherboolean checks. If these checks are evaluated totrue, we can say that the target operand matches this guarded pattern.

We cover this inProblem 64.

Type coverage

In a nutshell, theswitch expressions andswitch statements that usenull and/or pattern labels should be exhaustive. In other words, we must cover all the possible values withswitch case labels.

We cover this inProblem 66.

Current status of pattern matching

Currently, Java supports typepattern matching forinstanceof andswitch, and record pattern-destructuring patterns for records (covered inChapter 4). These are the final releases in JDK 21.

58. Introducing type pattern matching for instanceof

Can you name the shortcomings of the following classical snippet ofcode (this is a simple code used to save different kinds of artifacts on a USB device)?

publicstatic Stringsave(Object o)throws IOException {if (oinstanceof File) {Filefile= (File) o;return"Saving a file of size: "       + String.format("%,d bytes", file.length());  }if (oinstanceof Path) {Pathpath= (Path) o;return"Saving a file of size: "       + String.format("%,d bytes", Files.size(path));  }if (oinstanceof String) {Stringstr= (String) o;return"Saving a string of size: "       + String.format("%,d bytes", str.length());  }return"I cannot save the given object";}

You’re right…type checking and casting are burdensome to write and read. Moreover, those check-cast sequences are error-prone (it is easy to change the checked type or the casted type and forget to change the type of the other object). Basically, in each conditional statement, we do three steps, as follows:

  1. First, we do a type check (for instance,o instanceof File).
  2. Second, we do a type conversion via cast (for instance,(File) o).
  3. Third, we do a variable assignment (for instance,File file =).

But, starting with JDK 16 (JEP 394), we can usetype pattern matching for instanceofto perform the previous three steps in one expression. The type pattern is the first category of patterns supportedby Java. Let’s see the previous coderewritten via thetype pattern:

publicstatic Stringsave(Object o)throws IOException {if (oinstanceof File file) {return"Saving a file of size: "       + String.format("%,d bytes", file.length());  }if (oinstanceof String str) {return"Saving a string of size: "       + String.format("%,d bytes", str.length());  }if (oinstanceof Path path) {return"Saving a file of size: "       + String.format("%,d bytes", Files.size(path));  }return"I cannot save the given object";}

In eachif-then statement, we have a test/predicate to determine the type ofObject o, a cast ofObject o toFile,Path, orString, and a destructuring phase for extracting either the length or the size fromObject o.

The piece of code, (o instanceof File file) is not just some syntactic sugar. It is not just a convenient shortcut of the old-fashioned code to reduce the ceremony of conditional state extraction. This is atype pattern in action!

Practically, we match the variableo againstFile file. More precisely, we match the type ofo against the typeFile. We have thato is thetarget operand(the argument of the predicate),instanceof File is the predicate, and the variablefile is thepatternorbinding variablethat is automatically created only ifinstanceof File returnstrue. Moreover,instanceof File file is thetype pattern, or in short,File file is the pattern itself. The following figure illustrates this statement:

Figure 2.32.png

Figure 2.32: Type pattern matching for instanceof

In the type pattern forinstanceof, there is no need to perform explicitnull checks (exactly as in the case ofplaininstanceof), and no upcasting is allowed. Both of the following examples generate acompilation error in JDK 16-20, but not in JDK 14/15/21 (this is weird indeed):

if ("foo"instanceof String str) {}if ("foo"instanceof CharSequence sequence) {}

The compilation error points out that the expression type cannot be a subtype of pattern type (no upcasting is allowed). However, with plaininstanceof, this works in all JDKs:

if ("foo"instanceof String) {}if ("foo"instanceof CharSequence) {}

Next, let’s talk about the scope of binding variables.

59. Handling the scope of a binding variable in type patterns for instanceof

FromProblem 57, we know the headlines of scoping the binding variables in pattern matching. Moreover, we know fromthe previous problem that in the type pattern forinstanceof, we have a single binding variable. It is time to see somepractical examples, so let’s quickly crop this snippet from the previous problem:

if (oinstanceof Filefile) {return"Saving a file of size: "     + String.format("%,d bytes",file.length());}// 'file' is out of scope here

In this snippet, thefile binding variable is visible in theif-then block. Once the block is closed, thefile binding variable is out of scope. But, thanks to flow scoping, a binding variable can be used in theif statementthat has introduced it to define a so-calledguarded pattern. Here it is:

// 'file' is created ONLY if 'instanceof' returns trueif (oinstanceof File file// this is evaluated ONLY if 'file' was created    && file.length() >0 && file.length() <1000) {return"Saving a file of size: "     + String.format("%,d bytes", file.length());}// another exampleif (oinstanceof Path path     && Files.size(path) >0 && Files.size(path) <1000) {return"Saving a file of size: "     + String.format("%,d bytes", Files.size(path));}

The conditional part that startswith the&& short-circuit operator is evaluated by the compiler only if theinstanceof operator is evaluated totrue. Thismeans that you cannot use the|| operator instead of&&. For instance, is not logical to write this:

// this will not compileif (oinstanceof Path path  || Files.size(path) >0 && Files.size(path) <1000) {...}

On the other hand, this is perfectly acceptable:

if (oinstanceof Path path  && (Files.size(path) >0 || Files.size(path) <1000)) {...}

We can also extend the scope of the binding variable as follows:

if (!(oinstanceof String str)) {// str is not available herereturn"I cannot save the given object";}else {return"Saving a string of size: "     + String.format("%,d bytes", str.length());}

Since we negate theif-then statement, thestr binding variable is available in theelse branch. Following this logic, we can useearly returns as well:

publicintgetStringLength(Object o) {if (!(oinstanceof String str)) {return0;  }return str.length();}

Thanks to flow scoping, the compiler can set up strict boundaries for the scope of binding variables. For instance, in the following code, there is no risk of overlapping even if we keep using the same name for the binding variables:

private StringstrNumber(Object o) {if (oinstanceof Integernr) {return String.valueOf(nr.intValue()); }elseif (oinstanceof Longnr) {return String.valueOf(nr.longValue()); }else {// nr is out of scope herereturn"Probably a float number"; }}

Here, eachnr binding variable has a scope that covers only its own branch. No overlapping, no conflicts! However, using the same name for the multiple binding variables can be a little bit confusing, so it is better to avoid it. For instance, we can useintNr andlongNr instead of simplenr.

Another confusing scenario that is highly recommended to be avoided implies binding variables that hide fields. Check out this code:

privatefinalStringstr="   I am a string with leading and trailing spaces     ";public Stringconvert(Object o) {// local variable (binding variable) hides a fieldif (oinstanceof Stringstr) {return str.strip();// refers to binding variable, str  }else {return str.strip();// refers to field, str  } }

So, using the same name for binding variables (this is true for any local variable as well) and fields is a bad practice that should be avoided.

In JDK 14/15, we cannot reassign binding variables because they are declaredfinal by default. However, JDK 16+ solved the asymmetries that may occur between local and binding variables by removing thefinal modifier. So, startingwith JDK 16+, we can reassign binding variables as in the following snippet:

Stringdummy="";privateintgetLength(Object o) {  if(oinstanceof String str) {      str = dummy;// reassigning binding variable// returns the length of 'dummy' not the passed 'str'return str.length();   }return0;}

Even if this is possible, it is highly recommended to avoid suchcode smells and keep the world clean and happy by not re-assigning your binding variables.

60. Rewriting equals() via type patterns for instanceof

It is not mandatory to rely oninstanceof to implement theequals() method, but it is a convenient approach to write something as follows:

publicclassMyPoint {privatefinalint x;privatefinalint y;privatefinalint z;publicMyPoint(int x,int y,int z) {this.x = x;this.y = y;this.z = z;  }@Overridepublicbooleanequals(Object obj) {if (this == obj) {returntrue;    }if (!(objinstanceof MyPoint)) {returnfalse;    }finalMyPointother= (MyPoint) obj;return (this.x == other.x &&this.y == other.y      &&this.z == other.z);   }       }

If you are a fan of theprevious approach for implementingequals(), then you’ll love rewriting it via a type pattern forinstanceof. Check out the following snippet:

@Overridepublicbooleanequals(Object obj) {if (this == obj) {returntrue;  }return objinstanceof MyPoint other    &&this.x == other.x &&this.y == other.y    &&this.z == other.z; }

IfMyPoint is generic (MyPoint<E>) then simply use a wildcard as follows (more details are available in the next problem):

return objinstanceof MyPoint<?> other  &&this.x == other.x &&this.y == other.y  &&this.z == other.z;

Cool, right?! However, pay attention that usinginstanceof to express theequals() contract imposes the usage of afinal class offinal equals(). Otherwise, if subclasses are allowed to overrideequals(), theninstanceof may cause transitivity/symmetry bugs. A good approach is to passequals() through a dedicated verifier such as equals verifier (https://github.com/jqno/equalsverifier), which is capable of checking the validity of theequals() andhashCode() contracts.

61. Tackling type patterns for instanceof and generics

Consider the followingsnippet of code that usesinstanceof in the old-school fashion:

publicstatic <K, V>voidprocess(Map<K, ? extends V> map) {if (mapinstanceof EnumMap<?, ?extendsV>) {    EnumMap<?, ?extendsV> books      = (EnumMap<?, ?extendsV>) map;if (books.get(Status.DRAFT)instanceof Book) {Bookbook= (Book) books.get(Status.DRAFT);      book.review();    }  }}// use caseEnumMap<Status, Book> books =newEnumMap<>(Status.class);books.put(Status.DRAFT,newBook());books.put(Status.READY,newBook());process(books);

As we know fromProblem 56, we can combineinstanceof with generic types via unbounded wildcards, such as ourEnumMap<?, ? extends V> (orEnumMap<?, ?>, but notEnumMap<K, ? extends V>,EnumMap<K, ?>, orEnumMap<K, V>).

This code can be written more concisely via the type pattern forinstanceof as follows:

publicstatic <K, V>voidprocess(Map<K, ? extends V> map) {if (mapinstanceof EnumMap<?, ?extendsV> books    && books.get(Status.DRAFT)instanceof Book book) {      book.review();  }}

In the example based on plaininstanceof, we can also replaceEnumMap<?, ? extends V> withMap<?, ? extends V>. But, as we know fromProblem 53, this is not possible with type patterns because the expression type cannot be a subtype of pattern type (upcasting is allowed). However, this is not an issue anymore starting with JDK 21.

62. Tackling type patterns for instanceof and streams

Let’s consider aList<Engine> whereEngine is an interface implemented by several classes such asHypersonicEngine,HighSpeedEngine, andRegularEngine. Our goal is to filter thisList and eliminate allRegularEngine classes that are electric and cannot pass our autonomy test. So, we can write code as follows:

publicstatic List<Engine>filterRegularEngines(              List<Engine> engines,int testSpeed) {for (Iterator<Engine> i = engines.iterator(); i.hasNext();){finalEnginee= i.next();if (einstanceof RegularEngine) {finalRegularEnginepopularEngine= (RegularEngine) e;if (popularEngine.isElectric()) {if (!hasEnoughAutonomy(popularEngine, testSpeed)) {          i.remove();        }      }    }  }return engines;}

But, starting with JDK 8, we can safely remove from aList without using anIterator via adefault method fromjava.util.Collection namedpublic default boolean removeIf(Predicate<? super E> filter). If we combine this method (and, therefore, the Stream API) with type patterns forinstanceof, then we can simplify the previous code as follows:

publicstatic List<Engine>filterRegularEngines(              List<Engine> engines,int testSpeed) {  engines.removeIf(e -> einstanceof RegularEngine engine     && engine.isElectric()    && !hasEnoughAutonomy(engine, testSpeed));return engines;}

So, whenever you have the chance to use type patterns with the Stream API, don’t hesitate.

63. Introducing type pattern matching for switch

JDK 17 (JEP 406) added type pattern matching forswitch as a preview feature. A second preview was available in JDK 18 (JEP 420). The final release is available in JDK 21 as JEP 441.

Type pattern matching forswitch allows theselector expression(that is,o inswitch(o)) to be of any type not just anenum constant, number, or string. By “any type,” I mean any type (any object type,enum type, array type, record type, or sealed type)! The type pattern matching is not limited to a single hierarchy as it happens in the case of inheritance polymorphism. Thecase labels can have type patterns (referred to as case pattern labels or, simply, pattern labels), so the selector expression (o) can be matched against a type pattern, not only against a constant.

In the next snippet of code, we rewrote the example fromProblem 58 via a type pattern forswitch:

publicstatic Stringsave(Object o)throws IOException {returnswitch(o) {case File file ->"Saving a file of size: "               + String.format("%,d bytes", file.length());case Path path ->"Saving a file of size: "               + String.format("%,d bytes", Files.size(path));case String str ->"Saving a string of size: "               + String.format("%,d bytes", str.length());casenull ->"Why are you doing this?";default ->"I cannot save the given object";  }; }

The following figure identifies the main players of aswitch branch:

Figure 2.33.png

Figure 2.33: Type pattern matching for switch

Thecase fornull is notmandatory. We have added it just for the sake of completeness. On the other hand, thedefault branch is a must, but this topic is covered later in this chapter.

64. Adding guarded pattern labels in switch

Do you remember that type patterns forinstanceof can be refined with extraboolean checks applied to the binding variables to obtain fine-grained use cases? Well, we can do the same for theswitch expressions thatuse pattern labels. The result is namedguarded pattern labels. Let’s consider the following code:

privatestatic StringturnOnTheHeat(Heater heater) {returnswitch (heater) {case Stove stove ->"Make a fire in the stove";case Chimney chimney ->"Make a fire in the chimney";default ->"No heater available!";  };}

Having aStove and aChimney, thisswitch decides where to make a fire based on pattern labels. But, what will happen ifChimney is electric? Obviously, we will have to plugChimney in instead of firing it up. This means that we should add a guarded pattern label that helps us to make the difference between an electric and non-electricChimney:

returnswitch (heater) {case Stove stove ->"Make a fire in the stove";case Chimney chimney    && chimney.isElectric() ->"Plug in the chimney";case Chimney chimney ->"Make a fire in the chimney";default ->"No heater available!";};

Well, that was easy, wasn’t it? Let’s have another example that starts from the following code:

enumFuelType{ GASOLINE, HYDROGEN, KEROSENE }classVehicle {privatefinalint gallon;privatefinal FuelType fuel;  ...}

For eachVehicle, we know the fuel type and how many gallons of fuel fit in the tank. Now, we can write aswitch that can relyon guarded pattern labels to try to guess the type of the vehicle based on this information:

privatestatic StringtheVehicle(Vehicle vehicle) {returnswitch (vehicle) {case Vehicle v && v.getFuel().equals(GASOLINE)      && v.getGallon() <120 ->"probably a car/van";case Vehicle v && v.getFuel().equals(GASOLINE)      && v.getGallon() >120 ->"probably a big rig";case Vehicle v && v.getFuel().equals(HYDROGEN)       && v.getGallon() <300_000 ->"probably an aircraft";case Vehicle v && v.getFuel().equals(HYDROGEN)       && v.getGallon() >300_000 ->"probably a rocket";case Vehicle v && v.getFuel().equals(KEROSENE)       && v.getGallon() >2_000 && v.getGallon() <6_000          ->"probably a narrow-body aircraft";case Vehicle v && v.getFuel().equals(KEROSENE)       && v.getGallon() >6_000 && v.getGallon() <55_000         ->"probably a large (B747-400) aircraft";default ->"no clue";  };}

Notice that the pattern labels are the same in all cases (Vehicle v) and the decision is refined via the guarded conditions. The previous examples work just fine in JDK 17 and 18, but they don’t work starting with JDK 19+. Because the&& operator was considered confusing, starting with JDK 19+, we have to deal with a refinement syntax. Practically, instead of the&& operator, we use the new context-specific keywordwhen between the pattern label and the refiningboolean checks. So, in JDK 19+, the previous code becomes:

returnswitch (vehicle) {case Vehicle vwhen(v.getFuel().equals(GASOLINE)    && v.getGallon() <120) ->"probably a car/van";case Vehicle vwhen(v.getFuel().equals(GASOLINE)    && v.getGallon() >120) ->"probably a big rig";   ...case Vehicle vwhen(v.getFuel().equals(KEROSENE)     && v.getGallon() >6_000 && v.getGallon() <55_000)      ->"probably a large (B747-400) aircraft";default ->"no clue";};

In the bundled code, you can findboth versions for JDK 17/18, andJDK 19+.

65. Dealing with pattern label dominance in switch

The compiler matches the selector expression against the available pattern labels by testing the selector expressionagainst each label starting from top to bottom (or, from the first to the last) in the exact order in which we wrote them in theswitch block. This means that the first match wins. Let’s assume that we have the following base class (Pill) and some pills (Nurofen,Ibuprofen, andPiafen):

abstractclassPill {}classNurofenextendsPill {}classIbuprofenextendsPill {}classPiafenextendsPill {}

Hierarchically speaking,Nurofen,Ibuprofen, andPiafen are three classes placed at the same hierarchical level since all of them have thePill class as the base class. In an IS-A inheritance relationship, we say thatNurofen is aPill,Ibuprofen is aPill, andPiafen is also aPill. Next, let’s use aswitch to serve our clients the proper headache pill:

privatestatic Stringheadache(Pill o) {returnswitch(o) {case Nurofen nurofen ->"Get Nurofen ...";case Ibuprofen ibuprofen ->"Get Ibuprofen ...";case Piafen piafen ->"Get Piafen ...";default ->"Sorry, we cannot solve your headache!";  };}

Callingheadache(new Nurofen()) will match the first pattern label,Nurofen nurofen. In the same manner,headache(new Ibuprofen()) matches the second pattern label, andheadache(new Piafen()) matches the third one. No matter how we mix the order of these label cases, they will work as expected because they are on the same level and none of them dominate the others.

For instance, since people don’t want headaches, they order a lot of Nurofen, so we don’t have any anymore. We represent this by removing/comment the corresponding case:

returnswitch(o) {// case Nurofen nurofen -> "Get Nurofen ...";case Ibuprofen ibuprofen ->"Get Ibuprofen ...";case Piafen piafen ->"Get Piafen ...";default ->"Sorry, we cannot solve your headache!";};

So, what happens when a client wants Nurofen? You’re right … thedefault branch will take action sinceIbuprofen andPiafen don’t match the selector expression.

But, what will happen if we modify theswitch as follows?

returnswitch(o) {case Pill pill ->"Get a headache pill ...";case Nurofen nurofen ->"Get Nurofen ...";case Ibuprofen ibuprofen ->"Get Ibuprofen ...";case Piafen piafen ->"Get Piafen ...";};

Adding thePill base class as a pattern label case allows us to remove thedefault branch since we cover all possible values (this is covered in detail inProblem 66). This time, the compiler will raise an error to inform us that thePill label case dominates the rest of the label cases. Practically, the first label casePill pill dominates all other label cases because every value that matches any of theNurofen nurofen,Ibuprofen ibuprofen,Piafen piafen patterns also matches the patternPill pill. So,Pill pill always wins while the rest of the label cases are useless. SwitchingPill pillwithNurofen nurofen will give a chance toNurofen nurofen, butPill pillwill still dominate the remaining two. So, we can eliminate the dominance of the base classPill by moving its label case to the last position:

returnswitch(o) {case Nurofen nurofen ->"Get Nurofen ...";case Ibuprofen ibuprofen ->"Get Ibuprofen ...";case Piafenpiafen ->"Get Piafen ...";  case Pill pill ->"Get a headache pill ...";};

Now, every pattern label has a chance to win.

Let’s have another example that starts from this hierarchy:

abstractclassDrink {}classSmallextendsDrink {}classMediumextendsSmall {}classLargeextendsMedium {}classExtraextendsMedium {}classHugeextendsLarge {}classJumboextendsExtra {}

This time, we have seven classes disposed of in a multi-level hierarchy. If we exclude the base classDrink, we can represent the rest of them in aswitch as follows:

privatestatic StringbuyDrink(Drink o) {returnswitch(o) {case Jumbo j:yield"We can give a Jumbo ...";case Huge h:yield"We can give a Huge ...";case Extra e:yield"We can give a Extra ...";case Large l:yield"We can give a Large ...";case Medium m:yield"We can give a Medium ...";case Small s:yield"We can give a Small ...";default:yield"Sorry, we don't have this drink!";  };}

The order of pattern labels is imposed by the class hierarchy and is quite strict, but we can make some changes without creating any dominance issues. For instance, sinceExtra andLarge are subclasses ofMedium, we can switch their positions. Some things apply toJumbo andHuge since they are both subclasses ofMedium viaExtra, respectivelyLarge.

In this context, the compiler evaluates the selection expression by trying to match it against this hierarchy via an IS-A inheritance relationship. For instance, let’s order aJumbo drink while there are no moreJumbo andExtra drinks:

returnswitch(o) {case Huge h:yield"We can give a Huge ...";case Large l:yield"We can give a Large ...";case Medium m:yield"We can give a Medium ...";case Small s:yield"We can give a Small ...";default:yield"Sorry, we don't have this drink!";};

If we orderJumbo (o isJumbo), then we will getMedium. Why? The compiler matchesJumbo againstHuge without success. The same result is obtained while matchingJumbo againstLarge. However, when it matchesJumbo againstMedium, it sees thatJumbo is aMedium subclass via theExtra class. So, sinceJumbo isMedium, the compiler chooses theMedium m pattern label. At thispoint,Medium matchesJumbo,Extra, andMedium. So, soon we will be out ofMedium as well:

returnswitch(o) {case Huge h:yield"We can give a Huge ...";case Large l:yield"We can give a Large ...";case Small s:yield"We can give a Small ...";default:yield"Sorry, we don't have this drink!";};

This time, any request forJumbo,Extra,Medium, orSmall will give us aSmall. I think you get the idea.

Let’s take a step further, and analyze this code:

privatestaticintoneHundredDividedBy(Integer value) {returnswitch(value) {case Integer i ->100/i;case0 ->0;  };}

Have you spotted the problem? A pattern label case dominates a constant label case, so the compiler will complain about the fact that the second case (case 0) is dominated by the first case. This is normal, since 0 is anInteger as well, so it will match the pattern label. The solution requires switching the cases:

returnswitch(value) {case0 ->0;case Integer i ->100/i;  };

Here is another case to enforce this type of dominance:

enumHero { CAPTAIN_AMERICA, IRON_MAN, HULK }privatestatic StringcallMyMarvelHero(Hero hero) {returnswitch(hero) {case Hero h ->"Calling " + h;    case HULK ->"Sorry, we cannot call this guy!";  };}

In this case, the constant isHULK and it is dominated by theHero h pattern label case. This is normal, sinceHULK is also aMarvel hero, soHero h will match all Marvel heroes includingHULK. Again, the fix relies on switching the cases:

returnswitch(hero) {case HULK ->"Sorry, we cannot call this guy!";case Hero h ->"Calling " + h;  };

Okay, finally, let’s tackle this snippet of code:

privatestaticintoneHundredDividedByPositive(Integer value){returnswitch(value) {case Integer i when i >0 ->100/i;case0 ->0;case Integer i -> (-1) *100/i;  };}

You may think that if we enforce theInteger i pattern label with a condition that forcesi to be strictly positive, then the constant label will not be dominated. But, this is not true; a guarded pattern label still dominates a constant label. The proper order places the constant labels first, followed by guarded pattern labels, and finally, by non-guarded pattern labels. The next code fixes the previous one:

returnswitch(value) {case0 ->0;case Integer i when i >0 ->100/i;case Integer i -> (-1) *100/i;};

Okay, I think you get the idea. Feel free to practice all these examples in the bundled code.

66. Dealing with completeness (type coverage) in pattern labels for switch

In a nutshell,switch expressions andswitch statements that use null and/or pattern labels should be exhaustive. In other words, we must cover with explicit switch case labels all the possible values. Let’s consider the following example:

classVehicle {}classCarextendsVehicle {}classVanextendsVehicle {}privatestatic StringwhatAmI(Vehicle vehicle) {returnswitch(vehicle) {case Car car ->"You're a car";case Van van ->"You're a van";  };}

This snippet of code doesn’t compile. The error is clear:The switch expression does not cover all possible input values. The compiler complains because we don’t have a case pattern label forVehicle. This base class can be legitimately used without being aCar or aVan, so it is a valid candidate for ourswitch. We can add acase Vehicle or adefault label. If you know thatVehicle will remain an empty base class, then you’ll probably go for adefault label:

returnswitch(vehicle) {case Car car ->"You're a car";case Van van ->"You're a van";default ->"I have no idea ... what are you?";  };

If we continue by adding another vehicle such asclass Truck extends Vehicle {}, then this will be handled by thedefault branch. If we plan to useVehicle as an independent class (for instance, to enrich it with methods and functionalities), then we will prefer to add acase Vehicle as follows:

returnswitch(vehicle) {case Car car ->"You're a car";case Van van ->"You're a van";case Vehicle v ->"You're a vehicle";// total pattern};

This time, theTruck class will match thecase Vehicle branch. Of course, we can add acase Truck as well.

Important note

TheVehicle v pattern is named atotal type pattern. There are two labels that we can use to match all possible values: the total type pattern (for instance, a base class or an interface) and thedefault label. Generally speaking, a total pattern is a pattern that can be used instead of thedefault label.

In the previous example, we can accommodate all possible values via the total pattern or thedefault label but not both. This makes sense since thewhatAmI(Vehicle vehicle) method getsVehicle as an argument. So, in this example, the selector expression can be onlyVehicle or a subclass ofVehicle. How about modifying this method aswhatAmI(Object o)?

privatestatic StringwhatAmI(Object o) {returnswitch(o) {case Car car ->"You're a car";case Van van ->"You're a van";case Vehicle v ->"You're a vehicle";// optionaldefault ->"I have no idea ... what are you?";  };}

Now, the selector expression can be any type, which means that the total patternVehicle v is not total anymore. WhileVehicle v becomes an optional ordinary pattern, the new total pattern iscase Object obj. This means that we can cover all possible values by adding thedefault label or thecase Object obj total pattern:

returnswitch(o) {case Car car ->"You're a car";case Van van ->"You're a van";case Vehicle v ->"You're a vehicle";// optionalcase Object obj ->"You're an object";// total pattern};

I think you get the idea! How about using an interface for the base type? For instance, here is an example based on the Java built-inCharSequence interface:

publicstatic StringwhatAmI(CharSequence cs) {returnswitch(cs) {case String str ->"You're a string";case Segment segment ->"You're a Segment";case CharBuffer charbuffer ->"You're a CharBuffer";case StringBuffer strbuffer ->"You're a StringBuffer";case StringBuilder strbuilder ->"You're a StringBuilder";  };}

This snippet of code doesn’t compile. The error is clear:The switch expression does not cover all possible input values. But, if we check the documentation ofCharSequence, we see that it is implemented by five classes:CharBuffer,Segment,String,StringBuffer, andStringBuilder. In our code, each of these classes is covered by a pattern label, so we have covered all possiblevalues, right? Well, yes and no… “Yes” because we cover all possible values for the moment, and “no” because anyone can implement theCharSequence interface, which will break the exhaustive coverage of ourswitch. We can do this:

publicclassCoolCharimplementsCharSequence{ ... }

At this moment, theswitch expression doesn’t cover theCoolChar type. So, we still need adefault label or the total pattern,case CharSequence charseq, as follows:

returnswitch(cs) {case String str ->"You're a string";  ...case StringBuilder strbuilder ->"You're a StringBuilder";  // we have created thiscase CoolChar cool ->"Welcome ... you're a CoolChar";// this is a total patterncase CharSequence charseq ->"You're a CharSequence";// can be used instead of the total pattern// default -> "I have no idea ... what are you?";};

Okay, let’s tackle this scenario on thejava.lang.constant.ClassDesc built-in interface:

privatestatic StringwhatAmI(ConstantDesc constantDesc) {returnswitch(constantDesc) {case Integer i ->"You're an Integer";case Long l ->"You're a Long";case Float f ->" You're a Float";case Double d ->"You're a Double";case String s ->"You're a String";case ClassDesc cd ->"You're a ClassDesc";case DynamicConstantDesc dcd ->"You're a DCD";case MethodHandleDesc mhd ->"You're a MethodHandleDesc";case MethodTypeDesc mtd ->"You're a MethodTypeDesc";  };}

This code compiles! There is nodefault label and no total pattern but theswitch expression covers all possiblevalues. How so?! This interface is declared as sealed via thesealed modifier:

publicsealedinterfaceClassDesc  extendsConstantDesc, TypeDescriptor.OfField<ClassDesc>

Sealed interfaces/classes were introduced in JDK 17 (JEP 409) and we will cover this topic inChapter 8. However, for now, it is enough to know that sealing allows us to have fine-grained control of inheritance so classes and interfaces define their permitted subtypes. This means that the compiler can determine all possible values in aswitch expression. Let’s consider a simpler example that starts as follows:

sealedinterfacePlayer {}finalclassTennisimplementsPlayer {}finalclassFootballimplementsPlayer {}finalclassSnookerimplementsPlayer {}

And, let’s have aswitch expression covering all possible values forPlayer:

privatestatic StringtrainPlayer(Player p) {returnswitch (p) {case Tennis t ->"Training the tennis player ..." + t;case Football f ->"Training the football player ..." + f;case Snooker s ->"Training the snooker player ..." + s;  };}

The compiler is aware that thePlayer interface has only three implementations and all of them are covered via pattern labels. We can add adefault label or the total patterncase Player player, but you most probably don’t want to do that. Imagine that we add a new implementation of the sealedPlayer interface namedGolf:

finalclassGolfimplementsPlayer {}

If theswitch expression has adefault label, thenGolf values will be handled by thisdefault branch. If we have the total patternPlayer player, then this pattern will handle theGolf values. On the other hand, if none of thedefault labels or total patterns are present, the compilerwill immediately complain that theswitch expression doesn’t cover all possible values. So, we are immediately informed, and once we add acase Golf g, the error disappears. This way, we can easily maintain our code and have a guarantee that ourswitch expressions are always up to date and cover all possible values. The compiler will never miss the chance to inform us when a new implementation ofPlayer is available.

A similar logic applies to Java enums. Consider the followingenum:

privateenumPlayerTypes{ TENNIS, FOOTBALL, SNOOKER }

The compiler is aware of all the possible values forPlayerTypes, so the followingswitch expression compiles successfully:

privatestatic StringcreatePlayer(PlayerTypes p) {returnswitch (p) {case TENNIS ->"Creating a tennis player ...";case FOOTBALL ->"Creating a football player ...";case SNOOKER ->"Creating a snooker player ...";  };}

Again, we can add adefault label or the total pattern,case PlayerTypes pt. But, if we add a new value in theenum (for instance,GOLF), the compiler will delegate thedefault label or the total pattern to handle it. On the other hand, if none of these are available, the compiler will immediately complain that theGOLF value is not covered, so we can add it (case GOLF g) and create a golf player whenever required.

So far, so good! Now, let’s consider the following context:

finalstaticclassPlayerClubimplementsSport {};privateenumPlayerTypesimplementsSport  { TENNIS, FOOTBALL, SNOOKER }sealedinterfaceSport permits PlayerTypes, PlayerClub {};

The sealed interfaceSport allows only two subtypes:PlayerClub (a class) andPlayerTypes (an enum). If we write aswitch that covers all possible values forSport, then it will look as follows:

privatestatic StringcreatePlayerOrClub(Sport s) {returnswitch (s) {case PlayerTypes p when p == PlayerTypes.TENNIS      ->"Creating a tennis player ...";case PlayerTypes p when p == PlayerTypes.FOOTBALL      ->"Creating a football player ...";case PlayerTypes p ->"Creating a snooker player ...";case PlayerClub p ->"Creating a sport club ...";  };}

We immediately observe that writing casePlayerTypes p when p == PlayerTypes.TENNIS is not quite neat. What we actually want iscase PlayerTypes.TENNIS but, until JDK 21, this is not possible since qualified enum constants cannot be used incase labels. However, starting with JDK 21, we can use qualified names of enum constants as labels, so we can write this:

privatestatic StringcreatePlayerOrClub(Sport s) {returnswitch (s) {case PlayerTypes.TENNIS      ->"Creating a tennis player ...";case PlayerTypes.FOOTBALL      ->"Creating a football player ...";case PlayerTypes.SNOOKER      ->"Creating a snooker player ...";case PlayerClub p       ->"Creating a sport club ...";  };}

Done! Now you know how to deal with type coverage inswitch expressions.

67. Understanding the unconditional patterns and nulls in switch expressions

Let’s imagine that we use JDK 17 and we have thefollowing code:

privatestatic Stringdrive(Vehicle v) {returnswitch (v) {case Truck truck ->"truck: " + truck;case Van van ->"van: " + van;case Vehicle vehicle ->"vehicle: " + vehicle.start();  };}drive(null);

Notice the call,drive(null). This call will hit theVehicle vehicle total pattern, so evennull values match total patterns. But, this means that the binding variablevehicle will also benull, which means that this branch is prone toNullPointerException (for instance, if we call a hypothetical method,vehicle.start()):

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "modern.challenge.Vehicle.start()" because "vehicle" is null

BecauseVehicle vehicle matches all possible values, it is known as a total pattern but also as anunconditional pattern since it matches everything unconditionally.

But, as we know fromProblem 54, starting with JDK 17+ (JEP 427), we can have a pattern label fornull itself, so we can handle the previous shortcoming as follows:

returnswitch (v) {case Truck truck ->"truck: " + truck;case Van van ->"van: " + van;casenull ->"so, you don't have a vehicle?";case Vehicle vehicle ->"vehicle: " + vehicle.start();  };

Yes, everybody agrees that adding acase null between vehicles looks awkward. Adding it at the end is not an option since will raise a dominance issue. So, starting with JDK 19+, adding thiscase null is no longer needed in this kind of scenario. Basically, the idea remains the same meaning that the unconditional pattern still only matchesnull values so it will not allow the execution of that branch. Actually, when anull value occurs, theswitch expressions will throw aNullPointerException without even looking at the patterns. So, in JDK 19+, this code will throw an NPE right away:

returnswitch (v) {case Truck truck ->"truck: " + truck;  case Van van ->"van: " + van;  // we can still use a null check// case null -> "so, you don't have a vehicle?";// total/unconditional pattern throw NPE immediatelycase Vehicle vehicle ->"vehicle: " + vehicle.start();};

The NPE message revealsthatvehicle.start() was never called. The NPEoccurred much earlier:

Exception in thread "main" java.lang.NullPointerExceptionatjava.base/java.util.Objects.requireNonNull(Objects.java:233)

We will expand on this topic later when we will talk about Java records.

Summary

That’s all folks! This was a comprehensive chapter that covered four main topics, among others:java.util.Objects, immutability,switch expressions, and pattern matching forinstanceof andswitch expressions.

Join our community on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.gg/8mgytp5DGQ

Left arrow icon

Page1 of 33

Right arrow icon
Download code iconDownload Code

Key benefits

  • Solve Java programming challenges and get interview-ready with the power of modern Java 21
  • Test your Java skills using language features, algorithms, data structures, and design patterns
  • Explore tons of examples, all fully refreshed for this edition, meant to help you accommodate JDK 12 to JDK 21

Description

The super-fast evolution of the JDK between versions 12 and 21 has made the learning curve of modern Java steeper, and increased the time needed to learn it. This book will make your learning journey quicker and increase your willingness to try Java’s new features by explaining the correct practices and decisions related to complexity, performance, readability, and more.Java Coding Problems takes you through Java’s latest features but doesn’t always advocate the use of new solutions — instead, it focuses on revealing the trade-offs involved in deciding what the best solution is for a certain problem.There are more than two hundred brand new and carefully selected problems in this second edition, chosen to highlight and cover the core everyday challenges of a Java programmer.Apart from providing a comprehensive compendium of problem solutions based on real-world examples, this book will also give you the confidence to answer questions relating to matching particular streams and methods to various problems.By the end of this book you will have gained a strong understanding of Java’s new features and have the confidence to develop and choose the right solutions to your problems.

Who is this book for?

If you are a Java developer who wants to level-up by solving real-world problems, then this book is for you. Working knowledge of the Java programming language is required to get the most out of this book

What you will learn

  • Adopt the latest JDK 21 features in your applications
  • Explore Records, Record Patterns, Record serialization and so on
  • Work with Sealed Classes and Interfaces for increasing encapsulation
  • Learn how to exploit Context-Specific Deserialization Filters
  • Solve problems relating to collections and esoteric data structures
  • Learn advanced techniques for extending the Java functional API
  • Explore the brand-new Socket API and Simple Web Server
  • Tackle modern Garbage Collectors and Dynamic CDS Archives

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date :Mar 19, 2024
Length:798 pages
Edition :2nd
Language :English
ISBN-13 :9781837637614
Category :

What do you get with eBook?

Product feature iconInstant access to your Digital eBook purchase
Product feature icon Download this book inEPUB andPDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature iconDRM FREE - Read whenever, wherever and however you want
Product feature iconAI Assistant (beta) to help accelerate your learning
OR

Contact Details

Modal Close icon
Payment Processing...
tickCompleted

Billing Address

Product Details

Publication date :Mar 19, 2024
Length:798 pages
Edition :2nd
Language :English
ISBN-13 :9781837637614
Category :
Concepts :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99billed monthly
Feature tick iconUnlimited access to Packt's library of 7,000+ practical books and videos
Feature tick iconConstantly refreshed with 50+ new titles a month
Feature tick iconExclusive Early access to books as they're written
Feature tick iconSolve problems while you work with advanced search and reference features
Feature tick iconOffline reading on the mobile app
Feature tick iconSimple pricing, no contract
$199.99billed annually
Feature tick iconUnlimited access to Packt's library of 7,000+ practical books and videos
Feature tick iconConstantly refreshed with 50+ new titles a month
Feature tick iconExclusive Early access to books as they're written
Feature tick iconSolve problems while you work with advanced search and reference features
Feature tick iconOffline reading on the mobile app
Feature tick iconChoose a DRM-free eBook or Video every month to keep
Feature tick iconPLUS own as many other DRM-free eBooks or Videos as you like for just S$6 each
Feature tick iconExclusive print discounts
$279.99billed in 18 months
Feature tick iconUnlimited access to Packt's library of 7,000+ practical books and videos
Feature tick iconConstantly refreshed with 50+ new titles a month
Feature tick iconExclusive Early access to books as they're written
Feature tick iconSolve problems while you work with advanced search and reference features
Feature tick iconOffline reading on the mobile app
Feature tick iconChoose a DRM-free eBook or Video every month to keep
Feature tick iconPLUS own as many other DRM-free eBooks or Videos as you like for just S$6 each
Feature tick iconExclusive print discounts

Frequently bought together


The Complete Coding Interview Guide in Java
The Complete Coding Interview Guide in Java
Read more
Aug 2020788 pages
Full star icon4.9 (9)
eBook
eBook
S$46.99S$52.99
S$66.99
S$83.99
Java Coding Problems
Java Coding Problems
Read more
Mar 2024798 pages
Full star icon4.5 (14)
eBook
eBook
S$53.98S$59.99
S$74.99
Mastering the Java Virtual Machine
Mastering the Java Virtual Machine
Read more
Feb 2024234 pages
Full star icon3.1 (8)
eBook
eBook
S$38.99S$43.99
S$53.99
Stars icon
TotalS$195.97
The Complete Coding Interview Guide in Java
S$66.99
Java Coding Problems
S$74.99
Mastering the Java Virtual Machine
S$53.99
TotalS$195.97Stars icon

Table of Contents

15 Chapters
Text Blocks, Locales, Numbers, and MathChevron down iconChevron up icon
Text Blocks, Locales, Numbers, and Math
Problems
1. Creating a multiline SQL, JSON, and HTML string
2. Exemplifying the usage of text block delimiters
3. Working with indentation in text blocks
4. Removing incidental white spaces in text blocks
5. Using text blocks just for readability
6. Escaping quotes and line terminators in text blocks
7. Translating escape sequences programmatically
8. Formatting text blocks with variables/expressions
9. Adding comments in text blocks
10. Mixing ordinary string literals with text blocks
11. Mixing regular expression with text blocks
12. Checking if two text blocks are isomorphic
13. Concatenating strings versus StringBuilder
14. Converting int to String
15. Introducing string templates
16. Writing a custom template processor
17. Creating a Locale
18. Customizing localized date-time formats
19. Restoring Always-Strict Floating-Point semantics
20. Computing mathematical absolute value for int/long and result overflow
21. Computing the quotient of the arguments and result overflow
22. Computing the largest/smallest value that is less/greater than or equal to the algebraic quotient
23. Getting integral and fractional parts from a double
24. Testing if a double number is an integer
25. Hooking Java (un)signed integers in a nutshell
26. Returning the flooring/ceiling modulus
27. Collecting all prime factors of a given number
28. Computing the square root of a number using the Babylonian method
29. Rounding a float number to specified decimals
30. Clamping a value between min and max
31. Multiply two integers without using loops, multiplication, bitwise, division, and operators
32. Using TAU
33. Selecting a pseudo-random number generator
34. Filling a long array with pseudo-random numbers
35. Creating a stream of pseudo-random generators
36. Getting a legacy pseudo-random generator from new ones of JDK 17
37. Using pseudo-random generators in a thread-safe fashion (multithreaded environments)
Summary
Objects, Immutability, Switch Expressions, and Pattern MatchingChevron down iconChevron up icon
Objects, Immutability, Switch Expressions, and Pattern Matching
Problems
38. Explain and exemplifying UTF-8, UTF-16, and UTF-32
39. Checking a sub-range in the range from 0 to length
40. Returning an identity string
41. Hooking unnamed classes and instance main methods
42. Adding code snippets in Java API documentation
43. Invoking default methods from Proxy instances
44. Converting between bytes and hex-encoded strings
45. Exemplify the initialization-on-demand holder design pattern
46. Adding nested classes in anonymous classes
47. Exemplify erasure vs. overloading
48. Xlinting default constructors
49. Working with the receiver parameter
50. Implementing an immutable stack
51. Revealing a common mistake with Strings
52. Using the enhanced NullPointerException
53. Using yield in switch expressions
54. Tackling the case null clause in switch
55. Taking on the hard way to discover equals()
56. Hooking instanceof in a nutshell
57. Introducing pattern matching
58. Introducing type pattern matching for instanceof
59. Handling the scope of a binding variable in type patterns for instanceof
60. Rewriting equals() via type patterns for instanceof
61. Tackling type patterns for instanceof and generics
62. Tackling type patterns for instanceof and streams
63. Introducing type pattern matching for switch
64. Adding guarded pattern labels in switch
65. Dealing with pattern label dominance in switch
66. Dealing with completeness (type coverage) in pattern labels for switch
67. Understanding the unconditional patterns and nulls in switch expressions
Summary
Working with Date and TimeChevron down iconChevron up icon
Working with Date and Time
Problems
68. Defining a day period
69. Converting between Date and YearMonth
70. Converting between int and YearMonth
71. Converting week/year to Date
72. Checking for a leap year
73. Calculating the quarter of a given date
74. Getting the first and last day of a quarter
75. Extracting the months from a given quarter
76. Computing pregnancy due date
77. Implementing a stopwatch
78. Extracting the count of milliseconds since midnight
79. Splitting a date-time range into equal intervals
80. Explaining the difference between Clock.systemUTC() and Clock.systemDefaultZone()
81. Displaying the names of the days of the week
82. Getting the first and last day of the year
83. Getting the first and last day of the week
84. Calculating the middle of the month
85. Getting the number of quarters between two dates
86. Converting Calendar to LocalDateTime
87. Getting the number of weeks between two dates
Summary
Records and Record PatternsChevron down iconChevron up icon
Records and Record Patterns
Problems
88. Declaring a Java record
89. Introducing the canonical and compact constructors for records
90. Adding more artifacts in a record
91. Iterating what we cannot have in a record
92. Defining multiple constructors in a record
93. Implementing interfaces in records
94. Understanding record serialization
95. Invoking the canonical constructor via reflection
96. Using records in streams
97. Introducing record patterns for instanceof
98. Introducing record patterns for switch
99. Tackling guarded record patterns
100. Using generic records in record patterns
101. Handling nulls in nested record patterns
102. Simplifying expressions via record patterns
103. Hooking unnamed patterns and variables
104. Tackling records in Spring Boot
105. Tackling records in JPA
106. Tackling records in jOOQ
Summary
Arrays, Collections, and Data StructuresChevron down iconChevron up icon
Arrays, Collections, and Data Structures
Problems
107. Introducing parallel computations with arrays
108. Covering the Vector API’s structure and terminology
109. Summing two arrays via the Vector API
110. Summing two arrays unrolled via the Vector API
111. Benchmarking the Vector API
112. Applying the Vector API to compute FMA
113. Multiplying matrices via the Vector API
114. Hooking the image negative filter with the Vector API
115. Dissecting factory methods for collections
116. Getting a list from a stream
117. Handling map capacity
118. Tackling Sequenced Collections
119. Introducing the Rope data structure
120. Introducing the Skip List data structure
121. Introducing the K-D Tree data structure
122. Introducing the Zipper data structure
123. Introducing the Binomial Heap data structure
124. Introducing the Fibonacci Heap data structure
125. Introducing the Pairing Heap data structure
126. Introducing the Huffman Coding data structure
127. Introducing the Splay Tree data structure
128. Introducing the Interval Tree data structure
129. Introducing the Unrolled Linked List data structure
130. Implementing join algorithms
Summary
Java I/O: Context-Specific Deserialization FiltersChevron down iconChevron up icon
Java I/O: Context-Specific Deserialization Filters
Problems
131. Serializing objects to byte arrays
132. Serializing objects to strings
133. Serializing objects to XML
134. Introducing JDK 9 deserialization filters
135. Implementing a custom pattern-based ObjectInputFilter
136. Implementing a custom class ObjectInputFilter
137. Implementing a custom method ObjectInputFilter
138. Implementing a custom lambda ObjectInputFilter
139. Avoiding StackOverflowError at deserialization
140. Avoiding DoS attacks at deserialization
141. Introducing JDK 17 easy filter creation
142. Tackling context-specific deserialization filters
143. Monitoring deserialization via JFR
Summary
Foreign (Function) Memory APIChevron down iconChevron up icon
Foreign (Function) Memory API
Problems
144. Introducing Java Native Interface (JNI)
145. Introducing Java Native Access (JNA)
146. Introducing Java Native Runtime (JNR)
147. Motivating and introducing Project Panama
148. Introducing Panama’s architecture and terminology
149. Introducing Arena and MemorySegment
150. Allocating arrays into memory segments
151. Understanding addresses (pointers)
152. Introducing the sequence layout
153. Shaping C-like structs into memory segments
154. Shaping C-like unions into memory segments
155. Introducing PaddingLayout
156. Copying and slicing memory segments
157. Tackling the slicing allocator
158. Introducing the slice handle
159. Introducing layout flattening
160. Introducing layout reshaping
161. Introducing the layout spreader
162. Introducing the memory segment view VarHandle
163. Streaming memory segments
164. Tackling mapped memory segments
165. Introducing the Foreign Linker API
166. Calling the sumTwoInt() foreign function
167. Calling the modf() foreign function
168. Calling the strcat() foreign function
169. Calling the bsearch() foreign function
170. Introducing Jextract
171. Generating native binding for modf()
Summary
Sealed and Hidden ClassesChevron down iconChevron up icon
Sealed and Hidden Classes
Problems
172. Creating an electrical panel (hierarchy of classes)
173. Closing the electrical panel before JDK 17
174. Introducing JDK 17 sealed classes
175. Introducing the permits clause
176. Closing the electrical panel after JDK 17
177. Combining sealed classes and records
178. Hooking sealed classes and instanceof
179. Hooking sealed classes in switch
180. Reinterpreting the Visitor pattern via sealed classes and type pattern matching for switch
181. Getting info about sealed classes (using reflection)
182. Listing the top three benefits of sealed classes
183. Briefly introducing hidden classes
184. Creating a hidden class
Summary
Functional Style Programming – Extending APIsChevron down iconChevron up icon
Functional Style Programming – Extending APIs
Problems
185. Working with mapMulti()
186. Streaming custom code to map
187. Exemplifying a method reference vs. a lamda
188. Hooking lambda laziness via Supplier/Consumer
189. Refactoring code to add lambda laziness
190. Writing a Function<String, T> for parsing data
191. Composing predicates in a Stream’s filters
192. Filtering nested collections with Streams
193. Using BiPredicate
194. Building a dynamic predicate for a custom model
195. Building a dynamic predicate from a custom map of conditions
196. Logging in predicates
197. Extending Stream with containsAll() and containsAny()
198. Extending Stream with removeAll() and retainAll()
199. Introducing stream comparators
200. Sorting a map
201. Filtering a map
202. Creating a custom collector via Collector.of()
203. Throwing checked exceptions from lambdas
204. Implementing distinctBy() for the Stream API
205. Writing a custom collector that takes/skips a given number of elements
206. Implementing a Function that takes five (or any other arbitrary number of) arguments
207. Implementing a Consumer that takes five (or any other arbitrary number of) arguments
208. Partially applying a Function
Summary
Concurrency – Virtual Threads and Structured ConcurrencyChevron down iconChevron up icon
Concurrency – Virtual Threads and Structured Concurrency
Problems
209. Explaining concurrency vs. parallelism
210. Introducing structured concurrency
211. Introducing virtual threads
212. Using the ExecutorService for virtual threads
213. Explaining how virtual threads work
214. Hooking virtual threads and sync code
215. Exemplifying thread context switching
216. Introducing the ExecutorService invoke all/any for virtual threads – part 1
217. Introducing the ExecutorService invoke all/any for virtual threads – part 2
218. Hooking task state
219. Combining newVirtualThreadPerTaskExecutor() and streams
220. Introducing a scope object (StructuredTaskScope)
221. Introducing ShutdownOnSuccess
222. Introducing ShutdownOnFailure
223. Combining StructuredTaskScope and streams
224. Observing and monitoring virtual threads
Summary
Concurrency ‒ Virtual Threads and Structured Concurrency: Diving DeeperChevron down iconChevron up icon
Concurrency ‒ Virtual Threads and Structured Concurrency: Diving Deeper
Problems
225. Tackling continuations
226. Tracing virtual thread states and transitions
227. Extending StructuredTaskScope
228. Assembling StructuredTaskScope
229. Assembling StructuredTaskScope instances with timeout
230. Hooking ThreadLocal and virtual threads
231. Hooking ScopedValue and virtual threads
232. Using ScopedValue and executor services
233. Chaining and rebinding scoped values
234. Using ScopedValue and StructuredTaskScope
235. Using Semaphore instead of Executor
236. Avoiding pinning via locking
237. Solving the producer-consumer problem via virtual threads
238. Solving the producer-consumer problem via virtual threads (fixed via Semaphore)
239. Solving the producer-consumer problem via virtual threads (increase/decrease consumers)
240. Implementing an HTTP web server on top of virtual threads
241. Hooking CompletableFuture and virtual threads
242. Signaling virtual threads via wait() and notify()
Summary
Garbage Collectors and Dynamic CDS ArchivesChevron down iconChevron up icon
Garbage Collectors and Dynamic CDS Archives
Problems
243. Hooking the garbage collector goal
244. Handling the garbage collector stages
245. Covering some garbage collector terminology
246. Tracing the generational GC process
247. Choosing the correct garbage collector
248. Categorizing garbage collectors
249. Introducing G1
250. Tackling G1 throughput improvements
251. Tackling G1 latency improvements
252. Tackling G1 footprint improvements
253. Introducing ZGC
254. Monitoring garbage collectors
255. Logging garbage collectors
256. Tuning garbage collectors
257. Introducing Application Class Data Sharing (AppCDS, or Java’s Startup Booster)
Summary
Socket API and Simple Web ServerChevron down iconChevron up icon
Socket API and Simple Web Server
Problems
258. Introducing socket basics
259. Introducing TCP server/client applications
260. Introducing the Java Socket API
261. Writing a blocking TCP server/client application
262. Writing a non-blocking TCP server/client application
263. Writing UDP server/client applications
264. Introducing multicasting
265. Exploring network interfaces
266. Writing a UDP multicast server/client application
267. Adding KEM to a TCP server/client application
268. Reimplementing the legacy Socket API
269. Quick overview of SWS
270. Exploring the SWS command-line tool
271. Introducing the com.sun.net.httpserver API
272. Adapting request/exchange
273. Complementing a conditional HttpHandler with another handler
274. Implementing SWS for an in-memory file system
275. Implementing SWS for a zip file system
276. Implementing SWS for a Java runtime directory
Summary
Other Books You May EnjoyChevron down iconChevron up icon
Other Books You May Enjoy
IndexChevron down iconChevron up icon
Index

Recommendations for you

Left arrow icon
Debunking C++ Myths
Debunking C++ Myths
Read more
Dec 2024226 pages
Full star icon5 (1)
eBook
eBook
S$38.99S$43.99
S$53.99
Go Recipes for Developers
Go Recipes for Developers
Read more
Dec 2024350 pages
eBook
eBook
S$38.99S$43.99
S$53.99
50 Algorithms Every Programmer Should Know
50 Algorithms Every Programmer Should Know
Read more
Sep 2023538 pages
Full star icon4.5 (68)
eBook
eBook
S$47.99S$53.99
S$67.99
S$67.99
Asynchronous Programming with C++
Asynchronous Programming with C++
Read more
Nov 2024424 pages
Full star icon5 (1)
eBook
eBook
S$40.99S$45.99
S$56.99
Modern CMake for C++
Modern CMake for C++
Read more
May 2024504 pages
Full star icon4.7 (12)
eBook
eBook
S$47.99S$53.99
S$67.99
Learn Python Programming
Learn Python Programming
Read more
Nov 2024616 pages
Full star icon5 (1)
eBook
eBook
S$38.99S$43.99
S$53.99
Learn to Code with Rust
Learn to Code with Rust
Read more
Nov 202457hrs 40mins
Video
Video
S$101.99
Modern Python Cookbook
Modern Python Cookbook
Read more
Jul 2024818 pages
Full star icon4.9 (21)
eBook
eBook
S$53.98S$59.99
S$74.99
Right arrow icon

Customer reviews

Top Reviews
Rating distribution
Full star iconFull star iconFull star iconFull star iconHalf star icon4.5
(14 Ratings)
5 star71.4%
4 star21.4%
3 star0%
2 star0%
1 star7.1%
Filter icon Filter
Top Reviews

Filter reviews by




Steven FernandesAug 05, 2024
Full star iconFull star iconFull star iconFull star iconFull star icon5
Mastering Modern Java is essential for any Java developer keen on understanding JDK 21's latest features. It provides a detailed look at modern Java capabilities, from Records and Sealed Classes to Context-Specific Deserialization Filters, each explained with practical examples to simplify applications in real-world scenarios. The book excels in elucidating advanced data handling, security enhancements, and system architecture optimization. It also covers new APIs and modern garbage collection techniques, making it a valuable resource for enhancing both knowledge and practical skills in Java. Recommended for its thoroughness and real-world applicability.
Amazon Verified reviewAmazon
Sitesh PattanaikMar 29, 2024
Full star iconFull star iconFull star iconFull star iconFull star icon5
It's quite some time that I have came across an exhaustive book on usage of java focusing on various modern and real-world problems. Here are some of the initial feedback that I have on the book and I am blown by the exhaustiveness and clear explanations.- The reference book provides clear and simple code samples of the problem described.- Covers some of very niche (advanced) things like stages of GC and the epoch cycles.- Consists of a dedicated chapter for Socket and consists of various examples of the usage of Simple Web Server (SWS) for some of the real world scenarios.- For someone coming from Object oriented styled programming paradigm, the book has well explained examples of functional programming in general.The book looks very promising to build a deeper level insight on Java Programming.
Amazon Verified reviewAmazon
MargaritaApr 01, 2024
Full star iconFull star iconFull star iconFull star iconFull star icon5
I really like this book. I am a Senior Java engineer and think It will be useful for any Java fan of any level. Book is built like problem-solving that is very actual for nowadays comparing and explanations features from the very first Java versions till new features of Java 21. Besides code examples really good, clear structure and running without any problem, there are examples also with benchmark. It is easy to check theory by your own for me this is very important.The most I liked explanations of project Loom, Virtual threads because it is a completely new theme for me but after book I'm ready to use it in a project.Also found new useful information and problem solving for strings, switch, collections, records.
Amazon Verified reviewAmazon
AbhiJul 03, 2024
Full star iconFull star iconFull star iconFull star iconFull star icon5
Just started diving into Java and this book became my coding buddy! Over 250 problems might seem scary at first, but they're like a step-by-step guide. You learn new stuff constantly without feeling overloaded. Plus, it covers all the latest Java features, which feels pretty cool. But my favorite part? It's not just about the answer, it teaches you how to approach problems strategically, thinking about clean and efficient code. Huge bonus for the explanations too, even the trickier concepts were clear. Feeling way more confident with Java thanks to this book! Definitely recommend it for anyone who wants to up their coding game.
Amazon Verified reviewAmazon
DeepaJun 05, 2024
Full star iconFull star iconFull star iconFull star iconFull star icon5
A great reference for Java Coders! Highly recommended if you are an intermediate level programmer who wants to enhance their skills
Amazon Verified reviewAmazon
  • Arrow left icon Previous
  • 1
  • 2
  • 3
  • Arrow right icon Next

People who bought this also bought

Left arrow icon
50 Algorithms Every Programmer Should Know
50 Algorithms Every Programmer Should Know
Read more
Sep 2023538 pages
Full star icon4.5 (68)
eBook
eBook
S$47.99S$53.99
S$67.99
S$67.99
Event-Driven Architecture in Golang
Event-Driven Architecture in Golang
Read more
Nov 2022384 pages
Full star icon4.9 (11)
eBook
eBook
S$47.99S$53.99
S$67.99
The Python Workshop Second Edition
The Python Workshop Second Edition
Read more
Nov 2022600 pages
Full star icon4.6 (22)
eBook
eBook
S$50.99S$56.99
S$70.99
Template Metaprogramming with C++
Template Metaprogramming with C++
Read more
Aug 2022480 pages
Full star icon4.6 (14)
eBook
eBook
S$44.99S$50.99
S$63.99
Domain-Driven Design with Golang
Domain-Driven Design with Golang
Read more
Dec 2022204 pages
Full star icon4.4 (19)
eBook
eBook
S$43.99S$48.99
S$60.99
Right arrow icon

About the author

Profile icon Anghel Leonard
Anghel Leonard
Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more
See other products by Anghel Leonard
Getfree access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook?Chevron down iconChevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website?Chevron down iconChevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook?Chevron down iconChevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support?Chevron down iconChevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks?Chevron down iconChevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook?Chevron down iconChevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.


[8]ページ先頭

©2009-2025 Movatter.jp