JDK 15 is coming. Let's have a look at the code changes!
JDK 15 is going to be released in September 2020. Amongst the changes expected in JDK 15 - Build 23, there are 3 direct code related JEP's. We'll talk
JDK 15 is going to be released in September 2020. Amongst the changes expected in JDK 15 - Build 23, there are 3 direct code related JEP's. We'll talk about this here.
We will be going through hidden classes, which allow classes to be loaded during runtime. The usage of such concept may be of interest in some projects that need some kind of class obfuscation. Maybe these are projects that need to hide functionality of some classes. Maybe we do want to run some code on the fly. Whatever the case, hidden classes offer the promise of, with the use of reflection, make use of classes which have definitions and declarations that we know before-hand. Another aspect of the innovations given in the JDK is the fact that we will finally be able to use pattern matching when using the instanceOf
keyword. This is a important feature because it will in general, allow us to reduce code. Finally we are going to have a look at text blocks and how to they work. The idea behind text blocks is to allow developers to create strings and compare them more easily, by allowing String
's to be created within a block. For these blocks there are rules and in this article we'll have a look at the ones described in the documentation.
All features are listed here:
1. The other JEP's
1.1. Proposals
JEPs proposed to target JDK 15 | review ends |
360: Sealed Classes (Preview) | 2020/05/20 |
381: Remove the Solaris and SPARC Ports | 2020/05/20 |
383: Foreign-Memory Access API (Second Incubator) | 2020/05/21 |
1.2. Targets
JEPs targeted to JDK 15, so far |
339: Edwards-Curve Digital Signature Algorithm (EdDSA) |
371: Hidden Classes |
372: Remove the Nashorn JavaScript Engine |
373: Reimplement the Legacy DatagramSocket API |
374: Disable and Deprecate Biased Locking |
375: Pattern Matching for instanceof (Second Preview) |
377: ZGC: A Scalable Low-Latency Garbage Collector |
378: Text Blocks |
379: Shenandoah: A Low-Pause-Time Garbage Collector |
384: Records (Second Preview) |
2. Environment Setup
The project we will be looking at is developed in Java 15.
In the environment setup is is importanto to highlight the fact that my environment consists mainly of a MacBook Pro and IntelliJ. However what you need for this blog is the following:
A machine with a system in it
Java 15 EAB(Early Access Build) installed.
An IDE
Maven and GIT
In this post, I am assuming that you can install Java manually and that you can configure IntelliJ to run it. In order to help you though, I'd like to share with you how I created the alias for Java 15 on a MAC:
In terms of getting the packaged JDK 15 - Build 23 version for Mac-OS, this is an example of what you could do:
Example:
curl https://download.java.net/java/early_access/jdk15/23/GPL/openjdk-15-ea+23_osx-x64_bin.tar.gz --output openjdk-15-ea+23_osx-x64_bin.tar.gz
or
wget https://download.java.net/java/early_access/jdk15/23/GPL/openjdk-15-ea+23_osx-x64_bin.tar.gz
Install jenv:
brew install jenv
Add this to your .bash_profile
export PATH="$HOME/.jenv/bin:$PATH"
eval "$(jenv init -)"
jenv add /Users/jofisaes/Downloads/jdk-15.jdk/Contents/Home
jenv global 15-ea
alias java15="jenv global 15-ea"
Note that in order to get back to previous java versions, you may have to use the same process to identify your previous version, install it and give it an alias.
All my examples are located in my GitHub repo. Make sure you have it locally if you want to see these examples in actions.
Finally, bear in mind that I was able to run these examples without the help of the compilers provided by IntellJ on design time. My version of IntellJ recognizes Java code up to version 14. Configuring properly, you can still run it on version 15. With other words, this just means that you will see underlined code (mostly in a red color) indicating that the code isn't compilable. If JDK 15 is configured correctly, you will be able to run it regardless.
This is how I have it configured. In the JDK configurations I have added Java 15 with the path of where I downloaded it and unpacked it to:
Then I added this version to the project
Note that the compiler in design time, the project compiler, is set to JDK 14 and this is why IntellJ will give you misleading warnings and error notices.
And finally this is how the overview of my desktop looks like:
3. JEP in detail
Please note that it's important to read all JEP's to understand the examples I'm presenting further on.
3.1. JEP 371: Hidden Classes
Hidden classes are one of the most interesting related to code in this release. This allows you to work with hidden classes. If you open the project you'll find a runnable class with a very simple code:
public class Hidden {
public static void main(String[] args) throws IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
final ClassOption classOption = ClassOption.NESTMATE;
final InputStream stream = Hidden.class
.getClassLoader()
.getResourceAsStream("./org/jesperancinha/jtd/Mouse.class");
assert stream != null;
final Lookup lookup = MethodHandles.lookup()
.defineHiddenClass(stream.readAllBytes(), false, classOption);
final Class<?> aClass = lookup.findClass("org.jesperancinha.jtd.Mouse");
Object newInstance = aClass.newInstance();
Method method = aClass.getMethod("getCatch", Integer.class);
Object invokeResult = method.invoke(newInstance, 10);
System.out.println(invokeResult);
System.out.println(aClass);
}
}
First we open an inputstream from a class file. This class is ./org/jesperancinha/jtd/Mouse.class. This class is being generated in this module:
.
You don't need to create this file, because it's being loaded in the resources folder of this project.
With method defineHiddenClass
, we load this class and make it available in our hidden lookup
. Next, we create an instance of this class. We see now that we are working with a loaded class definition to which we know nothing about. It has been loaded and it's available but still, only through reflection
. This is ok!. That is the intention and this is why classes are loaded this way already in some frameworks.
What we are doing is just calling via Reflection
, the getCatch
method of the Mouse
class with an Integer
parameters.
After building with the maven plugin in IntellJ, our result should be something like this:
/Users/jofisaes/Downloads/jdk-15.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA 2020.1 EAP.app/Contents/lib/idea_rt.jar=61970:/Applications/IntelliJ IDEA 2020.1 EAP.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/jofisaes/dev/src/jofisaes/java-test-drives/java-test-15/java-test-hidden/target/classes org.jesperancinha.jtd.Hidden
You got me with 10 nails!
class org.jesperancinha.jtd.Mouse
Process finished with exit code 0
3.2. JEP 375: Pattern Matching for instanceof (Second Preview)
This JEP, allows for one to keep using the instance we are testing against and seamlessly cast it to continue testing in the same line of code.
We will check the types of cats in our example:
public class InstanceOf {
public static void main(String[] args) {
final Object cat = "Bob cat";
final Object cat2 = "Lynx";
if(cat instanceof String s && s.equals("Bob cat")){
System.out.println("This is a bob cat!");
}
if(cat instanceof String s && s.length() == 7){
System.out.println("We've written the bob cat sentence with enough characters!");
}
if(cat2 instanceof String s && s.equals("Lynx")){
System.out.println("This is a Lynx");
}
if(cat2 instanceof String s && s.length() == 4){
System.out.println("Indeed, we write Lynx with 4 letters");
}
}
}
In this way, we can cast our instance and immediately check that variable using String and Object methos. Method equals
is part of Object
and length
is part of String
.
3.3. JEP 378: Text Blocks
We will be able to create string literals in a much easier way. With thie JEP, engineers will be able to seamlessly create strings to serve the purpose of their developments.
- Multiple lines:
@Test
void testLineTerminator_whenMultipleLines_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test1.txt"));
assertThat(text).isEqualTo(
"""
Cat One
Cat Two
Cat Three
""");
}
In this case, everything between the triple quotes will be considered a string. New lines are considered new lines and there is no need to use '\n' for that.
- Suppressing the last new line.
@Test
void testLineTerminator_whenMultipleLinesNoLastNewLine_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test2.txt"));
assertThat(text).isEqualTo(
"""
Cat One
Cat Two
Cat Three""");
}
The triple quotes in the end which are attached to the last character of the string literal, means that no newline is added.
- Indentation
@Test
void testLineTerminator_whenIdentation_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test3.txt"));
assertThat(text).isEqualTo(
"""
Cat One
Cat Two
Cat Three
""");
}
You probably have just noticed a pattern. The last triple quotes are limiting where the identation should be. Where they start is where everything else gets an extra space character to compensate that identation. Identation can only be limited in other ways though. Let's look at one of them now.
- Indentation limited by text itself
@Test
void testLineTerminator_whenIdentation2_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test3.txt"));
assertThat(text).isEqualTo(
"""
Cat One
Cat Two
Cat Three
""");
}
We will still have success in the unit test for this case. The reason being is that in this example, the word Cat
comes first from the left before the triple quotes. It works just as well and all the other text further to the right will get filling spaces.
- Indentation to the left
@Test
void testLineTerminator_whenIdentationFromEnd_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test4.txt"));
assertThat(text).isEqualTo(
"""
Cat One
Cat Two
Cat Three
""");
}
In the same way as explained in the two examples above, we get an identation of three spaces from left to right. This happens because the triple quotes are determining the required indentation.
- Indentation Mix
@Test
void testLineTerminator_whenIdentationMixed_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test5.txt"));
assertThat(text).isEqualTo(
"""
Cat One
Cat Two
Cat Three
Cat Four""");
}
This is just another test to check multiple indentations.
- Escaping quotes
@Test
void testLineTerminator_whenQuotes_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test6.txt"));
assertThat(text).isEqualTo(
"""
"Cat" One
Cat "Two"
"Cat" Three
"Cat Four\"""");
}
The triple quotes are allowed freely accepted within the text block limiters. The only way they are not accepted is we need to place three in a row. In that case. For every triple quotes we add, at least one of them must be escaped. Escaping can always be done with \
.
- Escaping quotes inside Text Block
@Test
void testLineTerminator_when3Quotes_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test7.txt"));
assertThat(text).isEqualTo(
"""
Cat One
\"""Cat Two\"""
Cat Three
""");
}
This is just another example which showcases how to escape quotes when coming against a triple quote row.
- Excape special characters
@Test
void testLineTerminator_whenEscapeCharacters_thenOk() throws IOException {
assertThat("Cat One\nCat Four\n" +
"Cat Two\u0009Cat Five\n" +
"Cat Three" + (char) 0x0D + " Cat Six\n" +
"Cat Seven\'Cat Eight\\" +
"\n"
).isEqualTo(
"""
Cat One\nCat Four
Cat Two\tCat Five
Cat Three\r Cat Six
Cat Seven\'Cat Eight\\
""");
}
Most of my examples don't any hardcoded variable. In this case It was important to do so. This is because special characters that need to be escaped like \r, \t aren't really easy to save in an example text file. We can escape many characters this way like : \r, \n, \t, \\, \', \"
- Terminating with a space
@Test
void testLineTerminator_whenSpaceTermination_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test9.txt"));
assertThat(text).isEqualTo(
"""
Cat One \
Cat Two \
Cat Three\
""");
}
The idea of using \
is to limit a sentence in each line. With \
, we are not creating new lines. We are only saying that the following line should join here to build the whole string.
- Using Text Blocs on methods
@Test
void testLineTerminator_whenStringReplacement_thenOk() throws IOException {
final String text = IOUtils.toString(getClass().getResourceAsStream("/test11.txt"));
assertThat(text).isEqualTo(String.format(
"""
Cat One's name is %s
Cat Two's name is %s
Cat Three's name is %s
""", "Zuu", "Brinca", "Tommy"));
}
In this example, our cats get names as parameters. Other methods may be added for this Java release already.
4. Conclusion
I am very excited to get to test the official coming release of Java 15. We didn't went through the other features, like for the example the sealed classes, because either they are not directly related to code, or they are just not available in the EAB.
Let's see what happens in September this year!
If you have reached this far from this post, congratulation because that means that you are interested in knowing more. In that sense perhaps you are interested in knowing more about records and how are they different from data classes in Kotlin. For that, I made a whole video for, and you can find it over here: