Monday, January 17, 2022

Java Tutorial: Generating Random Numbers

Chapters

Java Tutorial: Generating Random Numbers

In this tutorial, we're going to discuss different ways of generating random numbers in java.
Math.random() Method

Method Form: public static double random()
This method is located in java.lang.Math package. This method returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0. Returned values are chosen pseudorandomly with (approximately) uniform distribution from that range.

This example demonstrates Math.random().
public class SampleClass{

  public static void main(String[] args){
  
    double d = Math.random();
    String s = String.format("%.1f",d);
    System.out.println(s);
    
    //with multiplier
    d = Math.random() * 2;
    s = String.format("%.1f",d);
    System.out.println(s);
  }
}

Result(may vary)
0.6
1.5
java.util.Random Class

Instance of Random is used to generate a stream of pseudorandom numbers. If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate and return identical sequences of numbers. More information can be read in the documentation.

This example demonstrates the Random class.
import java.util.Random;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random();
  
    System.out.println("Ints");
    for(int i = 0; i < 3; i++)
      System.out.println(random.nextInt() + " ");
    System.out.println();
  
    System.out.println("Doubles");
    for(int i = 0; i < 3; i++)
      System.out.printf("%.2f%n",random.nextDouble());
    System.out.println();
    
    System.out.println("Guassian");
    for(int i = 0; i < 3; i++)
      System.out.printf("%.2f%n",random.nextGaussian());
  }
}

Result(may vary)
Ints
-612467063
1655395407
2107210140

Doubles
0.46
0.40
0.25

Gaussian
0.90
-2.14
0.77
In the example above, I used this constructor of Random: new Random(). This constructor will automatically generate seeds for us everytime we invoke methods that starts random computation like nextInt(). nextInt() returns a randomed integer number. All 2^32 possible int values are produced with (approximately) equal probability.

nextDouble() returns a uniformly distributed double value between 0.0 and 1.0 from this random number generator's sequence. nextGaussian() Returns a Gaussian(normal distribution) distributed double value with mean 0.0 and standard deviation 1.0 from this random number generator's sequence.

Next, this example demonstrates random with explicit seed.
import java.util.Random;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random(1);
    
    System.out.println
    ("Initial random: " + random.nextInt());
    for(int i = 1; i <= 5; i++){
      random.setSeed(i);
      System.out.println("Random: " + random.nextInt());
    }
      
  }
}

Result(may vary)
Initial random: -1155869325
Random: -1155869325
Random: -1154715079
Random: -1155099828
Random: -1157023572
Random: -1157408321
Initial random and the first random are equal because they have the same seed which is 1. Remember that the algorithm used in the Random class is a pseudorandom governed by a mathematical formula.

Note: You should be knowledgeable about Stream class to understand the methods that I'm gonna demonstrate next.

nextInt() method have second form where we can put a limit or bounds to the returned random value. Take a look at this example.
import java.util.Random;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random();
  
    System.out.println("set1");
    //random between 0-9
    for(int i = 0; i < 3; i++)
      System.out.println(random.nextInt(10) + " ");
      
    System.out.println("set2");
    //random between 1-10
    for(int i = 0; i < 3; i++)
      System.out.println((random.nextInt(10)+1) + " ");
      
   System.out.println("set3");
    //random between 0 to -10
    for(int i = 0; i < 3; i++)
      System.out.println((random.nextInt(11)+(-10)) + " ");
    
  }
}

Result(may vary)
Set1
3
8
1
Set2
8
1
10
Set3
-1
-10
-5
Remember that the number in the parameter is exclusive. It means that the given value is not included in the range. For example, random.nextInt(10) has a range from 0-9 not 0-10. Random class has methods that return a specific variants of Stream class. doubles(), ints() and longs() return DoubleStream, IntStream and LongStream respectively.

This example demonstrates ints() method.
import java.util.Random;
import java.util.Optional;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random();
  
    int num = 
    random
    .ints()
    .findFirst()
    .getAsInt();
    
    System.out.println(num);
  }
}

Result(may vary)
-1994793268
ints() method returns an effectively unlimited stream of pseudorandom int values. Use short-circuiting operations to stop infinite stream like the example above. findFirst() is a short-circuiting terminal operation. The example above is applicable to doubles() and longs() with few modifications.

Next, this example demonstrates ints(int randomNumberOrigin, int randomNumberBound).
import java.util.Random;
import java.util.Optional;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random();
  
    int num = 
    random
    .ints(0,11)
    .findAny()
    .getAsInt();
    
    System.out.println(num);
  }
}

Result(may vary)
5
Just like the example above, the returned stream here is infinite. randomNumberBound is exclusive. This means that the exact range is between randomNumberOrigin-(randomNumberBound-1). This example is applicable to doubles(double randomNumberOrigin, double randomNumberBound) and longs(long randomNumberOrigin, long randomNumberBound) with few modifications.

Next, this example demonstrates ints(long streamSize).
import java.util.Random;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random();
  
    random
    .ints(10)
    .filter((t) -> 
    {
      if(t % 2 == 0)
        return true;
      else return false;
    })
    .forEach(System.out::println);
    
  }
}

Result(may vary)
1162373228
-483124408
-2060536350
351896346
1394074158
Unlike in the two previous examples before this example above, the returned stream here has finite elements. Thus, we are not required to use short-circuiting operations. streamSize is the size of the returned stream. This example is applicable to doubles(long streamSize) and longs(long streamSize).

Next, this example demonstrates ints(long streamSize, int randomNumberOrigin, int randomNumberBound).
import java.util.Random;

public class SampleClass{
  
  public static void main(String[] args){
    Random random = new Random();
  
    random
    .ints(5, 20, 41)
    .forEach(System.out::println);
    
  }
}

Result(may vary)
27
21
28
33
28
Just like the example that preceded this example above, the returned stream here is finite. randomNumberBound is exclusive. This means that the exact range is between randomNumberOrigin-(randomNumberBound-1). ints(long streamSize, int randomNumberOrigin, int randomNumberBound) is a combination of ints(int randomNumberOrigin, int randomNumberBound) and ints(long streamSize).

This example is applicable to doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) and longs(long streamSize, long randomNumberOrigin, long randomNumberBound).

java.util.concurrent.ThreadLocalRandom Class

Note: Most method of this class is identical to the method of Random class. Better read the Random class topic first before reading this topic.

ThreadLocalRandom is a random number generator (with period 2^64) isolated to the current thread. Like the global Random generator used by the Math class, a ThreadLocalRandom is initialized with an internally generated seed that may not otherwise be modified. When applicable, use of ThreadLocalRandom rather than shared Random objects in concurrent programs will typically encounter much less overhead and contention.

Use of ThreadLocalRandom is particularly appropriate when multiple tasks (for example, each a ForkJoinTask) use random numbers in parallel in thread pools. In Random class, we can use the method next(int bits) to get an integer random number that also atomatically updates its seed. Although, contention likely occurs if multiple threads access next(int bits).

As the description above says, using ThreadLocalRandom rather than shared Random objects in concurrent programs will typically encounter much less overhead and contention.

Usages of this class should typically be of the form: ThreadLocalRandom.current().nextX(...) (where X is Int, Long, etc). When all usages are of this form, it is never possible to accidentally share a ThreadLocalRandom across multiple threads.

This example demonstrates ThreadLocalRandom.
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SampleClass{
  
  public static void main(String[] args){
    System.out.println
    (Thread.currentThread().getName() +
     " | " + ThreadLocalRandom.current().nextInt(10));
    
    ExecutorService es = Executors.newFixedThreadPool(4);
    
    for(int i = 0; i < 4; i++)
      es.execute(() -> 
      {
        System.out.println
        (Thread.currentThread().getName() +
        " | " + ThreadLocalRandom.current().nextInt(10));
      });
    
    es.shutdown();
  }
}

Result(may vary)
main | 7
pool-1-thread-1: 0
pool-1-thread-3: 9
pool-1-thread-2: 1
pool-1-thread-4: 7
current() method returns the current thread's ThreadLocalRandom object. Methods of this object should be called only by the current thread, not by other threads. As the description implies, every thread has a ThreadLocalRandom object.

java.util.SplittableRandom Class

SplittableRandom is a generator of uniform pseudorandom values (with period 2^64) applicable for use in (among other contexts) isolated parallel computations that may generate subtasks. This class can split random-generating task into multiple subtasks by using the split() method.

There are important pieces of information in the documentation that I didn't put here. You should read it. This example demonstrates SplittableRandom.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.SplittableRandom;

public class SampleClass{

  public static void main(String[] args){
    
    System.out.println("\nCreating "+
                       "ForkJoinPool...");
    ForkJoinPool fjp = ForkJoinPool.commonPool();
                       
    int result = fjp.invoke(new Randomize(new SplittableRandom()));
    System.out.println("Sum: " + result);
  }
}

class Randomize extends RecursiveTask<Integer>{

  private final SplittableRandom splitTask;
  private static final AtomicInteger ai =
  new AtomicInteger(0);
  
  Randomize(SplittableRandom splitTask){
    this.splitTask = splitTask;
  }
  
  protected Integer compute(){
    Integer result = 0;
    
    if(ai.incrementAndGet() < 3){
      Randomize s1 = new Randomize(splitTask.split());
      s1.fork();
      int rand = splitTask.ints(10, 20).findAny().getAsInt();
      Randomize s2 = new Randomize(splitTask);
      result = rand + s2.compute() + s1.join();
      System.out.println(Thread.
      currentThread().getName() + " | Partial Result | " +
      rand);
    }
    else{
      result = splitTask.ints(10, 20).findAny().getAsInt();
      System.out.println(Thread.
      currentThread().getName() + " | Partial Result | " +
      result);
    }
     
    return result;
  }
}

Result(may vary)
Creating ForkJoinPool...
FokJoinPool.commonPool-worker-1 | Partial Result | 17
FokJoinPool.commonPool-worker-2 | Partial Result | 15
main | Partial Result | 17
FokJoinPool.commonPool-worker-1 | Partial Result | 11
main | Partial Result | 14
Sum: 74
If you want to split a specific SplittableRandom, use this form of split:
split(RandomGenerator.SplittableGenerator source)
splits() is a new method added to java 17 that returns new pseudorandom number generators, each of which implements the RandomGenerator.SplittableGenerator interface. This method has four forms. Two of them returns an infinite stream of pseudorandom number generators. The remaining two returns a finite stream. You can read them in the documentation.

java.security.SecureRandom Class

Previous random generators are cryptographically weak. SecureRandom uses different types of algorithms that are cryptographically strong. If you need a secure random generator in an application like authenticator app, consider using SecureRandom.

This class provides a cryptographically strong random number generator (RNG). A cryptographically strong random number minimally complies with the statistical random number generator tests specified in FIPS 140-2, Security Requirements for Cryptographic Modules, section 4.9.1. Additionally, SecureRandom must produce non-deterministic output.

Therefore any seed material passed to a SecureRandom object must be unpredictable, and all SecureRandom output sequences must be cryptographically strong, as described in RFC 4086: Randomness Requirements for Security. SecureRandom objects are safe for use by multiple concurrent threads. More information can be read in the documentation.

This example demonstrates SecureRandom.
import java.security.SecureRandom;

public class SampleClass{
  
  public static void main(String[] args){
    SecureRandom random = new SecureRandom();
    
    System.out.println("Algorithm: " + 
    random.getAlgorithm());
    
    random
    .ints(5, 20, 41)
    .forEach(System.out::println);
    
  }
}

Result(may vary)
Algorithm: DRBG
27
26
22
31
22
In java 17, when SecureRandom() constructor is used, the default alrogithm is DRBG or Deterministic Random Big Generator. SecureRandom can use different types of algorithm which can be seen in this article.

One of the best usage of SecureRandom is to generate random hashes. This example demonstrates generating salt.. One of the usage of salt is to secure passwords.
import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class SampleClass{
  
  public static void main(String[] args)
                throws NoSuchAlgorithmException{
    SecureRandom random = 
    SecureRandom.getInstance("SHA1PRNG");
    
    System.out.println("Algorithm: " + 
    random.getAlgorithm());
    
    byte[] salt = new byte[16];
    random.nextBytes(salt);
    
    System.out.println("Salt");
    System.out.println(
    new BigInteger(1, salt).toString(16));
    
  }
}

Result(may vary)
Algorithm: SHA1PRNG
Salt
d66d227b210c...
Use getInstance(String algorithm) to use other algorithms. This method returns a SecureRandom object that implements the specified Random Number Generator (RNG) algorithm. getInstance() has many forms that can be seen in the documentation.

nextBytes(byte[] bytes)
generates random bytes based on the length of the byte array argument and put those generated random bytes in the array.

Saturday, January 15, 2022

Java Tutorial: DoubleStream, IntStream and LongStream

Chapters

DoubleStream, IntStream and LongStream

DoubleStream, IntStream and LongStream are variants of Stream class. They specialize in operating on primitive wrapper classes. In this tutorial, I'm gonna demonstrate some methods that are distinct from Stream class.

Lots of the methods of these classes have closely equivalent versions to Stream class methods like filter(), map(), flatMap() and many more. I won't explain these method because I already explained them in this article. You may wanna read that article first before reading this tutorial.

average() Method

This method returns the arithmetic mean of elements of this stream, or an empty optional if this stream is empty. This is a terminal operation. This method is present in DoubleStream, IntStream and LongStream.

This example demonstrate average() in DoubleStream that returns an OptionalDouble.
import java.util.stream.Stream;
import java.util.OptionalDouble;

public class SampleClass{

  public static void main(String[] args){
    Stream<Double> numbers =
    Stream.of(3.5,4.4,1.75,5.55);
    
    OptionalDouble avg = 
    numbers
    .mapToDouble((e) -> e)
    .average();
    
    if(avg.isPresent())
      System.out.println("Average: " + avg.getAsDouble());
    else
      System.out.println("Result is empty!");
    
  }
}

Result
Average: 3.8
boxed() Method

Returns a Stream consisting of the elements of this stream, each boxed to a specified type. This is an intermediate operation. This method is present in DoubleStream, IntStream and LongStream.

This example demonstrates boxed() method in LongStream that returns Stream<Long>.
import java.util.stream.Stream;
import java.util.stream.Collectors;

public class SampleClass{

  public static void main(String[] args){
    Stream<Long> numbers =
    Stream.of(3L,4L,1L,5L);
    
    Long result = 
    numbers
    .mapToLong((v) -> v*v)
    .boxed()
    .collect(
     Collectors
     .summingLong((v) -> v+v));
    
    System.out.println(result);
  }
}

Result
102
mapToObj() Method and its Variants

This method returns an object-valued Stream consisting of the results of applying the given function to the elements of this stream. This is an intermediate operation. mapToObj() is present in DoubleStream, IntStream and LongStream. Other variants like mapToInt(), mapToDouble() and others are present if necessary in DoubleStream, IntStream and LongStream.

For example, mapToLong() is present in DoubleStream and IntStream but not present in LongStream. Moreover, These variants are present in the Stream class.

This example demonstrates mapToObj().
import java.util.stream.Stream;
import java.util.stream.Collectors;

public class SampleClass{

  public static void main(String[] args){
    Stream<Long> numbers =
    Stream.of(3L,4L,1L,5L);
    
    Long result = 
    numbers
    .mapToLong((v) -> v*v)
    .mapToObj((v) -> v+2)
    .collect(
     Collectors
     .summingLong((v) -> v+v));
    
    System.out.println(result);
  }
}

Result
118
range() Method

Returns a sequential ordered IntStream or LongStream starting from the first parameter(inclusive) to second parameter(exclusive) by an incremental step of 1. This method is not present in DoubleStream.

This example demonstrates range() method that returns an IntStream.
import java.util.stream.IntStream;

public class SampleClass{

  public static void main(String[] args){
    
    IntStream.range(3,9)
    .boxed()
    .forEach((v) -> 
    System.out.print(v + " "));
    
  }
}

Result
3 4 5 6 7 8
rangeClosed() Method

Returns a sequential ordered IntStream or LongStream starting from the first parameter(inclusive) to second parameter(inclusive) by an incremental step of 1. This method is not present in DoubleStream.

This example demonstrates rangeClosed() method that returns an IntStream.
import java.util.stream.IntStream;

public class SampleClass{

  public static void main(String[] args){
    
    IntStream.rangeClosed(3,9)
    .boxed()
    .forEach((v) -> 
    System.out.print(v + " "));
    
  }
}

Result
3 4 5 6 7 8 9
sum() Method

Returns the sum of elements in this stream. This method is present in DoubleStream, IntStream and LongStream.

This example demonstrates sum() that returns a double-type value.
import java.util.stream.Stream;

public class SampleClass{

  public static void main(String[] args){
    Stream<Double> numbers = 
    Stream.of(3.5,4.5,5.5,2.5);
    
    double result = 
    numbers
    .mapToDouble(v -> v+v)
    .sum();
    
    System.out.println(result);
  }
}

Result
32.0
summaryStatistics() Method

Returns a statistics describing various summary data about the elements of this stream. This method is present in IntStream, DoubleStream and LongStream and returns IntSummaryStatistics, LongSummaryStatistics and DoubleSummaryStatistics respectively.

This example demonstrates summaryStatistics() that returns an IntSummaryStatistics.
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.IntSummaryStatistics;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> numbers = 
    Stream.of(3,4,5,2);
    
    IntSummaryStatistics result = 
    numbers
    .mapToInt(v -> v+v)
    .summaryStatistics();
    
    System.out.println(result);
  }
}

Result
IntSummaryStatistics
{count=4, sum=28, min=4, 
average=7.00000, max=10}

Friday, January 14, 2022

Java Tutorial: Collector Interface

Chapters

Collector Interface

Collector is a mutable reduction operation that accumulates input elements into a mutable result container, optionally transforming the accumulated result into a final representation after all input elements have been processed. Reduction operations can be performed either sequentially or in parallel.

The class Collectors provides implementations of many common mutable reductions. Aside from pre-built methods from Collectors class, we can create our own Collector method. Note that my explanation here is simplified. If you're planning to create your own Collector then, you need to read the documentation for full details.

This example demonstrates Collector Interface methods like of(), characteristics() and finisher().
import java.util.stream.Collector;

@SuppressWarnings({"unchecked"})
public class SampleClass{

  public static void main(String[] args){
    
    Collector c = 
    Collector.of(StringBuilder::new,
                 StringBuilder::append,
                 (l,r) -> l.append(r.toString()));
    
    System.out.println("Characteristics");
    System.out.println(c.characteristics());
    
    StringBuilder builder = null;
    Object cont = c.supplier().get();
    
    if(cont instanceof StringBuilder)
       builder = (StringBuilder)cont;
    else System.exit(1);
    
    c.accumulator().accept(cont, "String1-");
    c.accumulator().accept(cont, "String2-");
    c.accumulator().accept(cont, "String3");
    
    Object s = c.finisher().apply(builder);
    System.out.println(s);
    
  }
}

Result
Characteristics
[IDENTITY_FINISH]
String1-String2-String3
In the example above, I used this form of of() method:
static <T, R> Collector<T,R,R> of(Supplier<R> supplier, BiConsumer<R,T> accumulator, BinaryOperator<R> combiner, Collector.Characteristics... characteristics)

supplier supplies our method with the specified container like StringBuilder, ArrayList, etc.
accumulator accumulates contents then put them to a container.
combiner combines two containers if two containers are in use. In the example above I only used one container. Thus, I didn't invoke combiner() method.
characteristics is the characteristics of a Collector. There are three characteristics and you can read their description in the documentation.
IDENTITY_FINISH characteristic simply means that the Function denoted by finisher parameter behaves like Function.Identity().

In the example above, the form of of() doesn't have finisher parameter. Although, I could still invoke a Function when I called the finisher(). In this type of situation, java assigns IDENTITY_FINISH characteristic to our Collector to infrom us that when we call finisher(), it will behave like Function.Identity().

Next, let's create an example where the second form of of() is used.
import java.util.stream.Collector;

@SuppressWarnings({"unchecked"})
public class SampleClass{

  public static void main(String[] args){
    
    Collector c = 
    Collector.of(StringBuilder::new,
                 StringBuilder::append,
                 (l,r) -> l.append(r.toString()),
                 Object::toString);
    
    System.out.println("Characteristics");
    System.out.println(c.characteristics());
    
    StringBuilder builder1 = null;
    Object cont1 = c.supplier().get();
    
    StringBuilder builder2 = null;
    Object cont2 = c.supplier().get();
    
    if(cont1 instanceof StringBuilder)
       builder1 = (StringBuilder)cont1;
    else System.exit(1);
    
    if(cont2 instanceof StringBuilder)
       builder2 = (StringBuilder)cont2;
    else System.exit(1);
    
    c.accumulator().accept(cont1, "String1-");
    c.accumulator().accept(cont1, "String2-");
    c.accumulator().accept(cont2, "String3-");
    c.accumulator().accept(cont2, "String4");
    
    Object combined = c.combiner().apply(cont1,cont2);
    StringBuilder result = null;
    
    if(combined instanceof StringBuilder)
       result = (StringBuilder)combined;
    else System.exit(1);
    
    Object s = c.finisher().apply(result);
    
    if(s instanceof String)
      System.out.println(s + " is a String");
    
  }
}

Result
Characteristics
[]
String1-String2-String3-String4 is a String
Next, let's put our Collector method in the collect() method of Stream class.
import java.util.stream.Collector;
import java.util.HashSet;

@SuppressWarnings({"unchecked"})
public class SampleClass{

  public static void main(String[] args){
    HashSet<String> hs =
    new HashSet<>();
    
    hs.add("A");
    hs.add("B");
    hs.add("C");
    hs.add("D");
    
    Collector c = 
    Collector.of(StringBuilder::new,
                 (t,u) -> t.append("-"+u+"-"),
                 (l,r) -> l.append(r.toString()),
                 Object::toString,
                 Collector.Characteristics.UNORDERED);
    
    System.out.println("Characteristics");
    System.out.println(c.characteristics());
    
    Object result = hs.stream().collect(c);
    System.out.println(result);
  }
}

Result
Characteristics
[UNORDERED]
-A--B--C--D-
UNORDERED characteristic indicates that the collection operation does not commit to preserving the encounter order of input elements. (This might be true if the result container has no intrinsic order, such as a Set.)

Note that the examples here are unsafe that may cause exceptions regarding raw types and object typecasting. Make sure to carefully build your Collector method. In this type of situation, @SuppressWarnings({"unchecked"}) is necessary.

Wednesday, January 12, 2022

Java Tutorial: Collectors Class

Chapters

Collectors Class

Note: It's recommended to be knowledgeable about Stream Class before reading this article.

Collectors class that implement various useful reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, etc. Mostly all of the methods in this class return a Collector interface type. One of the forms of collect() method from Stream class accepts Collector type as argument.

My explanation here is simplified. Read the documentation for more information.

averagingInt() and its variants

Method Form: averagingInt(ToIntFunction<? super T> mapper)
averagingInt() Returns a Collector that produces the arithmetic mean of an integer-valued function applied to the input elements. If no elements are present, the result is 0.

This method has two other variants: averagingDouble() and averagingLong(). This methods return arithmetic mean of a double-valued and long-valued functions respectively.

This example demonstrate averagingInt().
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(6);
    ar.add(8);
    ar.add(10);
    
    Double avg =
    ar.stream()
      .collect(Collectors
        .averagingInt((n) -> n*n));
    
    //(2^2 + 4^2 + 6^2 + 8^2 + 10^2) / 5
    //= 44
    System.out.println("Result: "+avg);
    
  }
}

Result
Result: 44.0

collectingAndThen() Method

Method form:
public static <T, A, R, RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher)
Adapts a Collector to perform an additional finishing transformation.

This example demonstrates collectingAndThen().
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(6);
    ar.add(8);
    ar.add(10);
    
    Double avg =
    ar.stream()
      .collect(
       Collectors
       .collectingAndThen(Collectors
        .averagingInt((n) -> n*n),
       (nn) -> nn*nn);
    
    //(2^2 + 4^2 + 6^2 + 8^2 + 10^2) / 5
    //= 44
    //44^2 = 1936
    System.out.println("Result: "+avg);
    
  }
}

Result
Result: 1936.0
In the example above, averagingInt() is applied first. Then, the finisher is applied to the result of the downstream.

counting() Method

Method Form: public static <T> Collector<T,?,Long> counting()
Returns a Collector accepting elements of type T that counts the number of input elements. If no elements are present, the result is 0.

