Chapter 13. Expanding Java

The Java language is now over 25 years old. It has grown and changed a great deal in that time. Some of the growth has been quiet and incremental. Other changes can feel abrupt. Even the process for how changes are introduced into the language has evolved.

In this chapter we’ll be looking at where those changes start and how they end up in an actual release of Java. We’ll recap the release process we discussed in “A Java Road Map” and take a peek at some of the topics being discussed for future releases. We’ll also return to the present and go over updating your existing code with a new feature—and when that makes sense. Not every new feature in Java will be of interest to every Java developer. On the flip side, almost every developer will find something of interest somewhere in the vast catalog of capabilities present directly in Java or in its many, many third-party libraries.

Java Releases

As we write this fifth edition, Java 14 is available as a preview release. We’be been working with the open-source version of the developer kit, the OpenJDK. You can see recent and upcoming releases at the JDK Project page. Again, Oracle maintains the official JDK which may be appropriate for large, corporate customers looking for paid support. You can follow the progress of the official releases at Oracle’s landing page: Java Standard Edition overview. If you’re curious about exactly what features and changes come with each version, check out Oracle’s JDK Release Notes page.

After Java 9, Oracle moved to a six-month release cycle for smaller, feature-based releases of the language. That rapid cadence means you’ll see regular updates to Java. You might look forward to each new release and working its new features into your code right away. Or you might choose to stick with one of the designated long-term support releases like Java 8 or Java 11. As we noted before, not all changes to Java will be useful to you. But we want to make sure you know how to evaluate new features as well as how to watch for what’s coming next.

JCP and JSRs

We’ll start the explanation of what features get added to Java by adding a few more acronyms. The Java Community Process (JCP) Program is designed to invite public participation in shaping Java’s road map. Through that process, Java Specification Requests (JSRs) are created and refined. A JSR is just a document outlining a particular, scoped idea for implementation and development by some team of programmers. For example, JSR 376 describes the Java Platform Module System for better handling of the parts necessary to build and deploy Java applications. (You can also browse all JSRs if you are curious about what is out there—including ideas that were specified but ultimately withdrawn or rejected.) Any JSR of sufficient interest might earn a spot as a preview feature in an upcoming version of Java. If an idea is not quite ready for a full specification, it might pop up as a JDK Enhancement Proposal (JEP). Not all proposals will grow out of that stage, but you can see there is a fairly robust environment for trying out new ideas and moving any winners forward for eventual inclusion.

If you want to see which features are going into the next release, check out Oracle’s JDK build site. Here you will see current JDKs as well as those available for early access. The early access builds include release notes on what’s around the bend. But as you look at those early access releases, take the first disclaimer from the site to heart: “Early-access (EA) functionality might never make it into a general-availability (GA) release.” Indeed, the releases of Java themselves are wrapped in “umbrella” JSRs such as JSR 337 for Java 8. These umbrella JSRs are a bit dry, but they are an authoritative description of what’s coming in the given release.

Lambda Expressions

JSR 337, for example, presaged some big changes in Java. The previous edition of this book left off with Java 7. The try-with-resources (among many other features) we used in Chapter 11 was hot off the presses. Developers at the time were already looking for other features that had been discussed but ultimately not included. One of the most anticipated additions was the idea of lambda expressions (JSR 335). Lambda expressions allow you to treat a bit of code as a first-class object. (In relation to lambda expressions, these bits of code are called functions.) If you don’t need to use that function anywhere else, this can lead to more concise and readable programs that are easier to understand, once you are familiar with the syntax. Like anonymous classes, lambda functions can access local variables that are in scope where they are written. They work really well with function-oriented APIs like the java.util.stream package, also noted in JSR 335. These impressive additions did indeed make it into Java 8.

Lambda functions1 allow you to approach problems with a more functional outlook. Functional programming is a more declarative style of programming. You focus on writing functions—methods with some specific restrictions—rather than on manipulating objects. We won’t go into the details of functional programming, but it is a powerful paradigm and one worth exploring as you continue your coding journey. We include some good books for functional programming homework in “Expanding Java beyond the core” at the end of the chapter.

Retrofitting Your Code

Lambda expressions seem pretty interesting. What if we want to use them in our own code? That’s a great question and one that will apply to any new feature. As we’ve noted, the release schedule for Java means that you will always be facing new versions. Let’s tackle these lambda expressions with an eye towards evaluating and potentially integrating new Java features.

Feature Research

