Java Collections Archives - Piotr's TechBlog https://piotrminkowski.com/tag/java-collections/ Java, Spring, Kotlin, microservices, Kubernetes, containers Fri, 26 Apr 2024 07:57:00 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 Java Collections Archives - Piotr's TechBlog https://piotrminkowski.com/tag/java-collections/ 32 32 181738725 Interesting Facts About Java Streams and Collections https://piotrminkowski.com/2024/04/25/interesting-facts-about-java-streams-and-collections/ https://piotrminkowski.com/2024/04/25/interesting-facts-about-java-streams-and-collections/#comments Thu, 25 Apr 2024 11:25:07 +0000 https://piotrminkowski.com/?p=15228 This article will show some interesting features of Java Streams and Collections you may not heard about. We will look at both the latest API enhancements as well as the older ones that have existed for years. That’s my private list of features I used recently or I just came across while reading articles about […]

The post Interesting Facts About Java Streams and Collections appeared first on Piotr's TechBlog.

]]>
This article will show some interesting features of Java Streams and Collections you may not heard about. We will look at both the latest API enhancements as well as the older ones that have existed for years. That’s my private list of features I used recently or I just came across while reading articles about Java. If you are interested in Java you can find some similar articles on my blog. In one of them, you can find a list of less-known but useful Java libraries.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that, you need to clone my GitHub repository. Once you clone the repository, you can switch to the jdk22 branch. Then you should just follow my instructions.

Mutable or Immutable

The approach to the collections immutability in Java can be annoying for some of you. How do you know if a Java Collection is mutable or immutable? Java does not provide dedicated interfaces and implementations for mutable or immutable collections like e.g. Kotlin. Of course, you can switch to the Eclipse Collections library, which provides a clear differentiation of types between readable, mutable, and immutable. However, if you are still with a standard Java Collections let’s analyze the situation by the example of the java.util.List interface. Some years ago Java 16 introduced a new Stream.toList() method to convert between streams and collections. Probably, you are using it quite often 🙂 It is worth mentioning that this method returns an unmodifiable List and allows nulls.

var l = Stream.of(null, "Green", "Yellow").toList();
assertEquals(3, l.size());
assertThrows(UnsupportedOperationException.class, () -> l.add("Red"));
assertThrows(UnsupportedOperationException.class, () -> l.set(0, "Red"));
Java

So, the Stream.toList() method is not just a replacement for the older approach based on the Collectors.toList(). On the other hand, an older Collectors.toList() method returns a modifiable List and also allows nulls.

var l = Stream.of(null, "Green", "Yellow").collect(Collectors.toList());
l.add("Red");
assertEquals(4, l.size());
Java

Finally, let’s see how to achieve the next possible option here. We need an unmodifiable List and does not allow nulls. In order to achieve it, we have to use the Collectors.toUnmodifiableList() method as shown below.

assertThrows(NullPointerException.class, () ->
        Stream.of(null, "Green", "Yellow")
                .collect(Collectors.toUnmodifiableList()));
Java

Grouping and Aggregations with Java Streams

Java Streams introduces several useful methods that allow us to group and aggregate collections using different criteria. We can find those methods in the java.util.stream.Collectors class. Let’s create the Employee record for testing purposes.

public record Employee(String firstName, 
                       String lastName, 
                       String position, 
                       int salary) {}
Java

Now, let’s assume we have a stream of employees and we want to calculate the sum of salary grouped by the position. In order to achieve this, we should combine two methods from the Collectors class: groupingBy and summingInt. If you would like to count the average salary by the position, you can just replace the summingInt method with the averagingInt method.

Stream<Employee> s1 = Stream.of(
    new Employee("AAA", "BBB", "developer", 10000),
    new Employee("AAB", "BBC", "architect", 15000),
    new Employee("AAC", "BBD", "developer", 13000),
    new Employee("AAD", "BBE", "tester", 7000),
    new Employee("AAE", "BBF", "tester", 9000)
);

var m = s1.collect(Collectors.groupingBy(Employee::position, 
   Collectors.summingInt(Employee::salary)));