This example demonstrates counting().
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(6);
    ar.add(8);
    ar.add(10);
    
    Long count =
    ar.stream()
      .collect(Collectors.counting());
      
    System.out.println("Result: " + count);
  }
}

Result: 5

filtering() Method

Method Form: public static <T, A, R> Collector<T,?,R> filtering(Predicate<? super T> predicate, Collector<? super T,A,R> downstream)
Adapts a Collector to one accepting elements of the same type T by applying the predicate to each input element and only accumulating if the predicate returns true. More information can be read in the documentation.

This example demonstrates filtering().
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(6);
    ar.add(8);
    ar.add(10);
    
    Double avg =
    ar.stream()
      .collect(
       Collectors.filtering(
        (t) -> t <= 6,
        Collectors.averagingInt(
         (n) -> n*n)
       )
      );
    
    //2^2 + 4^2 + 6^2 / 3 
    //= 18.666666666666668
    System.out.println("Result: " + avg);
  }
}

flatMapping() Method

Method Form: public static <T, U, A, R> Collector<T,?,R> flatMapping(Function<? super T,? extends Stream<? extends U>> mapper, Collector<? super U,A,R> downstream)
Adapts a Collector accepting elements of type U to one accepting elements of type T by applying a flat mapping function to each input element before accumulation. The flat mapping function maps an input element to a stream covering zero or more output elements that are then accumulated downstream.

Each mapped stream is closed after its contents have been placed downstream. If a mapped stream is null an empty stream is used, instead. More information can be read in the documentation.

This example demonstrates flatMapping().
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.List;

public class SampleClass{

  public static void main(String[] args){
  
    Integer[][] ints = {{2,4,6},{3,7,9}};
    
    List<Integer> flatInts = 
    Arrays.stream(ints)
          .collect(
           Collectors.flatMapping(
           (e) -> Arrays.stream(e),
           Collectors.toList())
          );
    
    for(Object o : flatInts)
      System.out.print(o + " ");
    
  }
}

Result
2 4 6 3 7 9
groupingBy() Method

Returns a Collector implementing a "group by" operation on input elements of type T, grouping elements according to a classification function. This method has three forms and I'm gonna demonstrate them one-by-one.

This example demonstrate this form:
public static <T, K> Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier)
import java.util.stream.Collectors;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(7);
    ar.add(8);
    ar.add(11);
    
    Map<String,List<Integer>> map =
    ar.stream()
      .collect(Collectors.groupingBy(
        (t) -> {
          if(t % 2 == 0)
            return "even";
          else
            return "odd";
        }));
    
    System.out.println(map);
  }
}

Result
{even=[2, 4, 8], odd=[7, 11]}
Next, This example demonstrate this form:
public static <T, K, D, A, M extends Map<K, D>> Collector<T,?,M> groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)
import java.util.stream.Collectors;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(7);
    ar.add(8);
    ar.add(11);
  
    LinkedHashMap<String,Set<Integer>> map = 
    ar.stream()
      .collect(Collectors.groupingBy(
        (t) -> {
          if(t % 2 == 0)
            return "even";
          else
            return "odd";
        }, 
        LinkedHashMap::new,
        Collectors.toSet()));
    
    System.out.println(map);
    
  }
}

Result
{even=[2, 4, 8], odd=[7, 11]}
Next, This example demonstrate this form:
public static <T, K, A, D> Collector<T,?,Map<K,D>> groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)
import java.util.stream.Collectors;
import java.util.Map;
import java.util.ArrayList;
import java.util.function.Function;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("Banana");
    ar.add("Apple");
    ar.add("Carrot");
    ar.add("Banana");
    ar.add("Apple");
    ar.add("Apple");
    
    Map<String,Long> map =
    ar.stream()
      .collect(Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()
      ));
    
    System.out.println(map);
    
  }
}

Result
{Carrot=1, Apple=3, Banana=2}
Function.identity() returns a function that always returns its input argument. This is useful if you don't wanna modify input arguments.

groupingByConcurrent() is a variant of groupingBy() method. These two are the same. Thus, the examples above can be applied to groupingByConcurrent(). However, groupingByConcurrent() is more optimized for concurrency especially for a huge collection of elements.

This example demonstrate groupingByConcurrent().
import java.util.stream.Collectors;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(7);
    ar.add(8);
    ar.add(11);
    
    Map<String,List<Integer>> map =
    ar.parallelStream()
      .collect(Collectors.groupingByConcurrent(
        (t) -> {
          if(t % 2 == 0)
            return "even";
          else
            return "odd";
        }));
    
    System.out.println(map);
  }
}

Result(may vary)
{even=[8, 4, 2], odd=[7, 11]}
joining() Method

Returns a Collector that concatenates the input elements into a String, in encounter order. This method has three forms. I'll demonstrate them one-by-one.

This example demonstrates this form:
public static Collector<CharSequence,?,String> joining()
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("A");
    ar.add(" B");
    ar.add(" C");
    ar.add(" D");
    
    String str = 
    ar.stream()
      .collect(Collectors.joining());
    
    System.out.println("Result: " + str);
  }
}

Result
Result: A B C D
Next, This example demonstrates this form:
public static Collector<CharSequence,?,String> joining(CharSequence delimiter)
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("A");
    ar.add("B");
    ar.add("C");
    ar.add("D");
    
    String str = 
    ar.stream()
      .collect(Collectors.joining("-"));
    
    System.out.println("Result: " + str);
  }
}

Result
Result: A-B-C-D
Next, this example demonstrates this form:
public static Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
import java.util.stream.Collectors;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("A");
    ar.add("B");
    ar.add("C");
    ar.add("D");
    
    String str = 
    ar.stream()
      .collect(Collectors.joining("-","^","*"));
    
    System.out.println("Result: " + str);
  }
}

Result
Result: ^A-B-C-D*
mapping() Method

Method form: public static <T, U, A, R> Collector<T,?,R> mapping(Function<? super T,? extends U> mapper, Collector<? super U,A,R> downstream)
Adapts a Collector accepting elements of type U to one accepting elements of type T by applying a mapping function to each input element before accumulation. More information can be read in the documentation.

This example demonstrates mapping().
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SampleClass{

  public static void main(String[] args){
    
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(7);
    ar.add(8);
    ar.add(11);
    
    Map<String, List<Integer>> map = 
    ar.stream()
      .collect(
       Collectors.groupingBy(
       (t) -> {
          if(t % 2 == 0)
            return "even";
          else
            return "odd";
        },
        Collectors.mapping(
          (t) -> t*t,
          Collectors.toList()
        )
      ));
    
    System.out.println(map);
  }
}

Result
{even=[4, 16, 64], odd=[49, 121]}
maxBy() Method

Method Form: public static <T> Collector<T,?,Optional<T>*gt; maxBy(Comparator<? super T> comparator)
Returns a Collector that produces the maximal element according to a given Comparator, described as an Optional<T>. More information can be read in the documentation.

This example demonstrates maxBy().
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.Optional;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("Joker");
    ar.add("queen");
    ar.add("jack");
    ar.add("King");
    
    Optional<String> opt =
    ar.stream().collect(
    Collectors.maxBy(String.CASE_INSENSITIVE_ORDER));
    
    if(opt.isPresent())
      System.out.println("max(last) index: " + opt.get());
    else
      System.out.println("Empty result!");
  }
}

Result
max(last) index: queen
minBy() Method

Method Form: public static <T> Collector<T,?,Optional<T>> minBy(Comparator<? super T> comparator)
Returns a Collector that produces the minimal element according to a given Comparator, described as an Optional<T>. More information can be read in the documentation.
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.Optional;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("Joker");
    ar.add("queen");
    ar.add("jack");
    ar.add("King");
    
    Optional<String> opt =
    ar.stream().collect(
    Collectors.minBy(String.CASE_INSENSITIVE_ORDER));
    
    if(opt.isPresent())
      System.out.println("min(first) index: " + opt.get());
    else
      System.out.println("Empty result!");
  }
}

Result
min(first) index: jack
partitioningBy() Method

returns a Collector which partitions the input elements according to a Predicate, and organizes them into a Map. The returned Map always contains mappings for both false and true keys. There are no guarantees on the type, mutability, serializability, or thread-safety of the Map or List returned.

This method is similar to groupBy() method. This method has two forms. I'm gonna demonstrate them one-by-one.

This example demonstrates this form:
public static <T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)
import java.util.stream.Collectors;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(2);
    ar.add(4);
    ar.add(7);
    ar.add(8);
    ar.add(11);
    
    Map<Boolean,List<Integer>> map =
    ar.stream()
      .collect(Collectors.partitioningBy(
        (t) -> {
          if(t % 2 == 0)
            return true;
          else
            return false;
        }));
    
    System.out.println(map);
  }
}

Result
{false=[7, 11], true=[2, 4, 8]}
Next, this example demonstrates this form:
public static <T, D, A> Collector<T,?,Map<Boolean,D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream)
import java.util.stream.Collectors;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("Banana");
    ar.add("Pork");
    ar.add("Bacon");
    ar.add("Quasar");
    ar.add("Bamboo");
    
    Map<Boolean,Set<String>> map =
    ar.stream()
      .collect(Collectors.partitioningBy(
        (t) -> t.startsWith("B"),
        Collectors.toSet()));
    
    System.out.println(map);
  }
}

Result
{false=[Quasar, Pork], true=[Bacon, Bamboo, Banana]}
As you can see, partinioningBy() is closely similar to groupingBy(). Although, in my opinion, It's better to use partinioningBy() if we want to separate elements only into two sections. For separating elements into multiple sections, use groupingBy().

reducing() Method

Method Form: public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op)
Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator. The result is described as an Optional<T>. This method has three forms. I'm gonna demonstrate them one-by-one.

This example demonstrates this form:
public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op)
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.Optional;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> ints = Stream.of(2,4,6);
    
    Optional<Integer> opt =
    ints.collect(
    Collectors.reducing((t,u) -> (t*t)+(u*u)));
    
    if(opt.isPresent())
      //(2*2 + 4*4)*(2*2 + 4*4) + 6*6
      //(20*20) + 36
      //400 + 36
      //=436
      System.out.println("SquaredThenAdd: " + opt.get());
    else
      System.out.println("Empty result!");
  }
}

Result:
SquaredThenAdd: 436
Next, this example demonstrates this form:
public static <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> op)
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> ints = Stream.of(4,6);
    
    Integer result =
    ints.collect(
    Collectors.reducing(2, (t,u) -> (t*t)+(u*u)));
    
    //(2*2 + 4*4)*(2*2 + 4*4) + 6*6
    //(20*20) + 36
    //400 + 36
    //=436
    System.out.println("SquaredThenAdd: " + result);
   
  }
}

Result:
SquaredThenAdd: 436
identity parameter denotes initial value.
Next, this example demonstrates this form:
public static <T, U> Collector<T,?,U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> ints = Stream.of(2,4,6);
    
    Integer result =
    ints.collect(
    Collectors
     .reducing(0,
      (t) -> t*t,
      (t,u) -> t+u));
    
    //2*2 + 4*4 + 6*6 = 56
    System.out.println("SquaredThenAdd: " + result);
   
  }
}

Result:
SquaredThenAdd: 56
summarizingInt() and its Variants

Method Form: public static <T> Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)

Returns a Collector which applies an int-producing mapping function to each input element, and returns summary statistics for the resulting values. summarizingInt() returns IntSummaryStatistics. Other variants like summarizingDouble() and summarizingLong() return DoubleSummaryStatistics and LongSummaryStatistics respectively.

This example demonstrates summarizingInt().
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.IntSummaryStatistics;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> ints = Stream.of(2,4,6);
    
    IntSummaryStatistics iss = 
    ints.collect(
    Collectors
    .summarizingInt((t) -> t*t));
    
    System.out.println("Records");
    System.out.println(iss + "\n");

  }
}

Result
Records
IntSummaryStatistics
{count=3, sum=56, min=4, average=18.666667, max=36}
summingInt() and its Variants

Method Form: public static <T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper)

Returns a Collector that produces the sum of a numerical-valued function applied to the input elements. If no elements are present, the result is 0. summingInt() returns Integer. Other variants like summingDouble() and summingLong() return Double and Long respectively.

This example demonstrates summingInt().
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> ints = Stream.of(2,4,6);
    
    Integer result = 
    ints.collect(
    Collectors
    .summingInt((t) -> t*t));
    
    System.out.println("Value: " + result);
  }
}

Result
Value: 56
teeing() Method

Method Form: public static <T, R1, R2, R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger)

Returns a Collector that is a composite of two downstream collectors. Every element passed to the resulting collector is processed by both downstream collectors, then their results are merged using the specified merge function into the final result.

More information can be read in the documentation.

This example demonstrates teeing().
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SampleClass{

  public static void main(String[] args){
    Stream<Integer> ints = Stream.of(2,4,6);
    
    Integer result = 
    ints.collect(
    Collectors
    .teeing(
     Collectors.summingInt((t) -> t*t),
     Collectors.summingInt((t) -> t+t),
     (t,u) -> t+u));
    
    //(2*2 + 4*4 + 6*6) + (2+2 + 4+4 + 6+6)
    //= 56 + 24 = 80
    System.out.println("Value: " + result);
  }
}
toMap() Method and other Variants

Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements. More information can be read in the documentation.

This method has three forms. I'm gonna demonstrate them one-by-one. Also, examples here can be applied to other variants like toConcurrentMap() and toUnmodifiableMap().

This example demonstrates this form:
public static <T, K, U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.Map;

public class SampleClass{

  public static void main(String[] args){
    String[][] strings = 
    new String[][]{{"key1","val1"},
                   {"key2","val2"},
                   {"key3","val3"}};
    
    Map<String,String> map =
    Arrays.stream(strings).collect(
    Collectors
     .toMap((t) -> t[0],
            (t) -> t[1]));
    
    System.out.println(map);
  }
}

Result
{key1=val1, key2=val2, key3=val3}
Next, this example demonstrates this form:
public static <T, K, U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.Map;

public class SampleClass{

  public static void main(String[] args){
    String[][] strings = 
    new String[][]{{"key1","val1"},
                   {"key2","val2"},
                   {"key1","val3"}};
    
    Map<String,String> map =
    Arrays.stream(strings).collect(
    Collectors
     .toMap((t) -> t[0],
            (t) -> t[1],
            (t,u) -> t+"-"+u));
    
    System.out.println(map);
  }
}

Result
{key1=val1-val3, key2=val2}
mergeFunction can merge the values of two duplicate keys. In the example above, val1 and val3 had been merged as one value of key1 with "-" delimiter.

Next, this example demonstrates this form:
public static <T, K, U, M extends Map&tl;K, U>> Collector<T,?,M> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapFactory)
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.TreeMap;

public class SampleClass{

  public static void main(String[] args){
    String[][] strings = 
    new String[][]{{"key1","val1"},
                   {"key3","val4"},
                   {"key2","val2"},
                   {"key1","val3"}};
    
    TreeMap<String,String> map =
    Arrays.stream(strings).collect(
    Collectors
     .toMap((t) -> t[0],
            (t) -> t[1],
            (t,u) -> t+"-"+u,
            TreeMap::new));
    
    System.out.println(map);
  }
}

Result
{key1=val1-val3, key2=val2, key3=val4}
This example is not applicable to toUnmodifiableMap() because this form of toMap() doesn't match(closely match) any form of toUnmodifiableMap().

Monday, January 10, 2022

Java Tutorial: Executors Class

Chapters

Executors Class

Note: It's recommended to be knowledgeable about java.util.concurrent Package before reading this article.

Executors class contains Factory and utility methods for Executor, ExecutorService, ScheduledExecutorService, ThreadFactory, and Callable classes defined in this package. This class supports the following kinds of methods:
  • Methods that create and return an ExecutorService set up with commonly useful configuration settings.
  • Methods that create and return a ScheduledExecutorService set up with commonly useful configuration settings.
  • Methods that create and return a "wrapped" ExecutorService, that disables reconfiguration by making implementation-specific methods inaccessible.
  • Methods that create and return a ThreadFactory that sets newly created threads to a known state.
  • Methods that create and return a Callable out of other closure-like forms, so they can be used in execution methods requiring Callable.
I'm gonna demonstrate some methods in this class. My explanation here is simplified, more information can be found in the documentation.

newSingleThreadExecutor() Method

Creates an Executor that uses a single worker thread operating off an unbounded queue. Tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time. Unlike the otherwise equivalent newFixedThreadPool(1) the returned executor is guaranteed not to be reconfigurable to use additional threads.

Note however that if this single thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.

This example demonstrates newSingleThreadExecutor().
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class SampleClass{

  public static void main(String[] args){
    ExecutorService es =
    Executors.newSingleThreadExecutor();
    
    es.execute(() -> System.out.println("Task #1"));
    es.execute(() -> System.out.println("Task #2"));
    es.execute(() -> System.out.println("Task #3"));
    es.execute(() -> System.out.println("Task #4"));
    es.shutdown();
  }
}

Result
Task #1
Task #2
Task #3
Task #4

callable() Method

callable() returns a Callable object. This method has four forms. However, I'll only demonstrate two of them. I'll demonstrate these two:
callable(Runnable task)
callable(Runnable task, T result)
I won't demonstrate these two:
callable(PrivilegedAction<?> action)
callable(PrivilegedExceptionAction<?> action)
PrivilegedAction and PrivilegedExceptionAction are connected to AccessController which is deprecated in java 17 and will be removed in future version of java.

This example demonstrates callable(Runnable task).
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    ExecutorService es =
    Executors.newSingleThreadExecutor();
    
    Future<?> future = 
    es.submit(Executors.callable(
    () -> System.out.println("callable task!")));
    es.shutdown();
    
    //used to block main thread
    future.get();
    
    System.out.println("Exiting main thread.");
  }
}
Typically, we use get() to get result. Although, in some situation, we may wanna only use get() to block a thread especially for executors that don't accept Runnable. callable() method executes its Runnable and returns null.

Next, this example demonstrates callable(Runnable task, T result)
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
     CompletableFuture<String> cf =
     CompletableFuture.supplyAsync(() -> "Test");
     
     ExecutorService es =
     Executors.newSingleThreadExecutor();
     
     Future<String> future = 
     es.submit(
     Executors.callable(() -> 
     System.out.println("Executing Future..."),
     cf.get()));
     es.shutdown();
     
     System.out.println("Result: " + future.get());
  }
}

Executing Future...
Result: Test
If you're not knowledgeable about CompletableFuture, you may wanna read my blogpost about CompletableFuture.

defaultThreadFactory() Method

Returns a default thread factory used to create new threads. This factory creates all new threads used by an Executor in the same ThreadGroup. If there is a SecurityManager, it uses the group of System.getSecurityManager(), else the group of the thread invoking this defaultThreadFactory method.

Each new thread is created as a non-daemon thread with priority set to the smaller of Thread.NORM_PRIORITY and the maximum priority permitted in the thread group. New threads have names accessible via Thread.getName() of pool-N-thread-M, where N is the sequence number of this factory, and M is the sequence number of the thread created by this factory.
import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;

public class SampleClass{

  public static void main(String[] args){
    
    CustomExecutor ce1 = new CustomExecutor();
    
    CustomExecutor ce2 = 
    new CustomExecutor(
    Executors.defaultThreadFactory());
    
    ce1.execute(() ->
    System.out.println(
    "ce1: "+Thread.currentThread().getName()));
    
    ce2.execute(() ->
    System.out.println(
    "ce2: "+Thread.currentThread().getName()));
  }
}

class CustomExecutor implements Executor{
  private ThreadFactory tFactory;
  
  CustomExecutor(){}
  
  CustomExecutor(ThreadFactory tFactory){
    this.tFactory = tFactory;
  }
  
  @Override
  public void execute(Runnable command){
    if(tFactory == null)
      new Thread(command).start();
    else
      tFactory.newThread(command).start();
  }
}

Result(may vary)
ce2: pool-1-Thread-1
ce1: Thread-0

newCachedThreadPool() Method

Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available.

If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources.

If you want to create your own custom cached thread pool, consider using ThreadPoolExecutor. Although, this method is enough for most situations.

This method has two forms. I'll only demonstrate this form:
public static ExecutorService newCachedThreadPool()

This example demonsrates newCachedThreadPool().
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException{
  
    ExecutorService es =
    Executors.newCachedThreadPool();
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #1"));
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #2"));
    
    Thread.sleep(50);
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #3"));
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #4"));
    
    es.shutdown();
  }
}

Result(may vary)
pool-1-Thread-1: task #1
pool-1-Thread-2: task #2
pool-1-Thread-2: task #3
pool-1-Thread-1: task #4

newFixedThreadPool() Method

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most nThreads threads will be active processing tasks. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available.

If any thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks. The threads in the pool will exist until it is explicitly shutdown.

This method has two forms. I'll only demonstrate this form:
public static ExecutorService newFixedThreadPool(int nThreads)

This example demonstrates newFixedThreadPool(int nThreads).
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException{
                     
    ExecutorService es =
    Executors.newFixedThreadPool(4);
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #1"));
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #2"));
    
    Thread.sleep(200);
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #3"));
    
    es.execute(() ->
    System.out.println(
    Thread.currentThread()
    .getName()+": task #4"));
    
    es.shutdown();
  }
}

Result(may vary)
pool-1-Thread-1: task #1
pool-1-Thread-2: task #2
pool-1-Thread-3: task #3
pool-1-Thread-4: task #4

newSingleThreadScheduledExecutor() Method

Creates a single-threaded executor that can schedule commands to run after a given delay, or to execute periodically. Tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time. Unlike the otherwise equivalent newScheduledThreadPool(1) the returned executor is guaranteed not to be reconfigurable to use additional threads.

Note however that if this single thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.

This method has two forms. I'll only demonstrate this form:
public static ScheduledExecutorService newSingleThreadScheduledExecutor()

This example demonstrates newSingleThreadScheduledExecutor().
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class SampleClass{

  public static void main(String[] args){
  
    ScheduledExecutorService ses = 
    Executors.newSingleThreadScheduledExecutor();
    
    Runnable command = () -> 
    System.out.println("Do command...");
    
    ScheduledFuture<?> task = 
    ses.scheduleWithFixedDelay(command, 2,
                      2, TimeUnit.SECONDS);
                      
    Runnable canceller = () ->
    task.cancel(false);
    
    //This method doesn't block the main thread
    ses.schedule(canceller, 12, TimeUnit.SECONDS);
    
    while(true){
      if(task.isCancelled()){
        ses.shutdown();
        break;
      }
    }
    
  }
}

Result
Do command...
Do command...
Do command...
Do command...
Do command...
This example is explained in this article. In that article, newScheduledThreadPool(1) is used. Although, in this example, newScheduledThreadPool(1) and newSingleThreadScheduledExecutor() are interchangeable.

newScheduledThreadPool() Method

Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically. This method has two forms. I'll only demonstrate this form:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

This example demonstrates newScheduledThreadPool(int corePoolSize).
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class SampleClass{

  public static void main(String[] args){
  
    ScheduledExecutorService ses = 
    Executors.newScheduledThreadPool(2);
    
    Runnable command = () -> 
    System.out.println(Thread.currentThread().getName());
    
    ScheduledFuture<?> task1 = 
    ses.scheduleWithFixedDelay(command, 2,
                      2, TimeUnit.SECONDS);
                      
    ScheduledFuture<?> task2 = 
    ses.scheduleWithFixedDelay(command, 2,
                      2, TimeUnit.SECONDS);
                      
    Runnable canceller = () -> {
    task1.cancel(false);
    task2.cancel(false);
    };
    
    //This method doesn't block the main thread
    ses.schedule(canceller, 6, TimeUnit.SECONDS);
    
    while(true){
      if(task1.isCancelled() &&
         task2.isCancelled()){
        ses.shutdown();
        break;
      }
    }
    
  }
}

Result(may vary)
pool-1-Thread-2
pool-1-Thread-1
pool-1-Thread-2
pool-1-Thread-1
I explained scheduleWithFixedDelay method in this article.

newWorkStealingPool() Method

Creates a work-stealing thread pool using the number of available processors as its target parallelism level.

This method has two forms. I'll only demonstrate this form:
public static ExecutorService newWorkStealingPool()

This example demonstrates newWorkStealingPool().
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class SampleClass{

  public static void main(String[] args){
  
    ExecutorService es =
    Executors.newWorkStealingPool();
    
    int procNum = Runtime.getRuntime()
                         .availableProcessors();
                         
    System.out.print("This Machine's "+
                       "Processor Count: ");
    System.out.print(procNum + "\n");
    
    es.execute(() -> {
    
      System.out.println
      (Thread.currentThread().getName()+
      " | Task #1");
      for(int i = 0; i < 1000; i++){}
    });
    
    es.execute(() -> {
    
      System.out.println
      (Thread.currentThread().getName()+
      " | Task #2");
      for(int i = 0; i < 10000; i++){}
    });
    
    es.execute(() -> {
    
      System.out.println
      (Thread.currentThread().getName()+
      " | Task #3");
      for(int i = 0; i < 100000; i++){}
    });
    
    es.execute(() -> {
    
      System.out.println
      (Thread.currentThread().getName()+
      " | Task #4");
      for(int i = 0; i < 1000000; i++){}
    });
    
    es.execute(() -> {
    
      System.out.println
      (Thread.currentThread().getName()+
      " | Task #5");
      for(int i = 0; i < 10000000; i++){}
    });
    es.shutdown();
    
    while(!es.isTerminated());
  }
}

Result(may vary)
This Machine's Processor Count: 4
ForkJoinPool-1-worker-1 | Task #1
ForkJoinPool-1-worker-1 | Task #3
ForkJoinPool-1-worker-2 | Task #2
ForkJoinPool-1-worker-4 | Task #5
ForkJoinPool-1-worker-3 | Task #4

unconfigurableExecutorService() Method

Returns an object that delegates all defined ExecutorService methods to the given executor, but not any other methods that might otherwise be accessible using casts. This provides a way to safely "freeze" configuration and disallow tuning of a given concrete implementation.

This method has two forms. I'll only demonstrate this form:
public static ExecutorService unconfigurableExecutorService(ExecutorService executor)

This example demonstrates unconfigurableExecutorService().
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;

public class SampleClass{

  public static void main(String[] args){
  
    ExecutorService es = 
    Executors.unconfigurableExecutorService(
    Executors.newFixedThreadPool(1));
    
    ThreadPoolExecutor tpe = null;
    if(es instanceof ThreadPoolExecutor)
      tpe = (ThreadPoolExecutor)es;
    
    if(tpe != null){
      tpe.setMaximumPoolSize(3);
      tpe.setCorePoolSize(3);
    }
    else System.out.println("tpe is null!");
    
    es.execute(() -> 
    System.out.println(
    Thread.currentThread().getName()));
    
    es.execute(() -> 
    System.out.println(
    Thread.currentThread().getName()));
    
    es.execute(() -> 
    System.out.println(
    Thread.currentThread().getName()));
    
    es.shutdown();
  }
}

Result
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
In the example above, the executor "es" is still using a single thread even I set the core pool size to 3. Now, this next example demonstrates configurable executor.
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;

public class SampleClass{

  public static void main(String[] args){
  
    ExecutorService es = Executors.newFixedThreadPool(1);
    
    ThreadPoolExecutor tpe = null;
    if(es instanceof ThreadPoolExecutor)
      tpe = (ThreadPoolExecutor)es;
    
    if(tpe != null){
      tpe.setMaximumPoolSize(3);
      tpe.setCorePoolSize(3);
    }
    else System.out.println("tpe is null!");
    
    es.execute(() -> 
    System.out.println(
    Thread.currentThread().getName()));
    
    es.execute(() -> 
    System.out.println(
    Thread.currentThread().getName()));
    
    es.execute(() -> 
    System.out.println(
    Thread.currentThread().getName()));
    
    es.shutdown();
  }
}

Result
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
Note the executors that are returned by newSingleThreadExecutor() and newSingleThreadScheduledExecutor() are unconfigurable by default.

Saturday, January 8, 2022

Java Tutorial: CompletableFuture Class

Chapters

Java Tutorial: CompletableFuture Class

Note: It's recommended to be knowledgeable about java.util.concurrent Package before reading this article.

CompletableFuture is closely similar to Future. Although, CompletableFuture can complete task by explicitly setting its value and status. Thus, making this Future a CompletableFuture. Also, CompletableFuture implements CompletionStage and Future. Thus, CompletableFuture can se used as Future and CompletionStage.

CompletionStage is a stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages. My explanation here is simplified. Take a look at the documentation for more information.

I will only demonstrate some useful methods of CompletableFuture in this tutorial. CompletableFuture has a lot of methods and you should read the CompletableFuture documentation to know those methods.

This example demonstrates CompletableFuture run by a single thread.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletableFuture<String> cf =
    new CompletableFuture<String>();
    
    cf.complete("Hello World!");
    System.out.println(cf.get());
  }
}

Result: Hello World!
complete() method explicitly completes CompletableFuture with the specified value. get() waits, if necessary, until CompletableFuture is complete and returns the specified value. Most of the time, CompletableFuture is used for asynchronous tasks.

runAsync() Method

Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() after it runs the given action.