With any new feature, you’ll first need to understand what the feature itself encompasses. That might be as simple as a small syntax change or as complex as a new way to build Java binaries. Our lambda expressions fall somewhere in between. Let’s look at a very simple expression and then use it in a bit of code.

So where would we begin with lambda expressions? If you have some programming experience from a functional language like LISP, perhaps you already know what lambdas are and where they might be used. If you don’t know much about the term, you could search online. If the feature has been available for some time (like lambda expressions in Java as we write this edition of the book at the dawn of 2020) you will likely turn up some good tutorials. If it’s a very new feature or your initial searches are not turning up useful results, you can go back to the JSR. For lambda expressions, again that’s JSR 335. Section 2 of a JSR is the Request section and usually contains some helpful hints. Here’s the opening paragraph from section 2.7 providing a short description of the feature:

We propose extending the Java Language to support compact lambda expressions (otherwise known as closures or anonymous methods.) Additionally, we will extend the language to support a conversion known as “SAM conversion” to allow lambda expressions to be used where a single-abstract-method interface or class is expected, enabling forward compatibility of existing libraries.

JSR 335 Section 2.7

There are several keywords in just those few sentences that could help you search for more background material. What are “closures”? What is “SAM conversion” The last sentence even gives you clue about where lambda expressions would be used: wherever a particular type of interface or class is allowed. That paragraph is certainly not enough to fully grasp lambda expressions on their own, but again, it has some hints about the right topics to research.

The rest of the JSR should give you more documentation you can read. It may include content that is immediately useful, but more often you’ll find links to supporting material, design documents by members of the team working on the JSR, or even earlier drafts of the request itself so you can see its evolution. You should also be able to find more concrete information in the Java documentation for the version containing your feature (Java 8 in our lambda example). Even the early access builds will have some official documentation available.

You should feel free to do some of that research on lambda expressions right now. Read some of the supporting documents from the JSR. Check out the Oracle tutorials on lambdas in Java 8. Try searching online at sites like Stack Overflow. You’ll want to become comfortable finding examples you understand from sources you trust. New releases of Java are now rolling out every six months. It will pay to know how you can best stay up-to-date!

Basic Lambda Expressions

While we hope you do some of that research homework, we do want to show you some examples of how compact and powerful lambda expressions can be. The basic syntax of a lambda expression is simple:

(params) -> expression or block

The “params” are zero or more named (and possibly typed) parameters that are passed to the expression on the right side of the new operator. The expression (or block of statements inside the usual pair of curly braces) can return a value or execute some code. For example, here’s a common “increment” lambda expression for a single input parameter:

(n) -> n + 1

It’s important to note that this lambda expression does not alter the value of the parameter n. It just performs a calculation. You could think of this particular example as a “next” operation for integers. If you had some other context that used a next() method to do some work, you could supply this lambda expression. That becomes more powerful when you want to use that same context to work with other types of objects like strings or dates. What is the “next” date? Is it the next day? the next year? With a lambda expression, you can provide a tailored version of “next” right where you need it.

You can pass more than one parameter to your function. Or you can pass none. In the wild, you will see all these variations including a popular shortcut: if you have exactly one parameter, you do not need to use parentheses on the left side of the expression. The following expressions are all valid:

// 1 parameter
(n) -> n + 1

n -> n + 1

n -> System.out.println("Working on " + n)

// No parameters
() -> System.out.println("Done working")

// Multiple parameters
(a, b, c) -> (a + b + c) / 3

Consider sorting lists. If we have a list of numbers (we’ll use the Integer wrapper class in this example), sorting is straightforward:

jshell> ArrayList<Integer> numbers = new ArrayList<>();
numbers ==> []

jshell> numbers.add(19)
$5 ==> true

jshell> numbers.add(6)
$6 ==> true

jshell> numbers.add(12)
$7 ==> true

jshell> numbers.add(7)
$8 ==> true

jshell> numbers
numbers ==> [19, 6, 12, 7]

jshell> Collections.sort(numbers)

jshell> numbers
numbers ==> [6, 7, 12, 19]

But what if we wanted the numbers in reverse order? Previously, we would have to write a special class that implements the Comparator interface or provide an anonymous inner class:

jshell> Collections.sort(numbers, new Comparator<Integer>() {
   ...>   public int compare(Integer a, Integer b) {
   ...>     return b.compareTo(a);
   ...>   }
   ...> })

jshell> numbers
numbers ==> [19, 12, 7, 6]

Fair enough, the anonymous inner class worked, but it was a little bulky. We could use a lambda expression instead to write a more compact version:

jshell> Collections.sort(numbers) // put the array back in ascending order