assertEquals(3, m.size());
assertEquals(m.get("developer"), 23000);
assertEquals(m.get("architect"), 15000);
assertEquals(m.get("tester"), 16000);
Java

For more simple grouping we can the partitioningBy method. It always returns a map with two entries, one where the predicate is true and the second one where it is false. So, for the same stream as in the previous example, we can use partitioningBy to divide employees into those with salaries higher than 10000, and lower or equal to 10000.

var m = s1.collect(Collectors.partitioningBy(emp -> emp.salary() > 10000));
assertEquals(2, m.size());
assertEquals(m.get(true).size(), 2);
assertEquals(m.get(false).size(), 3);
Java

Let’s take a look at another example. This time, we will count the number of times each element occurs in the collection. Once again, we can use the groupingBy method, but this time in conjunction with the Collectors.counting() method.

var s = Stream.of(2, 3, 4, 2, 3, 5, 1, 3, 4, 4)
   .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
assertEquals(5, m.size());
assertEquals(m.get(4), 3);
Java

The Map.merge() Method

In the previous examples, we use methods provided by Java Streams to perform grouping and aggregations. Now, the question is if we can do the same thing with the standard Java collections without converting them to streams. The answer is yes – we can easily do it with the Map.merge() method. It is probably the most versatile operation among all the Java key-value methods. The Map.merge() method either puts a new value under the given key (if absent) or updates the existing key with a given value. Let’s rewrite the previous examples, to switch from Java streams to collections. Here’s the implementation for counting the number of times each element occurs in the collection.

var map = new HashMap<Integer, Integer>();
var nums = List.of(2, 3, 4, 2, 3, 5, 1, 3, 4, 4);
nums.forEach(num -> map.merge(num, 1, Integer::sum));
assertEquals(5, map.size());
assertEquals(map.get(4), 3);
Java

Then, we can implement the operation of calculating the sum of salary grouped by the position. So, we are grouping by the emp.position() and calculating the total salary by summing the previous value with the value taken from the current Employee in the list. The results are the same as in the examples from the previous section.

var s1 = List.of(
   new Employee("AAA", "BBB", "developer", 10000),
   new Employee("AAB", "BBC", "architect", 15000),
   new Employee("AAC", "BBD", "developer", 13000),
   new Employee("AAD", "BBE", "tester", 7000),
   new Employee("AAE", "BBF", "tester", 9000)
);
var map = new HashMap<String, Integer>();
s1.forEach(emp -> map.merge(emp.position(), emp.salary(), Integer::sum));
assertEquals(3, map.size());
assertEquals(map.get("developer"), 23000);
Java

Use EnumSet for Java Enum

If you are storing enums inside Java collections you should use EnumSet instead of e.g. more popular HashSet. The EnumSet and EnumMap collections are specialized versions of Set and Map that are built for enums. Those abstracts guarantee less memory consumption and much better performance. They are also providing some methods dedicated to simplifying integration with Java Enum. In order to compare processing time between EnumSet and a standard Set we can prepare a simple test. In this test, I’m creating a subset of Java Enum inside the EnumSet and then checking out if all the values exist in the target EnumSet.

var x = EnumSet.of(
    EmployeePosition.SRE,
    EmployeePosition.ARCHITECT,
    EmployeePosition.DEVELOPER);
long beg = System.nanoTime();
for (int i = 0; i < 100_000_000; i++) {
   var es = EnumSet.allOf(EmployeePosition.class);
   es.containsAll(x);
}
long end = System.nanoTime();
System.out.println(x.getClass() + ": " + (end - beg)/1e9);
Java

Here’s a similar test without EnumSet:

var x = Set.of(
    EmployeePosition.SRE,
    EmployeePosition.ARCHITECT,
    EmployeePosition.DEVELOPER);
long beg = System.nanoTime();
for (int i = 0; i < 100_000_000; i++) {
   var hs = Set.of(EmployeePosition.values());
   hs.containsAll(x);
}
long end = System.nanoTime();
System.out.println(x.getClass() + ": " + (end - beg)/1e9);
Java

The difference in time consumption before both two variants visible above is pretty significant.

class java.util.ImmutableCollections$SetN: 8.577672411
class java.util.RegularEnumSet: 0.184956851
ShellSession

