Wednesday, January 17, 2018

What's New in Effective Java's Third Edition?

Ever since hearing about the pending publication of the Third Edition of Effective Java, I've wondered what would be new in it. I assumed that features introduced to Java since Java 6 would be covered and that is indeed the case. However, there are some other changes as well to this third edition of the Java developer classic. In this post, I provide a high-level overview of the topics that are added, changed significantly, or removed in this third edition.

Before listing what I've observed that appears to be new in Effective Java, Third Edition, I need to make the disclaimer statement that I'm likely to miss several changes throughout this book with 12 chapters encompassing 90 items covering well over 350 pages. This post is not intended to provide detailed coverage of the changes in the third edition, but rather is intended as a high-level sampling of the changes and readers are encouraged to borrow or purchase a copy of this Third Edition of Effective Java to access the low-level details.

As expected, there is significant new content in Effective Java, Third Edition related to new features of Java 7, Java 8, and even Java 9.

Java 7

An obvious new item motivated by Java 7 is Item 9 ("Prefer try-with-resources to try-finally") because try-with-resources was introduced with Java 7. Item 32 ("Combine generics and varargs judiciously") is new to the third edition and discusses Java 7-introduced @SafeVarargs annotation (which received some enhancements with Java 9).

Item 8 ("Avoid finalizers and cleaners") has been updated to discuss how to use Java 7-introduced AutoCloseable interface to replace finalizers and cleaners in some of their most common usages. Item 49 ("Check parameters for validity") has been updated to reference Objects.requireNonNull methods introduced with Java 7.

Item 80 ("Prefer executors, tasks, and streams to threads") has "streams" added to its title since the second edition of Effective Java and includes discussion regarding the addition of Fork/Join to the Executor framework in Java 7. Item 59 ("Know and Use the Libraries") discusses the ThreadLocalRandom that was introduced in Java 7.

Item 56 ("Write doc comments for all exposed API elements") discusses the -Xdoclint switch added to javadoc's command-line with JDK 7.

Java 8

Item 21 ("Design interfaces for posterity") covers best practices related to the use of default methods in Java interfaces. The entire Chapter 7 ("Lambdas and Streams") is, as its title describes, related to lambdas and streams introduced with Java 8 and consists of seven items (Item 42 through Item 48) on these functional programming concepts. Item 55 ("Return optionals judiciously") discusses proper use of Java 8-introduced Optional.

Item 1 ("Consider static factory methods instead of constructors") is not a new item in the third edition, but it now discusses static methods in interfaces as supported in Java 8 and enhanced in Java 9. Item 19 ("Design and document for inheritance or else prohibit it") is also not new, but now mentions the Javadoc @implSpec tag that was "added in Java 8 and used heavily in Java 9." Not surprisingly, Item 56 ("Write doc comments for all exposed API elements") also discusses @implSpec use.

Item 50 ("Make defensive copies when needed") does not focus much on it (dates and times are not the focus of that item), but does reference employing Instant instead of Date as of Java 8.

Java 9

The third edition of Effective Java provides less guidance than I anticipated related to modularity (Java Platform Module System), which is arguably the first thing many of us associate with Java 9. Item 15 ("Minimize the accessibility of classes and members") discusses the "two additional, implicit access levels introduced as part of the module system."

Item 8 ("Avoid finalizers and cleaners") was titled simply "Avoid finalizers" in the second edition. The addition of "and cleaners" to this item's title reflects that Java 9 deprecated the finalizer (for reasons I'm all too familiar with) and replaced it with the Cleaner class.

Item 56 ("Write doc comments for all exposed API elements") discusses use of Java 9-introduced Javadoc tag {@index}. Item 59 ("Know and Use the Libraries") discusses the method transferTo(OutputStream) that was added to InputStream with Java 9 in its discussion of why it's important to know what's available in standard libraries.

Item 6 ("Avoid creating unnecessary objects") mentions the deprecation in Java 9 of the Boolean constructor that accepts a single String parameter as an illustration of a point being made in that item. Incidentally, the only other Boolean constructor [Boolean(boolean)] was also deprecated in Java 9.

Effective Java, Third Edition addresses refinements made in Java 9 to static methods in interfaces (Item 1) and to Optional (Item 55). Item 19 also references Java 9's heavy use of @implSpec. Each of these of these three items were highlighted in the "Java 8" section earlier in this post.

Version-independent New General Java Items

There are some new items in Effective Java, Third Edition that lack an obvious connection to a newer version of Java than that covered in the second edition. These include Item 5 ("Prefer dependency injection to hardwiring resources"), Item 25 ("Limit source files to a single top-level class"), and Item 85 ("Prefer alternatives to Java serialization"). I have written a bit more about Item 85 ("Prefer alternatives to Java serialization") in my blog post "Using Google's Protocol Buffers with Java."

A Removed Item and the Appendix

One item from the second edition of Effective Java appears to have been entirely removed. The useful "Appendix" of the third edition is titled "Items Corresponding to the Second Edition" and it indicates that the single-page Item 73 ("Avoid thread groups") from the second edition has been "retired" in the third edition. This Appendix is also structured such that it's easy to identify that the second edition's Item 21 ("Use function objects to represent strategies") is replaced in third edition by Item 42 ("Prefer lambdas to anonymous classes"). Incidentally, there seems to be very few typos in this book in any of its editions, but one typo that does stand out in the third edition is for the row in the Appendix that correlates Item 69 from the second edition with Item 81 of the third edition.

Minor Text Updates

Several items in the third edition of Effective Java have had minor text updates, some of which have significant meaning in the change. These are the most difficult to call out, but I provide one example here. In the second edition, Bloch wrote in parenthetical passing that StringBuffer is "largely obsolete" compared to StringBuilder, but in the third edition this is more strongly worded to state that StringBuffer is the "obsolete predecessor" of StringBuilder. I agree wholeheartedly with that change in text.

Introduction

Eleven chapters in the Effective Java, Third Edition encompass the 90 items constituting "Best Practices for the Java Platform." However, Chapter 1 ("Introduction") is valuable because it associates "key features" from Java 7, Java 8, and Java 9 with the item or items that discuss those key features and the release of Java which introduced those key features. I wish I had paid attention to it earlier, but did not see this handy table on page 1 until after I was mostly finished composing this post. That table would have saved me a lot of time in identifying the items that cover Java 7, Java 8, and Java 9 new features!

The "Introduction" is also worth reading because it lays out the "few fundamental principles" from which "most of the rules in this book derive." I like that Bloch explicitly states in the Introduction, "This book is not for beginners: it assumes that you are already comfortable with Java." There are countless forums and threads online in which people ask for a good book for those new to Java. While I have highly recommended the various editions of Effective Java for intermediate and advanced Java developers, I've always felt that beginning Java developers are better off with a book written for learning Java and then should come to Effective Java when they know core concepts and want to know how to apply those concepts as clearly and simply as possible.

Conclusion

This post has highlighted some of the most significant additions and changes to Effective Java in the Third Edition. However, I only mentioned some of the quick references to Java 7, Java 8, and Java 9 and undoubtedly missed some new and changed text in my summary. The references to a few of the minor changes to items to reflect newer versions of Java have been intended to illustrate how new Java features are woven into multiple items that at first glance don't seem necessarily related to a newer version of Java.

Effective Java is the only book I've ever purchased three copies of, but I have now purchased one copy of each edition over the years and have not been sorry for doing so. The third edition of Effective Java not only covers new features of Java 7, Java 8, and Java 9, but also adds items and updates pre-existing items to reflect Josh Bloch's latest thinking on best practices using the Java programming language.

Tuesday, January 16, 2018

Using Google's Protocol Buffers with Java

Effective Java, Third Edition was recently released and I have been interested in identifying the updates to this class Java development book whose last edition only covered through Java 6. There are obviously completely new items in this edition that are closely related to Java 7, Java 8, and Java 9 such as Items 42 through 48 in Chapter 7 ("Lambdas and Streams"), Item 9 ("Prefer try-with-resources to try-finally"), and Item 55 ("Return optionals judiciously"). I was (very slightly) surprised to realize that the third edition of Effective Java had a new item not specifically driven by the new versions of Java, but that was instead was driven by developments in the software development world independent of the versions of Java. That item, Item 85 ("Prefer alternatives to Java Serialization") is what motivated me to write this introductory post on using Google's Protocol Buffers with Java.

In Item 85 of Effective Java, Third Edition, Josh Bloch emphasizes in bold text the following two assertions related to Java serialization:

  1. "The best way to avoid serialization exploits is to never deserialize anything."
  2. "There is no reason to use Java serialization in any new system you write."

After outlining the dangers of Java deserialization and making these bold statements, Bloch recommends that Java developers employ what he calls (to avoid confusion associated with the term "serialization" when discussing Java) "cross-platform structured-data representations." Bloch states that the leading offerings in this category are JSON (JavaScript Object Notation) and Protocol Buffers (protobuf). I found this mention of Protocol Buffers to be interesting because I've been reading about and playing with Protocol Buffers a bit lately. The use of JSON (even with Java) is exhaustively covered online. I feel like awareness of Protocol Buffers may be less among Java developers than awareness of JSON and so feel like a post on using Protocol Buffers with Java is warranted.

Google's Protocol Buffers is described on its project page as "a language-neutral, platform-neutral extensible mechanism for serializing structured data." That page adds, "think XML, but smaller, faster, and simpler." Although one of the advantages of Protocol Buffers is that they support representing data in a way that can be used by multiple programming languages, the focus of this post is exclusively on using Protocol Buffers with Java.