jshell> numbers
numbers ==> [6, 7, 12, 19]

jshell> Collections.sort(numbers, (a, b) -> b.compareTo(a))

jshell> numbers
numbers ==> [19, 12, 7, 6]

Wow! That is much cleaner. You have to understand what the Collections.sort() method is expecting as arguments and know that the Comparator interface has only one abstract method (i.e. it is a single-abstract-method—or SAM—interface; remember the JSR description?). But when you do have the right environment, a lambda expression can be quite efficient.

We could take these techniques and rewrite several of our “list generating” examples from throughout the book. Let’s take the snippet from “File operations” using java.io.File objects. We could sort and list the names using the actual File objects with the help of the Arrays.asList() method (to get an Iterable) and then use a lambda expression with the forEach() method like so:

File tmpDir = new File("/tmp" );
File [] files = tmpDir.listFiles();

Arrays.sort(files, (a,b) -> a.getName().compareTo(b.getName()))
Arrays.asList(files).forEach(n -> System.out.println(n.getName()))

We were able to get file names before without lambdas, but in many cases we can write more concise code with them. You have to get comfortable with the lambda syntax, of course, but that’s what all this practice is for!

Method References

That process of sorting complex objects using one of their attributes is so common, in fact, there’s a Java helper method that creates the right function already in the API. The Comparator.comparing() static method can help write something similar to our lambda expression that uses compareTo() in the previous section. It takes advantage of method references, a simplified type of lambda expression that uses existing methods from other classes.

There are many details and use cases for method references that we won’t go into here, but the basic syntax and usage is straightforward. You place the :: separator between a class name and a method name. The Comparator.comparing() method is expecting a reference to a method that can be used on the objects being sorted (i.e. you should still call appropriate methods). When sorting our File objects, we can use any of the getter methods that return sort-friendly information like the name or size of the file.

Arrays.sort(files, Comparator.comparing(File::getName));

That is pretty clean! And we can see exactly what is intended: we are going to sort a bunch of files by comparing their names. Which, of course, is exactly what we did with lambdas in the previous section. Remember, it’s not that using method references is better—they can always be replaced by a lambda expression—it’s that many times method references can provide more readable code once you get used to the new syntax (just like with lambda expressions themselves).2

Eventful Lambdas

We have seen a few other code examples that have similarly constrained environments. Think back to the many event handlers in Chapter 10. Several listeners were exactly the single-abstract-method variety like the ActionListener interface used by JButton or JMenuItem. Where appropriate, we can use a lambda expression to simplify our event handling code. We often have simple, temporary handlers to check the basic ability to click a button like so:

    JButton okButton = new JButton("OK");
    okButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
            System.out.println("OK pressed!");
        }
    });

We can now use a lambda expression to shorten that up quite a bit. It makes writing such quick proof-of-concept code for several buttons much easier:

    JButton okButton = new JButton("OK");
    okButton.addActionListener(ae -> System.out.println("OK pressed"));
    JButton noButton = new JButton("Cancel");
    noButton.addActionListener(ae -> System.out.println("Cancel pressed"));

Great! Lambda expressions can provide a nice way to tackle situations where a little dynamic code is needed. Not every event handler will lend itself to this type of conversion, or course. But many will and the more compact notation can help make your code more readable, too.

Replacing Runnable

Another popular interace that fits this model is the Runnable interface introduced in Chapter 9 and used again in Chapter 10. We saw examples of using both inner classes and anonymous inner classes to create new Thread objects. The SwingUtilities.invokeLater() method also needed a Runnable instance as an argument. We can use a lambda expression in these cases as well. Recall the ProgressPretender example from “SwingUtilities and component updates”. We were already inside the run() method of a class that implements the Runnable interface when we had to create a second, anonymous instance of Runnable to update a label:

    public void run() {
        while (progress <= 100) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    label.setText(progress + "%");
                }
            });
        // ...