Java 22 Stream Gatherers

The latest JDK 22 release introduces a new addition to Java streams called gatherers. Gatherers enrich the Java Stream API with capabilities for custom intermediate operations. Thanks to that we can transform data streams in ways that were previously complex or not directly supported by the existing API. First of all, this is a preview feature, so we need to explicitly enable it in the compiler configuration. Here’s the modification in the Maven pom.xml:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.13.0</version>
  <configuration>
    <release>22</release>
    <compilerArgs>--enable-preview</compilerArgs>
  </configuration>
</plugin>
XML

The aim of that article is not to provide you with a detailed explanation of Stream Gatherers. So, if you are looking for an intro it’s worth reading the following article. However, just to give you an example of how we can leverage gatherers I will provide a simple implementation of a circuit breaker with the slidingWindow() method. In order to show you, what exactly happens inside I’m logging the intermediate elements with the peek() method. Our implementation will open the circuit breaker if there are more than 10 errors in the specific period.

var errors = Stream.of(2, 0, 1, 3, 4, 2, 3, 0, 3, 1, 0, 0, 1)
                .gather(Gatherers.windowSliding(4))
                .peek(a -> System.out.println(a))
                .map(x -> x.stream().collect(summing()) > 10)
                .toList();
System.out.println(errors);
Java

The size of our sliding window is 4. Therefore, each time the slidingWindow() method creates a list with 4 subsequent elements. For each intermediate list, I’m summing the values and checking if the total number of errors is greater than 10. Here’s the output. As you see only the circuit breaker is opened for the [3, 4, 2, 3] fragment of the source stream.

[2, 0, 1, 3]
[0, 1, 3, 4]
[1, 3, 4, 2]
[3, 4, 2, 3]
[4, 2, 3, 0]
[2, 3, 0, 3]
[3, 0, 3, 1]
[0, 3, 1, 0]
[3, 1, 0, 0]
[1, 0, 0, 1]
[false, false, false, true, false, false, false, false, false, false]
Java

Use the Stream.reduce() Method

Finally, the last feature in our article. I believe that the Stream.reduce() method is not a very well-known and widely used stream method. However, it is very interesting. For example, we can use it to sum all the numbers in the Java List. The first parameter of the reduce method is an initial value, while the second is an accumulator algorithm.

var listOfNumbers = List.of(1, 2, 3, 4, 5);
var sum = listOfNumbers.stream().reduce(0, Integer::sum);
assertEquals(15, sum);
Java

Final Thoughts

Of course, it is just a small list of interesting facts about Java streams and collections. If you have some other favorite features you can put them in the article comments.

The post Interesting Facts About Java Streams and Collections appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2024/04/25/interesting-facts-about-java-streams-and-collections/feed/ 3 15228
Useful & Unknown Java Features https://piotrminkowski.com/2022/01/05/useful-unknown-java-features/ https://piotrminkowski.com/2022/01/05/useful-unknown-java-features/#comments Wed, 05 Jan 2022 15:54:04 +0000 https://piotrminkowski.com/?p=10438 In this article, you will learn about some useful Java features, you probably didn’t hear of. That’s my private list of features I used recently or I just came across while reading articles about Java. I’ll focus not on the language aspects, but rather on the API. I have already published all the examples related […]

The post Useful & Unknown Java Features appeared first on Piotr's TechBlog.

]]>
In this article, you will learn about some useful Java features, you probably didn’t hear of. That’s my private list of features I used recently or I just came across while reading articles about Java. I’ll focus not on the language aspects, but rather on the API. I have already published all the examples related to this article on Twitter in a form visible below. You may also find them on my Twitter account or just under the #java hashtag.

java-features-twitter

Do you like Java and want to be up-to-date with the latest features? You can also read my article about new features after Java 8. Below, you can find a list of eight unknown and useful described in the current article. Let’s begin!

1. Delay Queue

As you know, there are many types of collections available in Java. But did you hear about DelayQueue? It is a specific type of Java collection that allows us to sort elements based on their delay time. It is a very interesting class, to be honest. Although the DelayQueue class is a member of the Java Collections it belongs to the java.util.concurrent package. It implements the BlockingQueue interface. The elements can be taken from the queue only if their time has expired.