There are several useful online resources related to Protocol Buffers including the main project page, the GitHub protobuf project page, the proto3 Language Guide (proto2 Language Guide is also available), the Protocol Buffer Basics: Java tutorial, the Java Generated Code Guide, the Java API (Javadoc) Documentation, the Protocol Buffers release page, and the Maven Repository page. The examples in this post are based on Protocol Buffers 3.5.1.

The Protocol Buffer Basics: Java tutorial outlines the process for using Protocol Buffers with Java. It covers a lot more possibilities and things to consider when using Java than I will cover here. The first step is to define the language-independent Protocol Buffers format. This a done in a text file with the .proto extension. For my example, I've described my protocol format in the file album.proto which is shown in the next code listing.

album.proto

syntax = "proto3";

option java_outer_classname = "AlbumProtos";
option java_package = "dustin.examples.protobuf";

message Album
{
  string title = 1;
  repeated string artist = 2;
  int32 release_year = 3;
  repeated string song_title = 4;
}

Although the above definition of a protocol format is simple, there's a lot covered. The first line explicitly states that I'm using proto3 instead of the assumed default proto2 that is currently used when this is not explicitly specified. The two lines beginning with option are only of interest when using this protocol format to generate Java code and they indicate the name of the outermost class and the package of that outermost class that will be generated for use by Java applications to work with this protocol format.

The "message" keyword indicates that this structure, named "Album" here, is what needs to be represented. There are four fields in this construct with three of them being string format and one being an integer (int32). Two of the four fields can exist more than once in a given message because they are annotated with the repeated reserved word. Note that I created this definition without considering Java except for the two options that specify details of generation of Java classes from this format specification.

The album.proto file shown above now needs to be "compiled" into the Java source class file (AlbumProtos.java in the dustin.examples.protobuf package) that will allow for writing and reading Protocol Buffers's binary format that corresponds to the defined protocol format. This generation of Java source code file is accomplished using the protoc compiler that is included in the appropriate operating system-based archive file. In my case, because I'm running this example in Windows 10, I downloaded and unzipped protoc-3.5.1-win32.zip to get access to this protoc tool. The next image depicts my running protoc against album.proto with the command protoc --proto_path=src --java_out=dist\generated album.proto.

For running the above, I had my album.proto file in the src directory pointed to by --proto_path and I had a created (but empty) directory called build\generated for the generated Java source code to be placed in as specified by --java_out flag.

The generated class's Java source code file AlbumProtos.java in the specified package has more than 1000 lines and I won't list that generated class source code here, but it's available on GitHub. Among the several interesting things to note about this generated code is the lack of import statements (fully qualified package names used instead for all class references). More details regarding the Java source code generated by protoc is available in the Java Generated Code guide. It's important to note that this generated class AlbumProtos has still not been influenced by any of my own Java application code and is solely generated from the album.proto text file shown earlier in the post.

With the generated Java source code available for AlbumProtos, I now add the directory in which this class was generated to my IDE's source path because I'm treating it as a source code file now. I could have alternatively compiled it into a .class or .jar to use as a library. With this generated Java source code file now in my source path, I can build it alongside my own code.

Before going further in this example, we need a simple Java class to represent with Protocol Buffers. For this, I'll use the class Album that is defined in the next code listing (also available on GitHub).

Album.java

package dustin.examples.protobuf;

import java.util.ArrayList;
import java.util.List;

/**
 * Music album.
 */
public class Album
{
   private final String title;

   private final List<String> artists;

   private final int releaseYear;

   private final List<String> songsTitles;

   private Album(final String newTitle, final List<String> newArtists,
                 final int newYear, final List<String> newSongsTitles)
   {
      title = newTitle;
      artists = newArtists;
      releaseYear = newYear;
      songsTitles = newSongsTitles;
   }

   public String getTitle()
   {
      return title;
   }

   public List<String> getArtists()
   {
      return artists;
   }

   public int getReleaseYear()
   {
      return releaseYear;
   }

   public List<String> getSongsTitles()
   {
      return songsTitles;
   }

   @Override
   public String toString()
   {
      return "'" + title + "' (" + releaseYear + ") by " + artists + " features songs " + songsTitles;
   }

   /**
    * Builder class for instantiating an instance of
    * enclosing Album class.
    */
   public static class Builder
   {
      private String title;
      private ArrayList<String> artists = new ArrayList<>();
      private int releaseYear;
      private ArrayList<String> songsTitles = new ArrayList<>();

      public Builder(final String newTitle, final int newReleaseYear)
      {
         title = newTitle;
         releaseYear = newReleaseYear;
      }

      public Builder songTitle(final String newSongTitle)
      {
         songsTitles.add(newSongTitle);
         return this;
      }

      public Builder songsTitles(final List<String> newSongsTitles)
      {
         songsTitles.addAll(newSongsTitles);
         return this;
      }

      public Builder artist(final String newArtist)
      {
         artists.add(newArtist);
         return this;
      }

      public Builder artists(final List<String> newArtists)
      {
         artists.addAll(newArtists);
         return this;
      }

      public Album build()
      {
         return new Album(title, artists, releaseYear, songsTitles);
      }
   }
}

With a Java "data" class defined (Album) and with a Protocol Buffers-generated Java class available for representing this album (AlbumProtos.java), I'm ready to write Java application code to "serialize" the album information without using Java serialization. This application (demonstration) code resides in the AlbumDemo class which is available on GitHub and from which I'll highlight relevant portions of in this post.

We need to generate a sample instance of Album to use in this example and this is accomplished with the next hard-coded listing.

Generating Sample Instance of Album

/**
 * Generates instance of Album to be used in demonstration.
 *
 * @return Instance of Album to be used in demonstration.
 */
public Album generateAlbum()
{
   return new Album.Builder("Songs from the Big Chair", 1985)
      .artist("Tears For Fears")
      .songTitle("Shout")
      .songTitle("The Working Hour")
      .songTitle("Everybody Wants to Rule the World")
      .songTitle("Mothers Talk")
      .songTitle("I Believe")
      .songTitle("Broken")
      .songTitle("Head Over Heels")
      .songTitle("Listen")
      .build();
}

The Protocol Buffers generated class AlbumProtos includes a nested AlbumProtos.Album class that I'll be using to store the contents of my Album instance in binary form. The next code listing demonstrates how this is done.

Instantiating AlbumProtos.Album from Album

final Album album = instance.generateAlbum();
final AlbumProtos.Album albumMessage
   = AlbumProtos.Album.newBuilder()
      .setTitle(album.getTitle())
      .addAllArtist(album.getArtists())
      .setReleaseYear(album.getReleaseYear())
      .addAllSongTitle(album.getSongsTitles())
      .build();

As the previous code listing demonstrates, a "builder" is used to populate the immutable instance of the class generated by Protocol Buffers. With a reference to this instance, I can now easily write the contents of the instance out in Protocol Buffers's binary form using the method toByteArray() on that instance as shown in the next code listing.

Writing Binary Form of AlbumProtos.Album

final byte[] binaryAlbum = albumMessage.toByteArray();

Reading a byte[] array back into an instance of Album can be accomplished as shown in the next code listing.

Instantiating Album from Binary Form of AlbumProtos.Album

/**
 * Generates an instance of Album based on the provided
 * bytes array.
 *
 * @param binaryAlbum Bytes array that should represent an
 *    AlbumProtos.Album based on Google Protocol Buffers
 *    binary format.
 * @return Instance of Album based on the provided binary form
 *    of an Album; may be {@code null} if an error is encountered
 *    while trying to process the provided binary data.
 */
public Album instantiateAlbumFromBinary(final byte[] binaryAlbum)
{
   Album album = null;
   try
   {
      final AlbumProtos.Album copiedAlbumProtos = AlbumProtos.Album.parseFrom(binaryAlbum);
      final List<String> copiedArtists = copiedAlbumProtos.getArtistList();
      final List<String> copiedSongsTitles = copiedAlbumProtos.getSongTitleList();
      album = new Album.Builder(
         copiedAlbumProtos.getTitle(), copiedAlbumProtos.getReleaseYear())
         .artists(copiedArtists)
         .songsTitles(copiedSongsTitles)
         .build();
   }
   catch (InvalidProtocolBufferException ipbe)
   {
      out.println("ERROR: Unable to instantiate AlbumProtos.Album instance from provided binary data - "
         + ipbe);
   }
   return album;
}

As indicated in the last code listing, a checked exception InvalidProtocolBufferException can be thrown during the invocation of the static method parseFrom(byte[]) defined in the generated class. Obtaining a "deserialized" instance of the generated class is essentially a single line and the rest of the lines are getting data out of the instantiation of the generated class and setting that data in the original Album class's instance.

The demonstration class includes two lines that print out the contents of the original Album instance and the instance ultimately retrieved from the binary representation. These two lines include invocations of System.identityHashCode() on the two instances to prove that they are not the same instance even though their contents match. When this code is executed with the hard-coded Album instance details shown earlier, the output looks like this:

BEFORE Album (1323165413): 'Songs from the Big Chair' (1985) by [Tears For Fears] features songs [Shout, The Working Hour, Everybody Wants to Rule the World, Mothers Talk, I Believe, Broken, Head Over Heels, Listen]
 AFTER Album (1880587981): 'Songs from the Big Chair' (1985) by [Tears For Fears] features songs [Shout, The Working Hour, Everybody Wants to Rule the World, Mothers Talk, I Believe, Broken, Head Over Heels, Listen]

From this output, we see that the relevant fields are the same in both instances and that the two instances truly are unique. This is a bit more work than using Java's "nearly automatic" Serialization mechanism implementing the Serializable interface, but there are important advantages associated with this approach that can justify the cost. In Effective Java, Third Edition, Josh Bloch discusses the security vulnerabilities associated with deserialization in Java's default mechanism and asserts that "There is no reason to use Java serialization in any new system you write."

Monday, January 15, 2018

Easy Fine-Grained Sorting with JDK 8

Java 8's introduction of streams and useful static/default methods on the Comparator interface make it easy to compare two objects based on individual fields' values without need to implement a compare(T,T) method on the class whose objects are being compared.

I'm going to use a simple Song class to help demonstrate this and its Song.java code listing is shown next.

Song.java

package dustin.examples.jdk8;

/**
 * Simple class encapsulating details related to a song
 * and intended to be used for demonstration of JDK 8.
 */
public class Song
{
   /** Song title. */
   private final String title;

   /** Album on which song was originally included. */
   private final String album;

   /** Song's artist. */
   private final String artist;

   /** Year song was released. */
   private final int year;

   /**
    * Constructor accepting this instance's title, artist, and release year.
    *
    * @param newTitle Title of song.
    * @param newAlbum Album on which song was originally included.
    * @param newArtist Artist behind this song.
    * @param newYear Year song was released.
    */
   public Song(final String newTitle, final String newAlbum,
               final String newArtist, final int newYear)
   {
      title = newTitle;
      album = newAlbum;
      artist = newArtist;
      year = newYear;
   }

   public String getTitle()
   {
      return title;
   }

   public String getAlbum()
   {
      return album;
   }

   public String getArtist()
   {
      return artist;
   }

   public int getYear()
   {
      return year;
   }

   @Override
   public String toString()
   {
      return "'" + title + "' (" + year + ") from '" + album + "' by " + artist;
   }
}

The Song class whose listing was just shown lacks a compare method, but we can still compare instances of this class in JDK 8 very easily. Based on the class definition of Song just shown, the following code can be used to sort a List of song instances based, in order, on year released, artist, and finally album.

Sorting List of Songs by Year, Artist, and Album (in that order)

/**
 * Returns a sorted version of the provided List of Songs that is
 * sorted first by year of song's release, then sorted by artist,
 * and then sorted by album.
 *
 * @param songsToSort Songs to be sorted.
 * @return Songs sorted, in this order, by year, artist, and album.
 */
private static List<Song> sortedSongsByYearArtistAlbum(
   final List<Song> songsToSort)
{
   return songsToSort.stream()
      .sorted(
         Comparator.comparingInt(Song::getYear)
                   .thenComparing(Song::getArtist)
                   .thenComparing(Song::getAlbum))
      .collect(Collectors.toList());
}

The above code listing would have been slightly less verbose had I statically imported the Comparator and the Collectors, but it's still fairly concise to include those interface and class names in the listing and probably more useful for an introductory blog post on this subject.

In the above code listing, the static default methods Comparator.comparingInt and Comparator.thenComparing are used to sort the stream of Song associated with the underlying List by year, and then by artist, and finally by album. The code is highly readable and allows for comparison of objects (and resulting sorting of those instances) based on arbitrary individual accessor methods without need for an explicitly specified Comparator (natural sorting order used for each compared accessor result). Note that if an explicit Comparator is desired, it can be provided to these static default methods via overloaded methods of the same name that accept a Comparator.

The next code listing is the entire demonstration class. It includes the method just shown and also shows the contrived example constructed of an unsorted List of songs.

FineGrainSortingDemo.java

package dustin.examples.jdk8;

import static java.lang.System.out;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Demonstration of easy fine-grained sorting in JDK 8 via
 * stream support for sorting and Comparator's static and
 * default method implementations.
 */
public class FineGrainSortingDemo
{
   /**
    * Construct List of {@code Song}s.
    * 
    * @return Instances of {@code Song}.
    */
   private static List<Song> generateSongs()
   {
      final ArrayList<Song> songs = new ArrayList<>();
      songs.add(
         new Song(
            "Photograph",
            "Pyromania",
            "Def Leppard",
            1983));
      songs.add(
         new Song(
            "Hysteria",
            "Hysteria",
            "Def Leppard",
            1987));
      songs.add(
         new Song(
            "Shout",
            "Songs from the Big Chair",
            "Tears for Fears",
            1984));
      songs.add(
         new Song(
            "Everybody Wants to Rule the World",
            "Songs from the Big Chair",
            "Tears for Fears",
            1985));
      songs.add(
         new Song(
            "Head Over Heels",
            "Songs from the Big Chair",
            "Tears for Fears",
            1985
         ));
      songs.add(
         new Song(
            "Enter Sandman",
            "Metallica",
            "Metallica",
            1991
         )
      );
      songs.add(
         new Song(
            "Money for Nothing",
            "Brothers in Arms",
            "Dire Straits",
            1985
         )
      );
      songs.add(
         new Song(
            "Don't You (Forget About Me)",
            "A Brass Band in African Chimes",
            "Simple Minds",
            1985
         )
      );
      return songs;
   }