But now we can use a lambda expression to keep the focus on the real work the thread is doing:

    public void run() {
        while (progress <= 100) {
            SwingUtilities.invokeLater(() -> label.setText(progress + "%"));
        // ...

Again, a much more compact—and hopefully readable—bit of code. It is not a required change, nor does it improve performace of the application, but if you (and any members of your team in a work setting) understand lambda expressions, this code can increase maintainability and leave you more time to work on other problems.

Expanding Java beyond the core

It’s important to point out that many parts of Java make use of JSRs beyond core language features. JSR 369, for example, covers the Java Servlet 4.0 specification. You might recall from “Servlets” that we needed the separate servlet-api.jar file to compile and run the servlet examples. Looking over the description for JSR 369, we see that the 4.0 spec is designed to support features found in HTTP/2. If you dig into those features, one of the most anticipated addtions is support for server push—the ability for the server to speed up delivery of complex pages by “pushing” some files or resources ahead of their actual use.

Under the HTTP/1.1 protocol, an HTML page would be delivered to your browser when you visit a site. That page would, in turn, tell the browser to request other resources such as JavaScript files, style sheets, images, etc. Each of those resources would require a separate request. Caches speed up some of this process, but the first time you visit a new site, nothing is in the cache so the load time can be quite signifcant. HTTP/2 allows the server to send resources in advance—making efficient use of an existing connection. This optimization speeds up delivery of a page even if it contains things that are not, or cannot be, cached.

Now, transitioning to HTTP/2 is itself quite a Big Deal and not every site will be using it nor will every browser support it or support all of the options. We can’t cover it here, but if web-related work is part of your daily life, it might be worth some online research. Either way, it’s good to remember that you can watch the JCP site to see what’s coming in Java with regards to the language itself and its wider ecosystem.

Final wrap up and next steps

We have only scratched the surface of lambda expressions and their related parts of Java 8 including method references and the Streams API. Sadly, like so many other fun topics we’ve touched on in this book, we must leave further exploration to you. Happily, Java 8 has been out for many, many years3 now and online resources for these features abound. You can also get some great details on lambdas in particular and functional programming in Java more generally in Java 8 Lambdas: Pragmatic Functional Programming by Richard Warburton, O’Reilly, 2014. In the servlet world, version 4 of the specification is newer, but there are still some great resources online covering both this spec and HTTP/2.

But whew! You made it! Saying we covered a lot of ground is quite an understatement. Hopefully you have a good basis to use going forward learning more details and advanced techniques. Pick an area that interests you and go a little deeper. If you’re still curious about Java in general, try connecting parts of this book. For example, you could write a servlet to answer requests similar to those made by the DateAtHost client from “The DateAtHost Client”. You could try using regular expressions to parse our apple toss game protocol. Or you could build a more sophisticated protocol altogether and pass binary blocks over the network rather than simple strings. For practice writing more complex programs, you could rewrite some of the inner and anonymous classes in the game to be separate, standalone classes or take advantage of lambda expressions.

If you want to explore other Java libraries and packages while sticking with some of the examples you have already worked on, you could dig into the Java2D API and make nicer looking apples and trees. You could research the JSON format and try rewriting the ShowParameters and ShowSession servlets to return a block of valid JSON rather than an HTML page. You could try out some of the other collection objects like TreeMap or Stack.

And if you’re ready to branch out farther, you could see how Java works off the desktop by trying some Android development. Or look at very large networked environments and the Jakarta Enterprise Edition from the Eclipse Foundation. Maybe big data is on your radar? The Apache Foundation has several projects such as Hadoop or Spark. Java does have its detractors, but it remains a vibrant, vital part of the professional developer world.

With all those options in front of you, we are ready to wrap up the main part of our book. The Glossary contains a quick reference of many useful terms and topics we’ve covered. And Appendix A goes over installing the IntelliJ IDEA editor as well as getting the code examples imported and running. We hope that you’ve enjoyed Learning Java. This, the fifth edition of Learning Java, is really the seventh edition of the series that began over two decades years ago with Exploring Java. It has been a long and amazing trip watching Java develop in that time, and we thank those of you who have come along with us over the years. As always, we welcome your feedback to help us keep making this book better in the future. Ready for another decade of Java? We are!

1 James Elliott, a reviewer for this book and a fellow O’Reilly author provides a bit of historical context: “The reason they are called ‘lambda’ functions is that they were invented as part of the lambda calculus, which was introduced by mathematician Alonzo Church in the 1930s to produce rigorous mathematical definitions of how computation works. They became an actual programming language construct almost accidentally in 1958 when MIT students realized it would be pretty easy to implement a running version of the Lisp language that professor John McCarthy was using as a practical mathematical notation for analyzing computer programs. Although Lisp is the second-oldest high level language that is still in use (Fortran is one year older), its extreme expressivity allowed it to pioneer concepts that took a long time to spread into mainstream use, and Java was the language that helped many of them do so, notably garbage collection and dynamic typing.”

2 See the answer to this question provided by Brian Goetz on Stack Overflow.

3 Interestingly, Java 8 remains one of the most deployed versions of Java according to a variety of industry surveys as of 2019.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.198.43