In order to use it, first, your class needs to implement the getDelay method from the Delayed interface. It does not have to be a class – you can use Java Record as well.

public record DelayedEvent(long startTime, String msg) implements Delayed {

    public long getDelay(TimeUnit unit) {
        long diff = startTime - System.currentTimeMillis();
        return unit.convert(diff, TimeUnit.MILLISECONDS);
    }

    public int compareTo(Delayed o) {
        return (int) (this.startTime - ((DelayedEvent) o).startTime);
    }

}

Let’s say we would like to delay the element 10 seconds. We just need to set the current time increased by 10 seconds on our DelayedEvent class.

final DelayQueue<DelayedEvent> delayQueue = new DelayQueue<>();
final long timeFirst = System.currentTimeMillis() + 10000;
delayQueue.offer(new DelayedEvent(timeFirst, "1"));
log.info("Done");
log.info(delayQueue.take().msg());

What’s the output of the code visible above? Let’s see.

2. Period of Days in Time Format

Ok, maybe it won’t be one of the Java features very useful for most of you 🙂 But to be honest, I have a soft spot for that feature… Anyway, Java 8 has improved a lot the time processing API. Starting from this version of Java you probably won’t have to use any additional library like Joda Time in most cases. Could you imagine that since Java 16 you may even express periods in a day, such as “in the morning” or “in the afternoon” using a standard formatter? There is a new format pattern for that called B.

String s = DateTimeFormatter
  .ofPattern("B")
  .format(LocalDateTime.now());
System.out.println(s);

Here’s my result. But of course, your result depends on your time of day.

Ok. Wait a moment… Now, you are probably wondering why it is called B. Don’t ask 😉 In fact, it is not the most intuitive name for this type of format. But maybe the following table resolves all the doubts. It is a fragment of pattern letters and symbols handled by the DateTimeFormatter. I guess that B was the first free letter. But of course, I could be wrong.

java-features-time-api

3. Stamped Lock

In my opinion, Java Concurrent is one of the most interesting Java packages. And at the same, one of the less known by the developers, especially if they work mostly with the web frameworks. How many of you have ever used locks in Java? Locking is a more flexible thread synchronization mechanism than the synchronized block. Starting from Java 8 you may use a new kind of lock called StampedLock. StampedLock is an alternative to using a ReadWriteLock. It allows optimistic locking for the read operations. Also, it has better performance than the ReentrantReadWriteLock.

Let’s say we have two threads. The first of them update a balance, while the second of them reads the current value of balance. In order to update the balance, we of course need to read its current value first. We need some kind of synchronization here, assuming the first thread is running multiple times at the same time. The second thread just illustrates how to use an optimistic lock for the read operation.

StampedLock lock = new StampedLock();
Balance b = new Balance(10000);
Runnable w = () -> {
   long stamp = lock.writeLock();
   b.setAmount(b.getAmount() + 1000);
   System.out.println("Write: " + b.getAmount());
   lock.unlockWrite(stamp);
};
Runnable r = () -> {
   long stamp = lock.tryOptimisticRead();
   if (!lock.validate(stamp)) {
      stamp = lock.readLock();
      try {
         System.out.println("Read: " + b.getAmount());
      } finally {
         lock.unlockRead(stamp);
      }
   } else {
      System.out.println("Optimistic read fails");
   }
};

Now, let’s just test it by running both threads 50 times simultaneously. It should work as expected – the final value of the balance is 60000.

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 50; i++) {
   executor.submit(w);
   executor.submit(r);
}

4. Concurrent Accumulators

Locks are not the only one interesting features in Java Concurrent package. Another one is called concurrent accumulators. There are also concurrent adders, but it is a pretty similar functionality. LongAccumulator (there is also DoubleAccumulator) updates a value using a supplied function. It allows us to implement a lock-free algorithm in a number of scenarios. It is usually preferable to AtomicLong when multiple threads update a common value.

Let’s see how it works. In order to create it, you need to set two arguments in the constructor. The first them is a function used to calculate the result of accumulation. Usually, you will the sum method. The second parameter indicates the initial value of our accumulator.