   /**
    * Returns a sorted version of the provided List of Songs that is
    * sorted first by year of song's release, then sorted by artist,
    * and then sorted by album.
    *
    * @param songsToSort Songs to be sorted.
    * @return Songs sorted, in this order, by year, artist, and album.
    */
   private static List<Song> sortedSongsByYearArtistAlbum(
      final List<Song> songsToSort)
   {
      return songsToSort.stream()
         .sorted(
            Comparator.comparingInt(Song::getYear)
                      .thenComparing(Song::getArtist)
                      .thenComparing(Song::getAlbum))
         .collect(Collectors.toList());
   }

   /**
    * Demonstrate fine-grained sorting in JDK 8.
    *
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final List<Song> songs = generateSongs();
      final List<Song> sortedSongs = sortedSongsByYearArtistAlbum(songs);
      out.println("Original Songs:");
      songs.stream().forEach(song -> out.println("\t" + song));
      out.println("Sorted Songs");
      sortedSongs.forEach(song -> out.println("\t" + song));
   }
}

The output from running the above code is shown next and lists the newly ordered Songs after using the sorting code. It's worth noting that this stream.sorted() operation does not change the original List (it acts upon the stream rather than upon the List).

Original Songs:
 'Photograph' (1983) from 'Pyromania' by Def Leppard
 'Hysteria' (1987) from 'Hysteria' by Def Leppard
 'Shout' (1984) from 'Songs from the Big Chair' by Tears for Fears
 'Everybody Wants to Rule the World' (1985) from 'Songs from the Big Chair' by Tears for Fears
 'Head Over Heels' (1985) from 'Songs from the Big Chair' by Tears for Fears
 'Enter Sandman' (1991) from 'Metallica' by Metallica
 'Money for Nothing' (1985) from 'Brothers in Arms' by Dire Straits
 'Don't You (Forget About Me)' (1985) from 'A Brass Band in African Chimes' by Simple Minds
Sorted Songs
 'Photograph' (1983) from 'Pyromania' by Def Leppard
 'Shout' (1984) from 'Songs from the Big Chair' by Tears for Fears
 'Money for Nothing' (1985) from 'Brothers in Arms' by Dire Straits
 'Don't You (Forget About Me)' (1985) from 'A Brass Band in African Chimes' by Simple Minds
 'Everybody Wants to Rule the World' (1985) from 'Songs from the Big Chair' by Tears for Fears
 'Head Over Heels' (1985) from 'Songs from the Big Chair' by Tears for Fears
 'Hysteria' (1987) from 'Hysteria' by Def Leppard
 'Enter Sandman' (1991) from 'Metallica' by Metallica

JDK 8's introduction of streams and default and static methods in interfaces (particularly on Comparator in this case) make it easy to compare two objects field-by-field in a desirable order without any explicit Comparator other than the pre-built static default methods on the Comparator interface if the fields being compared have a desired natural order.

Thursday, January 11, 2018

Converting Collections to Maps with JDK 8

I have run into the situation several times where it is desirable to store multiple objects in a Map instead of a Set or List because there are some advantages from using a Map of unique identifying information to the objects. Java 8 has made this translation easier than ever with streams and the Collectors.toMap(...) methods.

One situation in which it has been useful to use a Map instead of a Set is when working with objects that lack or have sketchy equals(Object) or hashCode() implementations, but do have a field that uniquely identifies the objects. In those cases, if I cannot add or fix the objects' underlying implementations, I can gain better uniqueness guarantees by using a Map of the uniquely identifying field of the class (key) to the class's instantiated object (value). Perhaps a more frequent scenario when I prefer Map to List or Set is when I need to lookup items in the collection by a specific uniquely identifying field. A map lookup on a uniquely identifying key is speedy and often much faster than depending on iteration and comparing each object with invocation to the equals(Object) method.

With JDK 8, it's easier than ever to construct a Map from an existing List or Set. To help demonstrate this, a simple Book class will be used. This Book class does not override equals(Object) or hashCode() from the Object class and so is not an appropriate class to use in a Set or as a Map key. However, its getIsbn() method returns an International Standard Book Number that is assumed unique for purposes of this demonstration.

Book.java

package dustin.examples.jdk8;

/**
 * Represents a book, but does not override {@code equals(Object)}
 * or {@code hashCode()}.
 */
public class Book
{
   /** International Standard Book Number (ISBN-13). */
   final String isbn;

   /** Title of book. */
   final String title;

   /** Edition of book. */
   final int edition;

   /**
    * Constructor.
    *
    * @param newIsbn International Standard Book Number (-13).
    * @param newTitle Title.
    * @param newEdition Edition.
    */
   public Book(final String newIsbn, final String newTitle, final int newEdition)
   {
      isbn = newIsbn;
      title = newTitle;
      edition = newEdition;
   }

