DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Trending

  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Fixing Common Oracle Database Problems
  • Internal Developer Portals: Modern DevOps's Missing Piece
  • Unlocking the Potential of Apache Iceberg: A Comprehensive Analysis
  1. DZone
  2. Coding
  3. Java
  4. Java Collections Are Evolving

Java Collections Are Evolving

Collections have seen a lot of changes in the past couple of JDKs. Let's look at how they've improved in Java 9 and Java 10.

By 
Trisha Gee user avatar
Trisha Gee
·
Jun. 06, 18 · Tutorial
Likes (43)
Comment
Save
Tweet
Share
33.7K Views

Join the DZone community and get the full member experience.

Join For Free

This article is featured in the new DZone Guide to Java: Features, Improvements, & Updates. Get your free copy for more insightful articles, industry statistics, and more! 

It's very easy as a developer to get into the routine of using the same tools the same way as always, particularly if the language we use has seen lengthy periods of extreme stability. Java is no longer in that mode: with releases every six months, we can expect to see updates to it very frequently.

These updates may be large structural changes (like the Java Module System or API updates (for example, the Process API). Each release is likely to add something that's useful to us, so staying up-to-date is going to help us to be more productive developers. In this article, we're going to specifically look at the updates to Collections in Java 9 (released September 2017) and Java 10 (released March 2018). Java Collections are a core part of the language that we probably touch in one way or another every day, and anything that makes it easier to work with Collections makes our job easier.

Java 9: Convenience Factory Methods for Collections

Java 9 introduced new ways to create immutable collections. At some point, we've all written code that looks something like this:

List<String> moods = Arrays.asList("HAPPY", "SAD");


As of Java 9, you can now write the following instead:

List<String> moods = List.of("HAPPY", "SAD");


While saving six characters may be exciting to those who prefer very terse code, this might not seem like a huge improvement.

What's important to realize, though, is that this second case creates an immutable list. We may have fallen into the trap of thinking Arrays.asList returns an immutable list because it's not possible to append to it:

jshell> List<String> moods = Arrays.asList("HAPPY", "SAD");
moods ==> [HAPPY, SAD]

jshell> moods.add("ANGRY")
| java.lang.UnsupportedOperationException thrown
| at AbstractList.add (AbstractList.java:153)
| at AbstractList.add (AbstractList.java:111)
| at (#2:1)


However, changes are allowed within the limits of the list:

jshell> moods.set(0, "ANGRY")
$3 ==> "HAPPY"

jshell> System.out.println(moods)
[ANGRY, SAD]


It's likely this was not intended when we wrote this code. Instead, what we really wanted was:

List<String> moods = Collections.unmodifiableList(Arrays asList("HAPPY", "SAD"));


Now you can see that:

List<String> moods = List.of("HAPPY", "SAD");


...which does return an immutable list, is not only much shorter than the pre-Java-9 equivalent but also more correct than simply using Arrays.asList.

This is useful for Lists, but what's even more useful is that similar methods have been added for Sets and Maps. To create an immutable Set before Java 9, you could use something like:

Set<String> moods = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("HAPPY", "SAD")));


This really was unpleasant, even when using static imports to reduce some of the noise. In Java 9, it's simply:

Set<String> moods = Set.of("HAPPY", "SAD");


There are also factory methods for creating Maps. Before Java 9, if we wanted to create a Map with a fixed set of values, we'd have to do something a little long-winded:

    Map<String, Mood> wordToMood = new HashMap<>();
    wordToMood.put("happy", HAPPY);
    wordToMood.put("good", HAPPY);
    wordToMood.put("great", HAPPY);
    //… more values
    wordToMood.put("horrible", SAD);
    wordToMood.put("bad", SAD);
    wordToMood.put("awful", SAD);


This was even worse if we wanted to initialize this for a constant (e.g. a static field), as it would have to be put somewhere in a static block, and wrapping this in an unmodifiableMap simply adds to the noise. In Java 9, this can be:

Map<String, Mood> wordToMood
    = Map.ofEntries(Map.entry("happy", HAPPY), 
                    Map.entry("good", HAPPY),
                    Map.entry("great", HAPPY)
                    //...more values
                    Map.entry("horrible", SAD),
                    Map.entry("bad", SAD),
                    Map.entry("awful", SAD));


Static imports can make this more succinct, too. This Map.ofEntries method works for any arbitrary number of key/value pairs, as each pair is wrapped in a Map.entry and the ofEntries method takes a vararg. If the Map has fewer than ten values, we might want to use the convenience Map.of method, which takes up to ten key/value parameters:

Map<String, Mood> wordToMood = Map.of("happy", HAPPY,
                                      "good", HAPPY,
                                      "great", HAPPY,
                                      "horrible", SAD,
                                      "bad", SAD,
                                      "awful", SAD);


Java 10: Creating Immutable Copies of Collections

Java 9 introduced these factory methods to make it easier to create new immutable collections from known values. Java 10 recognizes that this is not the only way we create collections, and introduces more ways of creating immutable collections from existing collections or operations.

There's now an easy way to create an immutable Collection that's a copy of an existing Collection. Prior to Java 10, you could create a new list that was a copy of an existing collection using a copy constructor:

List<String> newCopyOfCollection = new ArrayList<>(moods);


In this case, even if the original collection moods is immutable/ unmodifiable, the new collection is not:

jshell> List<String> newCopyOfCollection = new ArrayList<>(moods);
newCopyOfCollection ==> [HAPPY, SAD]

jshell> newCopyOfCollection.add("ANGRY")
$5 ==> true

jshell> System.out.println(newCopyOfCollection)
[HAPPY, SAD, ANGRY]


To create a list that cannot be changed, it would have to be wrapped in an unmodifiable list:

jshell> List<String> newCopyOfCollection = Collections.unmodifiableList(new ArrayList<>(moods));
newCopyOfCollection ==> [HAPPY, SAD]