Now, let’s create the LongAccumulator with the initial value 10000 and then invoke the accumulate() method from multiple threads. What’s is the final result? If you think for a moment about it, we did exactly the same thing as in the previous section. But this time without any locks.

LongAccumulator balance = new LongAccumulator(Long::sum, 10000L);
Runnable w = () -> balance.accumulate(1000L);

ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
   executor.submit(w);
}

executor.shutdown();
if (executor.awaitTermination(1000L, TimeUnit.MILLISECONDS))
   System.out.println("Balance: " + balance.get());
assert balance.get() == 60000L;

5. Hex Format

There is no big story behind it. Sometimes we need to convert between hex-format strings, bytes, or chars. Since Java 17 you can use the HexFormat class. Just create an instance of HexFormat and then you can format e.g. an input byte table into the hex string. You can also e.g. parse input hex string to the byte table as shown below.

HexFormat format = HexFormat.of();

byte[] input = new byte[] {127, 0, -50, 105};
String hex = format.formatHex(input);
System.out.println(hex);

byte[] output = format.parseHex(hex);
assert Arrays.compare(input, output) == 0;

6. Binary Search for Arrays

Let’s say we want to insert a new element into the sorted table. The Arrays.binarySearch() returns an index of the search key, if contained. Otherwise, it returns an insertion point, which we can use to count an index for a new key: -(insertion point)-1. Moreover, the binarySearch method is the simplest and most efficient method to find an element in a sorted array in Java.

Let’s consider the following example. We have an input table with four elements ordered ascending. We would like to insert the number 3 into this table. Here’s how we can count the index of the insertion point.

int[] t = new int[] {1, 2, 4, 5};
int x = Arrays.binarySearch(t, 3);

assert ~x == 2;

7. Bit Set

What if we need to perform some operations on arrays of bits? You will use boolean[] for that? There is a more effective and memory-efficient method to achieve it. It is the BitSet class. The BitSet class allows us to store and manipulate arrays of bits. In comparison to boolean[] it consumes 8x less memory. We may perform logical operations on arrays such as e.g. and, or, xor.

Let’s say we have two input arrays of bits. We would like to perform the xor operation on them. To clarify, the operation that returns just those elements, which are inserted only to a single array, but not to another one. In order to do that we need to create two instances of BitSet and insert example elements there as shown below. Finally, you should invoke the xor method on one of the BitSet instances. It takes the second BitSet instance as the argument.

BitSet bs1 = new BitSet();
bs1.set(0);
bs1.set(2);
bs1.set(4);
System.out.println("bs1 : " + bs1);

BitSet bs2 = new BitSet();
bs2.set(1);
bs2.set(2);
bs2.set(3);
System.out.println("bs2 : " + bs2);

bs2.xor(bs1);
System.out.println("xor: " + bs2);

Here’s the result after running the code visible above.

8. Phaser

Finally, the last interesting Java feature in this article. The same as some other examples here, it is also the element of the Java Concurrent package. It is called Phaser. It is quite similar to the better-known CountDownLatch. However, it provides some additional features. It allows us to set the dynamic number of threads that need to wait before continuing execution. With Phaser the defined number of threads need to wait on the barrier before going to the next step of execution. Thanks to that we can coordinate multiple phases of execution.

In the following example, we set a barrier of 50 threads to arrive before going to the next phase of execution. Then we create a thread that invokes the method arriveAndAwaitAdvance() on the Phaser instance. It blocks the thread until all the 50 threads won’t arrive to the barrier. Then it goes to the phase-1 and also invokes the method arriveAndAwaitAdvance().

Phaser phaser = new Phaser(50);
Runnable r = () -> {
   System.out.println("phase-0");
   phaser.arriveAndAwaitAdvance();
   System.out.println("phase-1");
   phaser.arriveAndAwaitAdvance();
   System.out.println("phase-2");
   phaser.arriveAndDeregister();
};

ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
   executor.submit(r);
}

Here’s the result after executing the code visible above.

java-features-phaser

The post Useful & Unknown Java Features appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2022/01/05/useful-unknown-java-features/feed/ 14 10438