This method has two forms. In this example I'm gonna demonstrate this form:
public static CompletableFuture<Void> runAsync(Runnable runnable)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletableFuture<Void> cf =
    CompletableFuture.runAsync(() -> 
    System.out.println(Thread.currentThread().getName()));
    
    cf.get();
    System.out.println("CompletableFuture is complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result
ForkJoinPool.commonPool-worker-1
CompletableFuture is complete.
Main Thread is unblocked.
runAsync() returns a Completed CompletableFuture<Void> with null value.
The Void class is an uninstantiable placeholder class to hold a reference to the Class object representing the Java keyword void. If we want a variable that only accepts null value then, we can use this class like this: Void v = null;
We can also use Void class as a method return type
static Void meth(){
  return null;
}
Next, this example demonstrates this form of runAsync():
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    ExecutorService es = Executors.newSingleThreadExecutor();
    
    CompletableFuture<Void> cf =
    CompletableFuture.runAsync(() -> 
    System.out.println(Thread.currentThread().getName()),es);
    
    cf.get();
    es.shutdown();
    System.out.println("CompletableFuture is complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result
pool-1-Thread-1
CompletableFuture is complete.
Main Thread is unblocked.
We can use the runAsync() form above to pass a custom executor to runAsync() method instead of using ForkJoinPool.commonPool().

supplyAsync() Method

Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() with the value obtained by calling the given Supplier.

supplyAsync() is similar to runAsync(). However, supplyAsync() has a return type that can be null or non-null whereas runAsync() only returns null.

This method has two forms. In this example I'm gonna demonstrate this form:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletableFuture<String> cf =
    CompletableFuture.supplyAsync(() -> 
    Thread.currentThread().getName());
    
    System.out.println("Return value: "+cf.get());
    System.out.println("CompletableFuture is complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result
Return value: ForkJoinPool.commonPool-worker-1
CompletableFuture is complete.
Main Thread is unblocked.
If you don't wanna use ForkJoinPool.commonPool() as your executor, you can use the second form of this method:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

allOf() Method

Method form: public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
Returns a new CompletableFuture that is completed when all of the given CompletableFutures complete. If any of the given CompletableFutures complete exceptionally, then the returned CompletableFuture also does so, with a CompletionException holding this exception as its cause.

Otherwise, the results, if any, of the given CompletableFutures are not reflected in the returned CompletableFuture, but may be obtained by inspecting them individually. If no CompletableFutures are provided, returns a CompletableFuture completed with the value null.

This example demonstrates allOf().
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletableFuture<Void> cf1 =
    CompletableFuture.runAsync(() -> 
    System.out.println(Thread.currentThread().getName()));
    
    CompletableFuture<Void> cf2 =
    CompletableFuture.runAsync(() -> 
    System.out.println(Thread.currentThread().getName()));
    
    CompletableFuture.allOf(cf1, cf2);
    
    System.out.println("CompletableFutures are complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result(may vary)
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-2
CompletableFutures are complete.
Main Thread is unblocked.
Next, this example demonstrates allOf() completed exceptionally.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException{
    CompletableFuture<Void> cf1 =
    CompletableFuture.runAsync(() -> 
    System.out.println(Thread.currentThread().getName()));
    
    CompletableFuture<Void> cf2 =
    CompletableFuture.runAsync(() -> {
    throw new NullPointerException("Null");
    });
    
    CompletableFuture<Void> cf3 = 
    CompletableFuture.allOf(cf1, cf2);
    
    //allOf() may unblock waiting threads without thoroughly
    //checking the complete status of all CompletableFutures
    //in its argument. Thus, in this example,
    //cf3.isCompletedExceptionally() may return false
    //To prevent that from happening, we may check if cf3
    //is completely done with its task by using isDone() method
    //
    //while(!cf3.isDone());
    
    System.out.println("is completed exceptionally?: " +
    cf3.isCompletedExceptionally());
    
    System.out.println("CompletableFutures are complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result(may vary)
ForkJoinPool.commonPool-worker-1
is completed exceptionally: true
CompletableFutures are complete.
Main Thread is unblocked.

anyOf() Method

Method form: public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
Returns a new CompletableFuture that is completed when any of the given CompletableFutures complete, with the same result. Otherwise, if it completed exceptionally, the returned CompletableFuture also does so, with a CompletionException holding this exception as its cause. If no CompletableFutures are provided, returns an incomplete CompletableFuture.

This example demonstrates anyOf().
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  static volatile String str = "Thread name: ";
  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletableFuture<String> cf1 =
    CompletableFuture.supplyAsync(() -> 
    str + Thread.currentThread().getName());
    
    CompletableFuture<String> cf2 =
    CompletableFuture.supplyAsync(() -> 
    str + Thread.currentThread().getName());
    
    CompletableFuture<Object> cf3 =
    CompletableFuture.anyOf(cf1, cf2);
    
    System.out.println("Return value\n"+cf3.get());
    System.out.println("cf3 is complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result(may vary)
Return value
Thread name: ForkJoinPool.commonPool-worker-1
cf3 is complete.
Main thread is unblocked.
Next, this example demonstrates allOf() that may be completed exceptionally.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  static volatile String str = "Thread name: ";
  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletableFuture<Void> cf1 =
    CompletableFuture.supplyAsync(() -> {
    throw new NullPointerException("null");
    });
    
    CompletableFuture<String> cf2 =
    CompletableFuture.supplyAsync(() -> 
    str + Thread.currentThread().getName());
    
    CompletableFuture<Object> cf3 =
    CompletableFuture.anyOf(cf1, cf2);
    
    if(cf3.isCompletedExceptionally())
      System.out.println("Exceptionally Completed.");
    else
      System.out.println("Return value\n"+cf3.get());
    
    System.out.println("cf3 is complete.");
    System.out.println("Main Thread is unblocked.");
  }
}

Result(may vary)
Exceptionally Completed.
cf3 is complete.
Main Thread is unblocked.

Chaining CompletionStage Methods

CompletableFuture derives CompletionStage methods. These chained methods are called stages.

This example demonstrates Chaining CompletionStage Methods.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

public class SampleClass{

  public static void main(String[] args)
                     throws InterruptedException,
                            ExecutionException{
    CompletionStage<String> cs =
    CompletableFuture.completedStage
    (Thread.currentThread().getName()).
    thenApply((x) -> x.concat(" | cf1")).
    thenCombine(CompletableFuture.completedStage("Test"),
    (t,u) -> t.concat(" | " + u));
    
    CompletableFuture<String> cf =
    cs.toCompletableFuture();
    
    System.out.println("Return value: " + cf.get());
  }
}

Result
Return value: main | cf1 | Test
First off, completedStage() returns a new CompletionStage that is already completed with the given value and supports only those methods in interface CompletionStage.

thenApply() applies the function in its argument to the completed CompletionStage instance that calls this method. thenCombine() combines the completed CompletionStage in its first argument with the completed CompletionStage that calls this method. toCompletableFuture() converts CompletableStage to CompletableFuture. Once CompletionStage is converted to CompletableFuture, we can use the get() method to get the result.

If we want to handle exception during method chaining, we can use exceptionally(), handle() and whenComplete() methods.

This example demonstrates handle(), exceptionally() and whenComplete() methods.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

public class SampleClass{

  public static void main(String[] args){
    CompletionStage<String> cs =
    CompletableFuture.completedStage
    (Thread.currentThread().getName())
    .handle((t,u) -> t.concat(null))
    .exceptionally((t) -> {
      throw new RuntimeException
      ("failed concatenation",t);
    })
    .whenComplete((t,u) -> {
    
      try{
        if(u == null && t != null){
          System.out.println("Concatenated value");
          System.out.println(t);
        }
        
        if(u != null)
          throw new RuntimeException(u);
      }
      catch(Exception e){
        System.out.println(e.getCause());
      }
      
    });
    
  }
}

Result
java.util.CompletionException: 
java.lang.RuntimeException:
failed concatenation
In the example above, CompletionException which is a subclass of RuntimeException is propagated to whenComplete() by wrapping it in RuntimeException. First off, handle() method executes its BiFunction in its argument if the completed CompletionStage that calls this method is completed normally or exceptionally. completed CompletionStage result and exception are the arguments in the supplied function.

exceptionally() executes its Function in its argument if the Completed CompletionStage that calls this method is completed exceptionally. Otherwise, its Function won't be exectured.completed CompletionStage result and exception are the arguments in the supplied function.

whenComplete() executes its BiConsumer in its argument if the completed CompletionStage that calls this method is completed normally or exceptionally. completed CompletionStage result and exception are the arguments in the supplied function.

We just handled one exception in the example above. Multiple exceptions may occur if we construct a more complex method chaining. You need to read the CompletionStage documentation
if you're planning to handle multiple exceptions while chaining CompletionStage methods.

If we want multiple threads to chain methods at the same time, we need to use those methods "asynchronous" versions. Their asynchronous versions have a suffix "async" in their names.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;

public class SampleClass{
  
  private static AtomicInteger num =
  new AtomicInteger(3);
  public static void main(String[] args){
    
    System.out.println("Initial value: " + num.get());
    
    CompletionStage<Void> cs1 =
    CompletableFuture.runAsync(() -> 
    {num.incrementAndGet();})
    .minimalCompletionStage()
    .thenRunAsync(() -> 
    {num.addAndGet(-3);});
    
    CompletionStage<Void> cs2 =
    CompletableFuture.runAsync(() -> 
    {num.addAndGet(2);})
    .minimalCompletionStage()
    .runAfterBothAsync(cs1, () -> 
    {num.addAndGet(5);});
    
    CompletableFuture.allOf(cs1.toCompletableFuture(),
                            cs2.toCompletableFuture());
    
    System.out.println("Current value: " + num.get());
  }
}

Result
Initial value: 3
Current value: 8
minimalCompletionStage() returns a CompletionStage with the same value as this CompletableFuture and cannot be independently completed or otherwise used in ways not defined by the methods of interface CompletionStage.

thenRunAsync(Runnable action) returns a new CompletionStage that, when this stage completes normally, executes the given action using this stage's default asynchronous execution facility.

runAfterBothAsync(CompletionStage<?> other, Runnable action) returns a new CompletionStage that, when this and the other given stage both complete normally, executes the given action using this stage's default asynchronous execution facility.

defaultExecutor() Method

Returns the default Executor used for async methods that do not specify an Executor. This class uses the ForkJoinPool.commonPool() if it supports more than one parallel thread, or else an Executor using one thread per async task. This method may be overridden in subclasses to return an Executor that provides at least one independent thread.

This example demonstrates defaultExecutor().
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ForkJoinPool;

public class SampleClass{

  public static void main(String[] args){
    CompletableFuture<Void> cf = 
    CompletableFuture.completedFuture(null);
    
    if(cf.defaultExecutor() instanceof ForkJoinPool)
      System.out.println
      ("Default executor is an instance of ForkJoinPool.");
    else
      System.out.println
      ("Default executor is not an instance of ForkJoinPool.");
  }
}
Result(if your system supports more than one parallel thread)
Default executor is an instance of ForkJoinPool.

delayedExecutor() Method

Returns a new Executor that submits a task to the default executor after the given delay (or no delay if non-positive). Each delay commences upon invocation of the returned executor's execute method. This method has two forms, in this example I'm gonna demonstrate this form:
public static Executor delayedExecutor(long delay, TimeUnit unit)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;

public class SampleClass{
  
  private static volatile boolean isdone = false;
  public static void main(String[] args){
  
    System.out.println("Initial value: " + isdone);
    CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS)
    .execute(() -> {isdone = true;});
    
    while(!isdone);
    
    System.out.println("Current value: " + isdone);
  }
}