jshell> newCopyOfCollection.add("ANGRY")
| java.lang.UnsupportedOperationException thrown
| at Collections$UnmodifiableCollection.add
(Collections.java:1056)
| at (#8:1)


For Lists specifically, you can also use Collections.copy, but the syntax is a bit clunky and it's easy to get runtime errors if your destination list isn't set up correctly.

In Java 10, it's much easier to create a new immutable list from an existing Collection:

List<String> newCopyOfCollection = List.copyOf(moods);


As you'd expect, you can't add or remove elements or alter the items in the list:

jshell> List<String> newCopyOfCollection = List.copyOf(moods);
newCopyOfCollection ==> [HAPPY, SAD]

jshell> newCopyOfCollection.add("ANGRY")
| java.lang.UnsupportedOperationException thrown
| at ImmutableCollections.uoe
(ImmutableCollections.java:71)
| at
ImmutableCollections$AbstractImmutableList.add
(ImmutableCollections.java:77)
| at (#21:1)


Similar methods exist for Set:

Set<String> setCopyOfCollection = Set.copyOf(moods);


...and Map. The Map version takes another Map to copy, not a Collection:

Map<String, Mood> copyOfMoodMap = Map.copyOf(wordToMood);


Java 10: Creating Immutable Collections From Streams

Java 10 makes it easy to create immutable collections from Stream operations, with the addition of the toUnmodifiableList, toUnmodifiableSet, and toUnmodifiableMap methods on Collectors. This means that from Java 10, you can create immutable Collections not only from some known values or by copying an existing Collection or Map, but also from Stream operations.

For example, let's assume we have a Stream operation that takes a sentence and turns it into a list of unique words:

List<String> uniqueWords = 
    Pattern.compile("\\s*[^\\p{IsAlphabetic}]+\\s*").splitAsStream(message)
                                                    .map(String::toLowerCase)
                                                    .distinct()
                                                    .collect(Collectors.toList());


This list is a simple ArrayList containing the results, and can be changed:

jshell> String message = "I am so so happy today, and I am not happy every day";
message ==> "I am so so happy today, and I am not happy every day"

jshell> List<String> uniqueWords = Pattern.compile("\\s*[^\\p{IsAlphabetic}]+\\s*").splitAsStream(message).
    ...>
map(String::toLowerCase).
    ...> distinct().
    ...>
collect(Collectors.toList());
uniqueWords ==> [i, am, so, happy, today, and, not, every, day]

jshell> uniqueWords.getClass()
$35 ==> class java.util.ArrayList

jshell> uniqueWords.add("SAD")
$36 ==> true

jshell> System.out.println(uniqueWords)
[i, am, so, happy, today, and, not, every, day, SAD]


If the aim of this stream operation was to return some fixed set of results, we probably didn't want to return an ArrayList but some sort of immutable list. In Java 10, we can do this:

List<String> uniqueWords
    = Patternc ompile("\\s*[^\\p{IsAlphabetic}]+\\s*"
        .splitAsStream(message)
        .map(String::toLowerCase)
        .distinct()
        .collect(Collectors.toUnmodifiableList());


This returns a List that cannot be changed:

jshell> List<String> uniqueWords = Pattern.compile("\\s*[^\\p{IsAlphabetic}]+\\s*").splitAsStream(message).
    ...>
map(String::toLowerCase).
    ...>
distinct().
    ...>
collect(Collectors.toUnmodifiableList());
uniqueWords ==> [i, am, so, happy, today, and, not, every, day]

jshell> uniqueWords.getClass()
$39 ==> class java.util.ImmutableCollections$ListN

jshell> uniqueWords.add("SAD")
| java.lang.UnsupportedOperationException thrown
| at ImmutableCollections.uoe
(ImmutableCollections.java:71)
| at
ImmutableCollections$AbstractImmutableList.add
(ImmutableCollections.java:77)
| at (#40:1)


There's also Collectors.toUnmodifiableSet, which may be more appropriate in this scenario since the Collection contains only unique values.

Collectors.toUnmodifiableMap is for creating immutable Maps, and, like toMap, is a little trickier, as it means we need to give functions to define what the keys and the values are. If we change our example to use a Map to calculate the number of times each word is in the sentence, we can demonstrate how to collect into an immutable Map:

Map<String, Long> wordCount = Pattern.compile("\\s*[^\\p{IsAlphabetic}]+\\s*").
    splitAsStream(message).
    map(String::toLowerCase).
    collect(Collectors.toUnmodifiableMap(Function.identity(), word -> 1L, (oldCount, newVal) -> oldCount + newVal));


As before, we can see that values can't be added to or removed from this Map:

jshell> Map<String, Long> wordCount = Pattern.compile("\\s*[^\\p{IsAlphabetic}]+\\s*").
    splitAsStream(message).
     ...>
map(String::toLowerCase).
     ...>
collect(Collectors.toUnmodifiableMap(Function.identity(),
     ...>
word -> 1L,
     ...>
(oldCount, newVal) -> oldCount + newVal));
wordCount ==> {and=1, i=2, am=2, day=1, so=2, every=1, today=1, not=1, happy=2}

jshell> wordCount.getClass()
$49 ==> class java.util.ImmutableCollections$MapN

jshell> wordCount.put("WORD", 1000L)
| java.lang.UnsupportedOperationException thrown
| at ImmutableCollections.uoe
(ImmutableCollections.java:71)
| at
ImmutableCollections$AbstractImmutableMap.put
(ImmutableCollections.java:558)
| at (#50:1)


In conclusion, we can see that Java is evolving to make it easier for us developers to write convenient and correct code. Some of these changes are just small additions to existing APIs, so it's easy to miss them in the fuss of bigger language changes. Collections have evolved in the most recent versions of Java, and if we stay up-to-date and use these changes, we'll find our lives a little bit easier.

This article is featured in the new DZone Guide to Java: Features, Improvements, & Updates. Get your free copy for more insightful articles, industry statistics, and more!



If you enjoyed this article and want to learn more about Java Collections, check out this collection of tutorials and articles on all things Java Collections.

Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!