   /**
    * Provide ISBN-13 identifier associated with this book.
    *
    * @return ISBN-13 identifier.
    */
   public String getIsbn()
   {
      return isbn;
   }

   /**
    * Provide title of this book.
    *
    * @return Book's title.
    */
   public String getTitle()
   {
      return title;
   }

   /**
    * Provide edition of this book.
    *
    * @return Book's edition.
    */
   public int getEdition()
   {
      return edition;
   }

   @Override
   public String toString()
   {
      return title + " (Edition " + edition + ") - ISBN-13: " + isbn;
   }
}

With this class in place, the demonstration class CollectionToMapDemo shows how easy it is with JDK 8 to convert various Java collection types (Set, List, and even arrays) to a Map.

CollectionToMapDemo.java

package dustin.examples.jdk8;

import static java.lang.System.out;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Demonstrates conversion of Java collections to Java Maps.
 */
public class CollectionToMapDemo
{
   /**
    * Multiple instances of Book, a class that lacks a proper
    * equals(Object) method, but for which its getIsbn() method
    * is assumed to return a unique identifier for each instance.
    */
   private static final Book[] books;

   static
   {
      books = new Book[]
         {
            new Book("978-0-201-31005-4", "Effective Java", 1),
            new Book("978-0-321-35668-0", "Effective Java", 2),
            new Book("978-0-13-468599-1", "Effective Java", 3)
         };
   }

   /**
    * Convert provided array of Book instances to Map of each Book's ISBN to
    * that instance of the Book.
    * 
    * @param booksArray Array of Book instances.
    * @return Map of each book's ISBN (key) to the book's full instance (value).
    */
   private static Map<String, Book> convertArrayToMap(final Book[] booksArray)
   {
      return Arrays.stream(booksArray).collect(
         Collectors.toMap(Book::getIsbn, book -> book));
   }

   /**
    * Convert provided List of Book instances to Map of each Book's ISBN to
    * that instance of the Book.
    *
    * @param booksList List of Book instances.
    * @return Map of each book's ISBN (key) to the book's full instance (value).
    */
   private static Map<String, Book> convertListToMap(final List<Book> booksList)
   {
      return booksList.stream().collect(
         Collectors.toMap(Book::getIsbn, book -> book));
   }

   /**
    * Convert provided Set of Book instances to Map of each Book's ISBN to
    * that instance of the Book.
    *
    * @param booksSet Set of Book instances.
    * @return Map of each book's ISBN (key) to the book's full instance (value).
    */
   private static Map<String, Book> convertSetToMap(final Set<Book> booksSet)
   {
      return booksSet.stream().collect(
         Collectors.toMap(Book::getIsbn, book -> book));
   }

   public static void main(final String[] arguments)
   {
      out.println("ARRAY->MAP:\n" + convertArrayToMap(books));

      final List<Book> booksList = Arrays.asList(books);
      out.println("LIST->MAP:\n" + convertListToMap(booksList));

      final Set<Book> booksSet
         = new HashSet<>(Arrays.stream(books).collect(Collectors.toSet()));
      out.println("SET->MAP:\n" + convertSetToMap(booksSet));
   }
}

The most important methods in the class listing just shown are convertArrayToMap(Book[]), convertListToMap(List<Book>), and convertSetToMap(Set<Book>). All three implementations are the same once a stream based on the underlying Set, List, or array has been accessed. In all three cases, it's merely a matter of using one of the stream's collect() method (a reduction operator that is typically preferable over sequential iteration) and passing it an implementation of the Collector interface that is provided via a predefined toMap() Collector from the Collectors class.

The output from running this demonstration class against the instances of Book is shown next:

ARRAY->MAP:
{978-0-201-31005-4=Effective Java (Edition 1) - ISBN-13: 978-0-201-31005-4, 978-0-321-35668-0=Effective Java (Edition 2) - ISBN-13: 978-0-321-35668-0, 978-0-13-468599-1=Effective Java (Edition 3) - ISBN-13: 978-0-13-468599-1}
LIST->MAP:
{978-0-201-31005-4=Effective Java (Edition 1) - ISBN-13: 978-0-201-31005-4, 978-0-321-35668-0=Effective Java (Edition 2) - ISBN-13: 978-0-321-35668-0, 978-0-13-468599-1=Effective Java (Edition 3) - ISBN-13: 978-0-13-468599-1}
SET->MAP:
{978-0-201-31005-4=Effective Java (Edition 1) - ISBN-13: 978-0-201-31005-4, 978-0-321-35668-0=Effective Java (Edition 2) - ISBN-13: 978-0-321-35668-0, 978-0-13-468599-1=Effective Java (Edition 3) - ISBN-13: 978-0-13-468599-1}

I have run into several situations in which it has been advantageous to have multiple objects in a Map of unique identifier to full instance of those objects, but have been given the objects in a Set, List, or array. Although it's never been particularly difficult to convert these Sets, Lists, and arrays to Maps in Java, it's easier than ever in Java 8 to make this conversion.

Wednesday, January 10, 2018

The Highly Useful Java ChronoUnit Enum

Several years ago, I published the blog post "The Highly Useful Java TimeUnit Enum" that looked at the TimeUnit enum introduced with JDK 5. JDK 8 introduced a newer enum, ChronoUnit, that is better suited than TimeUnit for contexts other than concurrency such as date/time manipulations.

Located in the java.time.temporal package, the ChronoUnit class implements the TemporalUnit interface, an interface used extensively in the highly desired JDK 8-introduced Date/Time API. The blog post "Days Between Dates in Java 8" demonstrates use of this class to calculate periods of time between two instances of Temporal.

The blog post "Java 9. Where 'forever' is hard coded." looks at "two new methods in the TimeUnit class" for JDK 9. These methods, toChronoUnit() and of(ChronoUnit), support translation of TimeUnit to a ChronoUnit and translation of ChronoUnit to TimeUnit. Not all values in ChronoUnit can be translated to an equivalent in TimeUnit, in which cases an IllegalArgumentException is thrown.

The Javadoc comments on each value in ChronoUnit describe what unit of time each value represents. However, it's interesting to me to see what Duration is returned for each value in ChronoUnit. The following code snippet will write these Duration's toString() representations to standard output for all values in the ChronoUnit enum.

Displaying Durations of ChronoUnits

for (final ChronoUnit unit : ChronoUnit.values())
{
   final Duration duration = unit.getDuration();
   out.println(unit + ": " + duration + " (" + duration.getSeconds() + " seconds)");
}

When executed, the above code produces the following output:

Nanos: PT0.000000001S (0 seconds)
Micros: PT0.000001S (0 seconds)
Millis: PT0.001S (0 seconds)
Seconds: PT1S (1 seconds)
Minutes: PT1M (60 seconds)
Hours: PT1H (3600 seconds)
HalfDays: PT12H (43200 seconds)
Days: PT24H (86400 seconds)
Weeks: PT168H (604800 seconds)
Months: PT730H29M6S (2629746 seconds)
Years: PT8765H49M12S (31556952 seconds)
Decades: PT87658H12M (315569520 seconds)
Centuries: PT876582H (3155695200 seconds)
Millennia: PT8765820H (31556952000 seconds)
Eras: PT8765820000000H (31556952000000000 seconds)
Forever: PT2562047788015215H30M7.999999999S (9223372036854775807 seconds)

The "PT" prefix on each of the Duration's string representations shown above indicates that the representation is a "period" duration designation ("P") and a "time" designation ("T") per the ISO-8601 standard. The "S", "M", and "H" are seconds, minutes, and hours respectively. The values of ChronoUnit that represent time units less than a second (NANOS, MICROS, and MILLIS) show "0 seconds" because they are less than 1 second and the returned value is an integral long.

The Javadoc comments on each value defined in the ChronoUnit class are well written. They follow what in my mind is a Javadoc "best practice": place a concise but informative initial sentence in the Javadoc to show up in the "Method Summary" section of the generated HTML page and place additional useful details in sentences after that initial summary sentence. For example, the Javadoc comment for ChronoUnit.ERAS states, "Unit that represents the concept of an era. The ISO calendar system doesn't have eras thus it is impossible to add an era to a date or date-time. The estimated duration of the era is artificially defined as 1,000,000,000 Years. When used with other calendar systems there are no restrictions on the unit." The bolded sentence (I added that emphasis) is what shows up in the "Method Summary" and the entire text shown here is displayed above the method in its complete explanation.

One of the more interesting values in the ChronoUnit enum is FOREVER. As the output of the code listing above demonstrated, the FOREVER value has a Duration of "PT2562047788015215H30M7.999999999S", corresponding to 2562047788015215 hours, 30 minutes, and 7.999999999 seconds. Or, as Grzegorz Gajos expresses it, "Java defines forever as 9 223 372 036 854 775 807 seconds. Which is 2.92277266 × 1011 years. Better be sure to schedule Java upgrade in your application before times run out."

When would ChronoUnit.FOREVER be useful? Its Javadoc-based description explains its primary reason for existence: "Artificial unit that represents the concept of forever. This is primarily used with TemporalField to represent unbounded fields such as the year or era. The estimated duration of the era is artificially defined as the largest duration supported by Duration."

TimeUnit is a useful enum for working with Java's concurrency constructs and could be used in contexts other than concurrency as long as some severe limitations for these other contexts were considered. The JDK 8-introduced ChronoUnit is a better enum for these non-concurrency contexts and is especially designed for use with the JDK 8 Date/Time API.