Monday, September 28, 2020

Testing with Hoverfly and Java Part 3: State

Oracle Java Tutorial and Material, Oracle Java Exam Prep, Oracle Java Guides

Previously we simulated a delay scenario using Hoverfly. Now it’s time to dive deeper and go for a state-based testing. By doing a stateful simulation we can change the way the tests endpoints behave based on how the state changed.

Hoverfly does have a state capability. State in a hoverfly simulation is like a map. Initially it is empty but you can define how it will get populated per request.

Our strategy would be to have a request that initializes the state and then specifies other requests that change that state.

public class SimulationStateTests {

    private Hoverfly hoverfly;

    @BeforeEach

    void setUp() {

        var simulation = SimulationSource.dsl(service("http://localhost:8085")

                .get("/initialize")

                .willReturn(success("{\"initialized\":true}", "application/json")

                        .andSetState("shouldSucceed", "true")

                )

                .get("/state")

                .withState("shouldSucceed", "false")

                .willReturn(serverError().andSetState("shouldSucceed", "true"))

                .get("/state")

                .withState("shouldSucceed", "true")

                .willReturn(success("{\"username\":\"test-user\"}", "application/json")

                        .andSetState("shouldSucceed", "false"))

        );

        var localConfig = HoverflyConfig.localConfigs().disableTlsVerification().asWebServer().proxyPort(8085);

        hoverfly = new Hoverfly(localConfig, SIMULATE);

        hoverfly.start();

        hoverfly.simulate(simulation);

    }

    @AfterEach

    void tearDown() {

        hoverfly.close();

    }

}

Unfortunately on the state we can only specify values in a key value fashion and not by passing a function for a key.

However with the right workaround many scenarios could be simulated.

In the example we first initialize the state and the we issue requests that behave differently based on the state, but also they do change the state.

Oracle Java Tutorial and Material, Oracle Java Exam Prep, Oracle Java Guides

So we expect to have a continuous first succeed and then fail mode, which can be depicted in the following test.

@Test

    void testWithState() {

        var client = HttpClient.newHttpClient();

        var initializationRequest = HttpRequest.newBuilder()

                .uri(URI.create("http://localhost:8085/initialize"))

                .build();

        var initializationResponse = client.sendAsync(initializationRequest, HttpResponse.BodyHandlers.ofString())

                .thenApply(HttpResponse::body)

                .join();

        Assertions.assertEquals("{\"initialized\":true}", initializationResponse);

        var statefulRequest = HttpRequest.newBuilder()

                .uri(URI.create("http://localhost:8085/state"))

                .build();

        for (int i = 0; i < 100; i++) {

            var response = client.sendAsync(statefulRequest, HttpResponse.BodyHandlers.ofString())

                    .join();

            int statusCode = i % 2 == 0 ? 200 : 500;

            Assertions.assertEquals(statusCode, response.statusCode());

        }

    }

Wednesday, September 23, 2020

Difference between Core Java and Advanced Java

Core Java, Advanced Java, Oracle Java Learning

Core Java: Core Java is the part of Java programming language that is used for creating or developing a general-purpose application.

Advanced Java: Advanced Java is also a part of Java programming language that generally deals with online application like the website and mobile application.

Below is the difference between the two:

CORE JAVA ADVANCED JAVA 
To develop general purpose applications. To develop online application and mobile application.
Without Core Java no one can develop any advanced java applications.  Where as advanced java only deals with some specialization like Database, DOM(web), networking etc. 
OOP, data types, operators, functions, loops, exception handling, threading etc.  Apart from the core java parts it has some specific sections like database connectivity, web services, servlets etc. 
It uses only one tier architecture that is why it is called as ‘stand alone’ application.  It uses two tier architecture i.e. client side architecture and server side or backend architecture. 
Core java programming covers the swings, socket, awt, thread concept, collection object and classess.  Advance java is used for web based application and enterprise application. 

Monday, September 21, 2020

Adding Partition To Existing Table In Oracle

Learn How To Add Partition To Existing Table In Oracle. New changes to partition in Oracle 12c. partitioned table using the ALTER TABLE … MODIFY command in 3 ways. 1) Basic offline operation 2) Online operation 3) Online operation with modification of index partitioning

How do I alter my existing table to create a range partition in Oracle

Creating partition is on a table is very important when you want to see performance improvement especially on DATE columns. In this post, We will learn how to alter existing table with partition.

What is Partition in Oracle:

Oracle Partitioning allows tables and indexes to be partitioned into smaller, more manageable units, providing database administrators with the ability to pursue a “divide and conquer” approach to data management. … That table could be range- partitioned so that each partition contains one day of data.

Oracle Java Study Materials, Oracle Database Learning, Java Exam Prep

Creating Partitioning using range partition:

CREATE TABLE table_name

   (column_name DATE)

 PARTITION BY RANGE (column_name)

(PARTITION partition_name VALUES LESS THAN (value));

Example:

CREATE TABLE STUDENT
  (
    STUDENT_ID   NUMBER(22,0) NOT NULL,
    STUDENT_NAME VARCHAR2(64) NOT NULL,
    STUDENT_JOINING_DATE DATE NOT NULL
  )
  PARTITION BY RANGE
  (
    STUDENT_JOINING_DATE
  )
  (
    PARTITION JOINING_20190701 VALUES LESS THAN (TO_DATE('2019-07-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20200101 VALUES LESS THAN (TO_DATE('2020-01-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20200701 VALUES LESS THAN (TO_DATE('2020-07-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20210101 VALUES LESS THAN (TO_DATE('2021-01-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20210701 VALUES LESS THAN (TO_DATE('2021-07-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20220101 VALUES LESS THAN (TO_DATE('2022-01-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20220701 VALUES LESS THAN (TO_DATE('2022-07-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20230101 VALUES LESS THAN (TO_DATE('2023-01-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20230701 VALUES LESS THAN (TO_DATE('2023-07-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20240101 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20240701 VALUES LESS THAN (TO_DATE('2024-07-01', 'YYYY-MM-DD')),
    PARTITION JOINING_20250101 VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD')),
    PARTITION JOINING_MAX VALUES LESS THAN (MAXVALUE)
  );

Now this new table is deployed in production enviornment and it is there in prod for 1 year. This table has now10 million records in it. Later now, we need to add a partition on STUDENT_JOINING_DATE column. We will see now how to handle this case.

Is it possible to add a Partion on existing table:


Oracle Java Study Materials, Oracle Database Learning, Java Exam Prep

Well, one way or another, you’ll have to recreate the table — there is quite simply NO WAY AROUND that. Think about it — you have an existing table full of data. Rows with jan are next to feb are next to mar. The entire table needs to be rewritten.

This is not possible in Oracle database.

We should do the following.

1) export the table

2) create a new empty table that has the partition definition on it

3) import the table with IGNORE=Y

You must take care of following.

here you would need recreate

1) index

2) grant

3) add constraints

4) add triggers

5) add EVERYTHING you need to make

Conversion of a Non-Partitioned Table to a Partitioned Table in Oracle:


We will do the following steps to create a partition on existing table in oracle.

1) Drop table if exists

2) Create table

3) Creating index on partition column

4) Inserting 10000 records into our existing table which is created in step 2

Create and populate a test table. You will need to repeat this between each test.

Drop table if exists:

DROP TABLE Emp ;

Creating table:

CREATE TABLE Emp (
  id           NUMBER,
  name  VARCHAR2(50),
  join_date DATE,
  CONSTRAINT emp_pk PRIMARY KEY (id)
);

Creating index:

CREATE INDEX emp_join_date_idx ON t1(join_date);

Inserting Data:

Here this script inserts 10000 records into Emp table.

INSERT INTO Emp
SELECT level,
       'Name for ' || level,
       ADD_MONTHS(TO_DATE('01-JAN-2019', 'DD-MON-YYYY'), -TRUNC(DBMS_RANDOM.value(1,4)-1)*12)
FROM   dual
CONNECT BY level <= 10000;
COMMIT;

See the data based on the year:

We can see the data is spread across three years.

SELECT join_date, COUNT(*)
FROM   Emp
GROUP BY join_date
ORDER BY 1;
 
Output:
 
join_date   COUNT(*)
--------- ----------
01-JAN-15       3340
01-JAN-16       3290
01-JAN-17       3370

Partition an existing Table:


We can convert the table to a partitioned table using the ALTER TABLE … MODIFY command. Here are some basic examples of this operation. Adding the ONLINE keyword allows the operation to be completed online.

This can be done in 3 ways.

1) Basic offline operation
2) Online operation
3) Online operation with modification of index partitioning

1) Basic offline operation:

ALTER TABLE Emp MODIFY
  PARTITION BY RANGE (join_date) (
    PARTITION Emp_part_2015 VALUES LESS THAN (TO_DATE('01-JAN-2016','DD-MON-YYYY')),
    PARTITION Emp_part_2016 VALUES LESS THAN (TO_DATE('01-JAN-2017','DD-MON-YYYY')),
    PARTITION Emp_part_2017 VALUES LESS THAN (TO_DATE('01-JAN-2018','DD-MON-YYYY'))
  );

2) Online operation

ALTER TABLE Emp MODIFY
  PARTITION BY RANGE (join_date) (
    PARTITION Emp_part_2015 VALUES LESS THAN (TO_DATE('01-JAN-2016','DD-MON-YYYY')),
    PARTITION Emp_part_2016 VALUES LESS THAN (TO_DATE('01-JAN-2017','DD-MON-YYYY')),
    PARTITION Emp_part_2017 VALUES LESS THAN (TO_DATE('01-JAN-2018','DD-MON-YYYY'))
  ) ONLINE;

3) Online operation with modification of index partitioning

ALTER TABLE Emp MODIFY
  PARTITION BY RANGE (join_date) (
    PARTITION Emp_part_2015 VALUES LESS THAN (TO_DATE('01-JAN-2016','DD-MON-YYYY')),
    PARTITION Emp_part_2016 VALUES LESS THAN (TO_DATE('01-JAN-2017','DD-MON-YYYY')),
    PARTITION Emp_part_2017 VALUES LESS THAN (TO_DATE('01-JAN-2018','DD-MON-YYYY'))
  ) ONLINE
  UPDATE INDEXES
  (
    Emp_pk GLOBAL,
    Emp_join_date_idx LOCAL

Friday, September 18, 2020

Testing with Hoverfly and Java Part 2: Delays

Oracle Java Tutorial and Material, Oracle Java Learning, Oracle Java Exam Prep

On the previous post we implemented json and Java based Hoverfly scenarios..

Now it’s time to dive deeper and use other Ηoverfly features.

A big part of testing has to do with negative scenarios. One of them is delays. Although we always mock a server and we are successful to reproduce erroneous scenarios one thing that is key to simulate in todays microservices driven world is delay.

Oracle Java Tutorial and Material, Oracle Java Learning, Oracle Java Exam Prep

So let me make a server with a 30 secs delay.

public class SimulateDelayTests {

    private Hoverfly hoverfly;

    @BeforeEach

    void setUp() {

        var simulation = SimulationSource.dsl(service("http://localhost:8085")

                .get("/delay")

                .willReturn(success("{\"username\":\"test-user\"}", "application/json").withDelay(30, TimeUnit.SECONDS)));

        var localConfig = HoverflyConfig.localConfigs().disableTlsVerification().asWebServer().proxyPort(8085);

        hoverfly = new Hoverfly(localConfig, SIMULATE);

        hoverfly.start();

        hoverfly.simulate(simulation);

    }

    @AfterEach

    void tearDown() {

        hoverfly.close();

    }

}

Let’s add the Delay Test

@Test

void testWithDelay() {

   var client = HttpClient.newHttpClient();

   var request = HttpRequest.newBuilder()

         .uri(URI.create("http://localhost:8085/delay"))

         .build();

   var start = Instant.now();

   var res = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())

         .thenApply(HttpResponse::body)

         .join();

   var end = Instant.now();

   Assertions.assertEquals("{\"username\":\"test-user\"}", res);

   var seconds = Duration.between(start, end).getSeconds();

   Assertions.assertTrue(seconds >= 30);

}

Delay simulation is there, up and running, so let’s try to simulate timeouts.

@Test

    void testTimeout() {

        var client = HttpClient.newHttpClient();

        var request = HttpRequest.newBuilder()

                .uri(URI.create("http://localhost:8085/delay"))

                .timeout(Duration.ofSeconds(10))

                .build();

        assertThrows(HttpTimeoutException.class, () -> {

                    try {

                        client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();

                    } catch (CompletionException ex) {

                        throw ex.getCause();

                    }

                }

        );

    }

Wednesday, September 16, 2020

Introduction to Hypermedia REST APIs

Oracle Java Hypermedia REST APIs, Oracle Java Tutorial and Materials, Oracle Java Exam Prep, Oracle Java Guides

Introduction


When browsing the web we typically navigate from one site to another by following Hyperlinks. Those links make the web for humans discoverable.

Hypermedia APIs provide the same discoverability for services. According to Roy Fielding Hypermedia is an essential part of a REST API and the Richardson REST Maturity Model describes Hypermedia as the final step to the glory of REST. So, Hypermedia seems to be quite an important thing for REST. However, in my experience, Hypermedia is used very rarely. This is a bit sad because Hypermedia provides some nice features to APIs.

Resource representations with links


Let's start with a simple example without Hypermedia. Assume we want to create a resource that provides information about an order. It might look like this:

GET /orders/123

{
    "buyer_id": 456,
    "order_date": "2020-15-08T09:30:00",
    "total_price": 4.99,
    "payment_date": null,
    "status": "open",
    "items": [
        {
            "product_id" : 789,
            "quantity": 1,
            "price": 4.99
        }
    ]
}

Note that the fields buyer_id and product_id are references to other resources. If the client wants to get more information about the buyer, it has to construct a new request URI like this:

String buyerUrl = "/customer/" + order.getBuyerId();

Here the client has to know the exact URI format of related resources. This is similar to surfing the web without using hyperlinks. Instead of clicking on links we have to manually update the browser request line for every sub page we want to visit.

To add Hypermedia support to our order representation, we have to replace IDs with links to related resources.

For example:

{
    "buyer_url": "/customers/456",
    "order_date": "2020-15-08T09:30:00",
    "total_price": 4.99,
    "payment_date": null,
    "status": "open",
    "items": [
        {
            "product_url" : "/products/789",
            "quantity": 5,
            "price": 4.99
        }
    ]
}

We now created links between related resources. A client does no longer have to care about IDs and URI construction. To get buyer information the client just has to send a GET request to the value of buyer_url.

Hypermedia response formats typically group links together in a separate JSON object. It is also a good idea to use a JSON object to represent a link. This gives us the option to add more information to links later.

If we apply this to our order representation it might look like this:

{
    "order_date": "2020-15-08T09:30:00",
    "total_price": 4.99,
    "payment_date": null,
    "status": "open",
    "items": [
        {
            "quantity": 5,
            "price": 4.99,
            "links" : [
                { "rel": "product", "href": "/products/789" }
            ]
        }
    ],
    "links" : [
        { "rel": "buyer", "href": "/customers/456" }
    ]
}

With the rel field we describe the type of the resource relation while href contains the actual link (more on this later).

State Transitions (HATEOAS)


So far we only used links to indicate relations to other resources. Links can also be used to indicate possible actions on a resource. For example, orders can be paid and cancelled. We can use links to point to these operations:

{
    "order_date": "2020-15-08T09:30:00",
    "total_price": 4.99,
    "status": "open",
    "payment_date": null,
    "items": [ ... ],
    "links" : [
        { "rel": "buyer", "href": "/customers/456" },
        { "rel": "payment", "href": "/orders/123/payment" },
        { "rel": "cancellation", "href": "/orders/123/cancellation" }
    ]
}

In order to cancel an order we can now simply send a PUT request to the cancellation link. After cancelling the order, the resource representation might look like this:

{
    "order_date": "2020-15-08T09:30:00",
    "total_price": 4.99,
    "status": "cancelled",
    "payment_date": null,
    "items": [ ... ],
    "links" : [
        { "rel": "buyer", "href": "/customers/456" },
    ]
}

Note that the order status has changed and the links for cancellation and payment are gone. Of course a cancelled order cannot be cancelled again and paying for a cancelled order makes no sense. So links do not just point to actions, they also tell us which actions are possible in the current resource status.

This is called Hypermedia as the Engine of Application State (HATEOAS). HATEOAS can transform a REST API into a state machine over HTTP.

More on Links


We used quite a few links so far. So, it's a good point to look into a few details.

The link attributes rel and href come from the attributes of the <a> tag that is used in HTML to represent links. A common set of link relations (like first, next, previous, etc.) has been standardized by IANA. You can find those relations on the IANA website. It is a good idea to take a look at this list before you come up with your own new rel type.

It is also a good practice to include a link to the current resource, named self. For example:

GET /orders/123

{
    ...
    "links" : [
        { "rel": "self", "href": "/orders/123" },
        ...
    ]
}

Links might not always point to exact resources. It is also possible to create links that contain placeholders or optional parameters. For example, the order list might contain a search-by-status link that contains a status request parameter:

GET /orders

{
    ...
    "links" : [
        { "rel": "self", "href": "/orders" },
        { "rel": "search-by-status", "href": "/orders{?status}" },
        ...
    ]
}

Clients can use that link to filter the order list by a specific order status. For example, this might be a valid request:

GET /orders?status=open

These templates are called URI Templates (defined in RFC 6570). The RFC is a good source for more information.

Links are also an important part of your API documentation. Instead of documenting exact resource URIs you should document possible link relations for your resources. The client needs to know what a specific links does and how it should be used (HTTP method, request body if required, etc.)

The API entry point


If clients do not know any resource URIs they need some entry point for an initial request. This initial entry point then provides links to accessible resources. An API entry point for our example API might look like this:

GET /

{
    "version": "1.2.3",
    "description": "Example API to manage orders",
    "links": [
        { "rel": "orders", "href": "/orders" },
        { "rel": "customers", "href": "/customers"},
        { "rel": "customer-by-id", "href": "/customer/{id}"},
        { "rel": "customer-by-email", "href": "/customer{?email}"},
        ...
    ]
}

With URI templates we can make sure clients do not need to browse through large collections in order to find a needed resource.

Hypermedia response formats


So far we just added links elements to our JSON representation. However, it can be a good idea to look at some common Hypermedia response formats before building a Hypermedia REST API. Unfortunately there is no single standard format. Instead, we can choose from a lot of different formats.

Here are some examples:

◉ HAL (Hypertext Application Language)
◉ JSON LD (JSON for Linking Data)
◉ Collection+JSON
◉ Siren
◉ JSON Hyper Schema

I would recommend looking at HAL first. HAL is quite simple and one of the formats that is widely supported by libraries. Besides standard REST clients you can use tools like HAL explorer to interact with APIs that use HAL.

Why is this useful and what are the downsides?


Introducing Hypermedia to REST APIs comes with a lot of benefits. It reduces coupling between the server and clients. Servers are able to refactor and evolve their URI structure without breaking clients. Clients no longer need to construct request URIs.

It also reduces the logic required on the client. Let's recap the previous example with the order that can be cancelled or paid. However, this time without links:

{
    "order_date": "2020-15-08T09:30:00",
    "total_price": 4.99,
    "status": "open",
    "payment_date": null,
    "items": [ ... ],
}

How does the client decide if it is possible to cancel or pay this order? Maybe an order can be cancelled as long as it is in open state? And it is possible to pay an order as long as it is in open state and payment_date is null?

This logic is already present on the server and can be communicated with HATEOAS. So instead of duplicating logic the client has just to check if a specific link is present. For example: If the cancellation link is present, it is possible to cancel the order and therefore the Cancel order button should be shown in the user interface.

The same approach works great for communicating allowed operations. The server already contains the logic to decide what a user is allowed to do based on his permissions/roles. So, if a user has no permission to cancel an order, don't add a cancellation link.

Those points are all great, but what are the downsides?

Adding links for resource relations and state transitions can be a significant effort on the server side. You have to construct links, list possible state transitions and check if the client has the permissions use them. This effort is only useful if clients actually make use of the Hypermedia elements provided by the API and do not use hardcoded URIs.

Using Hypermedia can also significantly increase the response size.

Monday, September 14, 2020

REST: Retrieving resources

Oracle Java Exam Prep, Oracle Java Tutorial and Materials, Oracle Java Certification

Retrieving resources is probably the simplest REST API operation. It is implemented by sending a GET request to an appropriate resource URI. Note that GET is a safe HTTP method, so a GET request is not allowed to change resource state. The response format is determined by Content-Negotiation.

Retrieving collection resources


Collections are retrieved by sending a GET request to a resource collection.

For example, a GET request to /paintings might return a collection of painting resources:

Request:

GET /paintings
Accept: application/json

Response:

HTTP/1.1 200 (Ok)
Content-Type: application/json
 
[
    {
        "id": 1,
        "name": "Mona Lisa"
    }, {
        "id": 2
        "name": "The Starry Night"
    }
]

The server indicates a successful response using the HTTP 200 status code.

Note that it can be a good idea to use a JSON object instead of an array as root element. This allows additional collection information and Hypermedia links besides actual collection items.

Example response:

HTTP/1.1 200 (Ok)
Content-Type: application/json
 
{
    "total": 2,
    "lastUpdated": "2020-01-15T10:30:00",
    "items": [
        {
            "id": 1,
            "name": "Mona Lisa"
        }, {
            "id": 2
            "name": "The Starry Night"
        }
    ],
    "_links": [
        { "rel": "self", "href": "/paintings" }
    ]
}

If the collection is empty the server should respond with HTTP 200 and an empty collection (instead of returning an error).

For example:

HTTP/1.1 200 (Ok)
Content-Type: application/json
 
{
    "total": 0,
    "lastUpdated": "2020-01-15T10:30:00",
    "items": [],
    "_links": [
        { "rel": "self", "href": "/paintings" }
    ]
}

Resource collections are often top level resources without an id (like /products or /paintings) but can also be sub-resources. For example, /artists/42/paintings might represent the collection of painting resources for the artist with id 42.

Retrieving single resources


Single resources retrieved in the same way as collections. If the resource is part of a collection it is typically identified by the collection URI plus the resource id.

For example, a GET request to /paintings/1 might return the painting with id 1:

Request:

GET /paintings/1
Accept: application/json

Response:

HTTP/1.1 200 (Ok)
Content-Type: application/json
 
{
    "id": 1,
    "name": "Mona Lisa",
    "artist": "Leonardo da Vinci"
}

If no resource for the given id is available, HTTP 404 (Not found) should be returned.

Friday, September 11, 2020

Keeping the Caller Busy

There are ways we could refactor the displayCart function, but consider a second what the caller is doing each time it wants to use the function.

Consider this simple code example:

// caller
String shoppingCartMessage = displayCart("You have %s", cart.size());

String otherMessage = displayCart("You saved %s", saved.size());

// common function
String displayCart(String template, int size) {
   if (size == 0) {
      return String.format(template, "no items");
   }
   return String.format(template, size > 1 ? "a total of " + size + " items" :
     " an item");
}

Each call requires the caller to calculate the size of the list that it’s holding. For two examples, that’s fine. If the caller has a variety of different sources of data, then it’s also fine.

Oracle Java Tutorial and Material, Oracle Java Certification, Oracle Java Prep

But if it’s always the same source data, then getting each call site to do a wee calculation is not less efficient in terms of processing, but it’s less ideomatic and it’s more typing.

Given the object of the displayCart function is to describe a particular shopping cart, why is it not working out what data it needs from the cart itself?

This becomes more of a problem if the caller has to calculate several attributes of the source data in order to use a function on those data.

Conversely, this alternative:

String displayCart(String template, List<Item> cart) {
   int size = cart.size();
   ...
}

… is only a reasonable thing to do if the common function:

◉ is really about the source data

◉ can be coupled with the types involved

◉ is suitably broken down so it doesn’t become a monolith with too many responsibilities

The way to identify this smell is a gang of callers all busily calculating the same inputs from the same types of source data.

Wednesday, September 9, 2020

Java 8 Base64 Encoding and Decoding (With Examples)

Oracle Java Exam Prep, Oracle Java Tutorial and Material, Oracle Java Study Material

Learn different ways to do base64 encoding and decoding functionality in java and using java 8 api, and also examples using the apache commons-codec.

1. Overview


In this article, you’ll learn the different ways to do the base 64 encoding and decoding techniques in java 8 and other alternative libraries such as apache common API utility.

Understand the techniques on how to encode and decode base64 in java.

2. Java 8 – Base64 API


After a long time, java 8 is added with the builtin base64 encoding and decoding functionalities as part of JDK 8.

This simplifies the encode and decode process without using the other third-party libraries.

In java 8, We can use mainly 3 types of encoding and decoding capabilities as standard. All of the classes are related to this are in java.util.base64 package.

2.1 Base or Simple Type

2.2 URL Encoding/Decoding

2.3 MIME Encoding/Decoding

Let us explore all of these ones.

3. Java 8 Base64 Methods


Base64 is a base class for encoding and decoding. It has two static inner classes and those are Base64.Encoder, Base64.Decoder.

Use the below methods to get the right encoders and decoders for each type.

3.1 static Base64.Decoder getDecoder()

Returns a Base64.Decoder that decodes using the Basic type base64 encoding scheme.

3.2 static Base64.Encoder getEncoder()

Returns a Base64.Encoder that encodes using the Basic type base64 encoding scheme.

3.3 static Base64.Decoder getMimeDecoder()

Returns a Base64.Decoder that decodes using the MIME type base64 decoding scheme.

3.4 static Base64.Encoder getMimeEncoder()

Returns a Base64.Encoder that encodes using the MIME type base64 encoding scheme.

3.5 static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)

Returns a Base64.Encoder that encodes using the MIME type base64 encoding scheme with specified line length and line separators.

3.6 static Base64.Decoder getUrlDecoder()

Returns a Base64.Decoder that decodes using the URL and Filename safe type base64 encoding scheme.

3.7 static Base64.Encoder getUrlEncoder()

Returns a Base64.Encoder that encodes using the URL and Filename safe type base64 encoding scheme.

4. Java 8 Base 64 Simple Encoding and Decoding


This is a simple encoding technique in the Base64.

The encoded string will contain only “A-Za-z0-9+/” these characters set and considers the new line character as line breaker in decoding. Let us see the example you will understand clearly.

package com.oraclejavacertified.blogspot.java8.base64;

import java.util.Base64;

public class SImpleBase64Example {

    public static void main(String[] args) {

        String originalString = "Welcome to oraclejavacertified.blogspot.com \n good day";

        // create base simple encoder object
        Base64.Encoder simpleEncoder = Base64.getEncoder();

        // Encoding string using simple encode
        String encodedString = simpleEncoder.encodeToString(originalString.getBytes());
        System.out.println("Encoded string : "+encodedString);
       
        // Create base simple decoder  object
        Base64.Decoder simpleDecoder = Base64.getDecoder();
       
        // Deconding the encoded string using decoder
        String decodedString = new String(simpleDecoder.decode(encodedString.getBytes()));
        System.out.println("Decoded String : "+decodedString);
    }
}

Output:

Encoded string : V2VsY29tZSB0byBqYXZhcHJvZ3JhbXRvLmNvbSAKIGdvb2QgZGF5
Decoded String : Welcome to oraclejavacertified.blogspot.com
 good day

As you see in the output, the actual input string is having a line separator. For encoding used. encodeToString() method and it is having the characters from simple encoding characters set.

But, when decoded using decode() method that returns byte[] rather than a string. You need to explicitly cast to the String object.

Finally, the decoded string is showing the content in the next line when the line separator encounters.

5. Java 8 Base64 Encoding and Decoding With Padding


By default, encoding pads with the ‘=’ double equal operator if the encoded string length is not met the desired length.

Typically, an encoded string should be multiples of 3 otherwise it will be added with = character.

On the other side, while decoding all extra padded characters will be discarded.

If you do want to be decoded then encode without padding using withoutPadding().

withoutPadding() method helps to skip the padding of the output.

Many developers think that no padding encoded string cannot be decoded back to the original string.

But, it is wrong and Base64.Decode api provides flexibility to decode back.

Look at the below example to understand with and without padding and also how it can be encoded to the original form.

package com.oraclejavacertified.blogspot.java8.base64;

import java.util.Base64;

public class SImpleBase64WithoutPaddingExample {

    public static void main(String[] args) {

        String originalString = "Welcome to oraclejavacertified.blogspot.com";

        System.out.println("Original String : "+originalString);
        // create base simple encoder object
        Base64.Encoder simpleEncoder = Base64.getEncoder();

        // Encoding string using simple encode
        String encodedString = simpleEncoder.encodeToString(originalString.getBytes());
        System.out.println("Encoded string with padding : "+encodedString);

        // encode  without  padding
        Base64.Encoder withoutPaddingEncoder = Base64.getEncoder().withoutPadding();
        String encodeWithoutPadding  =  withoutPaddingEncoder.encodeToString(originalString.getBytes());
        System.out.println("Encoded string without padding : "+encodeWithoutPadding);

        // Create base simple decoder  object
        Base64.Decoder simpleDecoder = Base64.getDecoder();

        // Deconding the encoded string using decoder
        String decodedString = new String(simpleDecoder.decode(encodeWithoutPadding.getBytes()));
        System.out.println("Decoded String : "+decodedString);
    }
}

Output:

Original String : Welcome to oraclejavacertified.blogspot.com
Encoded string with padding : V2VsY29tZSB0byBqYXZhcHJvZ3JhbXRvLmNvbQ==
Encoded string without padding : V2VsY29tZSB0byBqYXZhcHJvZ3JhbXRvLmNvbQ
Decoded String : Welcome to oraclejavacertified.blogspot.com

6. Java 8 Base64 URL Encoding and Decoding


Base64 URL encoder is very similar to the simple encoder. All character set is the same as a simple one but the difference is ‘_’ instead of ‘\’.

Here is the full URL allowed characters ‘A-Za-z0-9+_’.

And also does not add any line separation in the decoder.

use getUrlEncoder() for encoding and getUrlDecoder() for decoding the URL’s from Base64.

Base64 URL Example:

package com.oraclejavacertified.blogspot.java8.base64;

import java.util.Base64;

public class SImpleBase64URLExample {

    public static void main(String[] args) {

        String originalStringURL = "https://www.google.co.in/imghp?hl=en&tab=wi&authuser=0&ogbl";

        System.out.println("Original String : "+originalStringURL);

        // create url encoder object
        Base64.Encoder simpleEncoder = Base64.getUrlEncoder();

        // Encoding string using url encode
        String encodedString = simpleEncoder.encodeToString(originalStringURL.getBytes());
        System.out.println("Encoded URL string with padding : "+encodedString);

        // encode  without  padding
        Base64.Encoder withoutPaddingEncoder = Base64.getEncoder().withoutPadding();
        String encodeWithoutPadding  =  withoutPaddingEncoder.encodeToString(originalStringURL.getBytes());
        System.out.println("Encoded URL string without padding : "+encodeWithoutPadding);

        // Create base simple decoder  object
        Base64.Decoder simpleDecoder = Base64.getDecoder();

        // Deconding the encoded string using decoder
        String decodedString = new String(simpleDecoder.decode(encodeWithoutPadding.getBytes()));
        System.out.println("Decoded URL String : "+decodedString);
    }
}

Output:

Original String : https://www.google.co.in/imghp?hl=en&tab=wi&authuser=0&ogbl
Encoded URL string with padding : aHR0cHM6Ly93d3cuZ29vZ2xlLmNvLmluL2ltZ2hwP2hsPWVuJnRhYj13aSZhdXRodXNlcj0wJm9nYmw=
Encoded URL string without padding : aHR0cHM6Ly93d3cuZ29vZ2xlLmNvLmluL2ltZ2hwP2hsPWVuJnRhYj13aSZhdXRodXNlcj0wJm9nYmw
Decoded URL String : https://www.google.co.in/imghp?hl=en&tab=wi&authuser=0&ogbl

7. Java 8 Base64 MIME Encoding and Decoding


MIME encoding is another type of Base64 encode and decode. MIME technique is mainly used for more data such as ASCII, email contents attachments, audio, video, and images that need to be encoded and decoded.

Use Base64.getMimeEncoder() for MIME data encoding and Base64.getMimeDecoder() for MIME data decoding.

The encoded content byMIME type will be divided into multiple lines and each line length not exceeding 76 characters. Each line ends with a carriage return such as \n or \r.

package com.oraclejavacertified.blogspot.java8.base64;
import java.util.Base64;
import java.util.UUID;

public class SImpleBase64MIMEExample {

    public static void main(String[] args) {

        // adding random  10 UUID values.
        String mimeContent = generateMimeBuffer().toString();

        System.out.println("Original mime buffer length: " + mimeContent.length());

        // Base64 MIME Encode
        Base64.Encoder mimeEncode = Base64.getMimeEncoder();
        String mimeEncodedStr = mimeEncode.encodeToString(mimeContent.getBytes());
        System.out.println("MIME encoded string : " + mimeEncodedStr);

        // Base64 MIME Decode
        Base64.Decoder mimeDecode = Base64.getMimeDecoder();
        String mimeDecodedStr = new String(mimeDecode.decode(mimeEncodedStr.getBytes()));
        System.out.println("MIME decoded string length: " + mimeDecodedStr.length());
    }

    /**
     * Genreates random 10 UUID numbers and adds to the StringBuffer.
     *
     * @return
     */
    private static StringBuffer generateMimeBuffer() {

        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 10; i++) {

            buffer.append(UUID.randomUUID());
        }

        return buffer;

    }
}

Output:

package com.oraclejavacertified.blogspot.java8.base64;
Original mime buffer length: 360
MIME encoded string : N2Y1Njk5MjEtMjg1OC00YTBhLWFlMDgtYTJhMTIzMDkxOTY5OWNjNWM3OWYtZGQ5My00OTE4LWIz
N2MtYmNlM2I1MzA2MDk0ODdhZTUwNDgtODBlMi00YTUxLThjMGEtZDY4M2Y1YWZmMjc1OWI2YWU4
ZDMtZTE0Ni00ZTQzLWE4MWItMTllYTZiYzE0MDEwYzQ1YjE3MzQtZTM1Ni00NmU5LThhOWMtNDlk
ODA2NGQ4YTU1YzgwYzIxZTUtZDI1Zi00YzMyLTliMzEtY2ViNmU3OGIyNWU3ZDcxOWI4OGYtNzY2
OC00ZGExLThiZGYtMDlmOGM4MTk2MzkxMjI3MTllZjQtMTVkZC00YTY1LTgxZmQtYWUyYzAxYjI1
MjAyYjM2ZDY4ZmItODkxMS00ZGY1LWJiZmEtM2YwMDhiYmZkMzQ5NGIwZmUyOTUtNzMyOC00Nzgz
LThmOTEtYTI5ZWMwM2E4NWFk
MIME decoded string length: 360

8. Encoding and Decoding Using Apache Commons API


The first thing is you need to add a dependency to the pom.xml file as below or add commons-codec.jar file to the project.

package com.oraclejavacertified.blogspot.java8.base64;
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
</dependency>

It is suggested to use always the latest version of commons-codec as security is high priority.

Use the org.apache.commons.codec.binary.Base64 class methods work with encoding and decoding and it has various constructors with different arguments. Most of them look very useful.

Base64(): Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.

Base64(boolean urlSafe): Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode.

Base64(int lineLength): Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.

Base64(int lineLength, byte[] lineSeparator): Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.

Base64(int lineLength, byte[] lineSeparator, boolean urlSafe): Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.

Apache Commons Codec Bas64 Example:

package com.oraclejavacertified.blogspot.java8.base64;
package com.oraclejavacertified.blogspot.java8.base64.apache;

import org.apache.commons.codec.binary.Base64;

public class ApacheCommonsCodecBase64 {
    public static void main(String[] args) {

        String originalInput = "test input for apache commons codec";

        Base64 base64 = new Base64();

        // Commons codec encoding
        String encodedString = new String(base64.encode(originalInput.getBytes()));
        System.out.println("Encoded string : "+encodedString);

        // Commons codec decoding
        String decodedString = new String(base64.decode(encodedString.getBytes()));
        System.out.println("Decoded string : "+decodedString);

    }
}

Output:

package com.oraclejavacertified.blogspot.java8.base64;
Encoded string : dGVzdCBpbnB1dCBmb3IgYXBhY2hlIGNvbW1vbnMgY29kZWM=
Decoded string : test input for apache commons codec
And also apache commons codec api provides static methods for decoding and encoding.

Base64.encodeBase64() & Base64.decodeBase64() Example:

package com.oraclejavacertified.blogspot.java8.base64;
package com.oraclejavacertified.blogspot.java8.base64.apache;

import org.apache.commons.codec.binary.Base64;

public class ApacheCommonsCodecBase64Static {
    public static void main(String[] args) {

        String originalInput = "Test commons codec with static methods";

        // Commons codec encoding with Base64.encodeBase64()
        String encodedString = new String(Base64.encodeBase64(originalInput.getBytes()));
        System.out.println("Encoded string : "+encodedString);

        // Commons codec decoding with Base64.decodeBase64()
        String decodedString = new String(Base64.decodeBase64(encodedString.getBytes()));
        System.out.println("Decoded string : "+decodedString);

    }
}

Output:

package com.oraclejavacertified.blogspot.java8.base64;
Encoded string : VGVzdCBjb21tb25zIGNvZGVjIHdpdGggc3RhdGljIG1ldGhvZHM=
Decoded string : Test commons codec with static methods

Use isChunked argument and pass it as true then it will divide the encoded string into multiple lines and each line with 76 characters.

Monday, September 7, 2020

Java 8 Optional Tutorial With Examples

Java 8, Oracle Java Tutorial and Material, Oracle Java Learning, Oracle Java Certification

A quick and in-depth tutorial to Optional API in java 8. Exploring various types of examples to understand the right usage.

1. Overview


In this tutorial, You will learn in-depth about Java 8 Optional Class methods and its usages.

Optional class is added to the java.util package. The intention of introducing this class in java 8 is mainly to check whether the value is present in the object or it is absent.

The object is to hold the set of values that means it contains the real values. So, such kind of object is called a Container.

The container object may contain a null or non-null value in it.

2. Optional Advantages


Before using Optional class methods you should know what are the benefits that you are going to get from it.

2.1 Completely you can avoid the null checks in the application and no need to write the utility methods

2.2 Zero NullPointerExceptions at runtime.

2.3 Code becomes cleaner and more readable

2.4 Say goodbye to boilerplate coding

2.5 Avoid third party api’s such as Apache Commons API ObjectsUtils

3. Optional Main Methods


The Optional class has many methods but only two methods are most used in the coding. Those two are isPresent() and get() methods.

isPresent() returns true if the optional has non-null values,  otherwise false.

get() returns the actual value from Optional object. If optional has null value means it is empty. In such a case, NoSuchElementException is thrown.

Sample Example:

package com.oraclejavacertified.blogspot.java8.base64;
package com.oraclejavacertified.blogspot.java8.optional;

import java.util.Optional;

public class OptionalGetExample {

    public static void main(String[] args) {

        // Creating non null optional object.
        Optional<String> optional= Optional.of("hello");

        // checking value present in the optional or not.
        if(optional.isPresent()){
            String value = optional.get();
            System.out.println("Optional value : "+value);
        } else {
            // if optional has no value
            System.out.println("Optional has no value");
        }
    }
}

Output:

package com.oraclejavacertified.blogspot.java8.base64;
Optional value : hello

4. Optional Constructors and Creating Optional Objects


Optional is a final class and it has two private constructors as below. These two constructors can not be accessed because these are declared as private.

private Optional() {
        this.value = null;
    }
   
 private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

So, now the question is how to create the Optional object with a non-null value and null value?

Answer: Optional class is added with static methods that help in creating Optional Objects for the given values.  Optional object creation static methods are below with syntax.

static <T> Optional<T> empty(): Returns an empty Optional instance.
static <T> Optional<T> of(T value): Returns an Optional with the specified present non-null value.

the empty() method creates an empty stream with null value whereas. of() method creates Optional object with non-null value.

Example to create Optional objects:

import java.util.Optional;

public class OptionalObjectCreationExample {
    public static void main(String[] args) {

        // creating an empty optional object
        Optional<Void> emptyOptional = Optional.empty();

        // checking if the optional is empty or not
        System.out.println(" Is optional is empty : " + emptyOptional.isEmpty());

        // Creating String type Optional
        Optional<String> stringOptional = Optional.of("Hello");

        if (stringOptional.isPresent()) {
            System.out.println("Getting value from stringOptional : " + stringOptional.get());
        }

        // Integer type optional
        Optional<Integer> intOptionbal = Optional.of(1244);
        System.out.println("Integer Optional: " + intOptionbal.get());
       
        // Passing null to  of() method.
        Optional.of(null);

    }
}

Output:

Is optional is empty : true
Getting value from stringOptional : Hello
Integer Optional: 1244
Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
    at java.base/java.util.Optional.<init>(Optional.java:107)
    at java.base/java.util.Optional.of(Optional.java:120)
    at com.oraclejavacertified.blogspot.java8.optional.OptionalObjectCreationExample.main(OptionalObjectCreationExample.java:26)

Optional of() and empty() methods are to create non-null and empty Optional objects using its static methods.

Non null value must be passed to of() method otherwise it will throw NullPointerException.

5. Optional ofNullable() 


If you are expecting some null values then use the ofNullable() method.

By using this method, if we pass in a null reference, it doesn’t throw an exception but rather returns an empty Optional object.

import java.util.Optional;

public class OfNullableExample {
    public static void main(String[] args) {

        // Creating optional
        Optional<String> ofNullable = Optional.ofNullable("Non null value");

        String content = ofNullable.get();
        System.out.println("Ofnullable value :" + content);

        // passing null value
        Optional nullOptional = Optional.ofNullable(null);
        nullOptional.get();
    }
}

Output:

Ofnullable value :Non null value

It is suggested to use the ofNullable() method rather than using of() and empty() method separately.  Because it does uses internally both of() for nonnull value and empty() for a null value.

6. Checking Value Presence


If a method is returning an Optional object then you need to check that whether the Optional is having a value or not.

To check this, the Optional class has an isPresent() method which returns true if it has non-null value or false if it empty or null value.

import java.util.Optional;

public class OfNullableExample {
    public static void main(String[] args) {

        // Creating optional
        Optional<String> optional = Optional.ofNullable("oraclejavacertified.blogspot.com");

        System.out.println("Chceking if optional has value with isPresent() method");
        System.out.println("isPresent value : "+optional.isPresent());
       
        // empty optional
        Optional<String> emptyOptional = Optional.ofNullable(null);
        System.out.println("isPresent value for empty optional : "+emptyOptional.isPresent());
       
    }
}

Output:

Chceking if optional has value with isPresent() method
isPresent value : true
isPresent value for empty optional : false

Java 11 api is added with a new method to check if optional isempty or not using isEmpty() method.

public class OptionalIsEmpty {
    public static void main(String[] args) {

        // Creating optional
        Optional<String> optional = Optional.ofNullable("oraclejavacertified.blogspot.com");

        System.out.println("Checking if optional has value with isEmpty() method");
        System.out.println("isEmpty value : " + optional.isEmpty());

        // empty optional
        Optional<String> emptyOptional = Optional.ofNullable(null);
        System.out.println("isPreisEmptysent value for empty optional : " + emptyOptional.isEmpty());

    }
}

Output:

Checking if optional has value with isEmpty() method
isEmpty value : false
isPreisEmptysent value for empty optional : true

7. Optional ifPresent()


You can do the if and if-else conditions on Optional object using the ifPresent() method.

public void ifPresent(Consumer<? super T> consumer)

This method takes Consumer as a functional argument and its logic is only be executed if it has not null value.

ifPresent() is introduced mainly to avoid null pointer exception.

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
 
public class OptionalIfPresent {
    public static void main(String[] args) {
 
        System.out.println("Example 1 : ------ Optional string ------ ");
        // Creating optional
        Optional<String> optional = Optional.ofNullable("oraclejavacertified.blogspot.com");
 
        // ifpresent syntax
        optional.ifPresent(value -> System.out.println(value));
 
        System.out.println("Example 2 : ------  Optional List of integers ------ ");
 
        // Creating list of numbers
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
 
        // Passing lsit to optional
        Optional<List<Integer>> numbersOfListOptional = Optional.ofNullable(list);
 
        // cheeking with ifpresent
        numbersOfListOptional.ifPresent(optionalList -> optionalList.forEach(v -> System.out.println(v)));
 
        Optional empty = Optional.empty();
        empty.ifPresent(valeu -> System.out.println("no value"));
 
    }
}

Output:

Example 1 : ------ Optional string ------ 
oraclejavacertified.blogspot.com
Example 2 : ------  Optional List of integers ------ 
1
2
3
4
5

8. Optional Default Values – orElse()


If the Optional is created with empty() or ofNullable(null) then you will get empty optional. 

If the optional is empty then you can still get the default value using orElse() method.

orElse() method takes the value as a type of original Optional caller.

public T orElse(T other)

orElse() Example:

public class OptionalOrElse {
    public static void main(String[] args) {
 
        Optional<String> o1 = Optional.ofNullable(null);
         
        String value = o1.orElse("Default One");
         
        System.out.println("Fetching the value from orElse() : "+value);
         
        Optional<Integer> intOptional = Optional.empty();
         
        int defaultValue = intOptional.orElse(15);
         
        System.out.println("Int  default value :"+defaultValue);
 
    }
}

Output:

Fetching the value from orElse() : Default One
Int  default value :15

9. Optional Default Values – orElseGet()


orElseGet() method is to get the alternate value if the Optional is empty or has a null value. This method takes the Supplier as an argument.

public T orElseGet(Supplier<? extends T> other)

orElseGet() Example:

public class OptionalOrElseGet {
    public static void main(String[] args) {
 
        Optional<String> o1 = Optional.ofNullable(null);
 
        String value = o1.orElseGet(() -> "Default One  from  supplier");
 
        System.out.println("Fetching the value from orElseGet() : " + value);
 
        Optional<Integer> intOptional = Optional.of(134);
 
        int defaultValue = intOptional.orElseGet(() -> 15);
 
        System.out.println("orElseGet Int  default value :" + defaultValue);
 
    }
}

Output:

Fetching the value from orElseGet() : Default One  from  supplier
orElseGet Int  default value :134

10. Differences Between orElse() and orElseGet() Methods


orElseGet() method looks similar to the orElse() method as seen in the above section but there is a little different and that is the most important in the large scale applications.

The main difference is orElse() method will be executed always whether source Optional has value or not whereas orElseGet() will be invoked if and only if source optiona is an empty or null value.

Look at the example below to understand clearly.

Creating a method to get the string as a return. This method will be called by both of these methods.

In the below example, taking the Optional with null value by both methods.

public class OptionalDiffOrElseGetVsorElse {
    public static void main(String[] args) {
 
        // Optional with null value
        Optional<String> opt1 = Optional.ofNullable(null);
        String orElseValue1 = opt1.orElse(getDefaultValue());
        System.out.println("orElse() value : " + orElseValue1);
 
        // Optional with null value
        Optional<String> opt2 = Optional.ofNullable(null);
        String orElseValue2 = opt2.orElseGet(() -> getDefaultValue());
        System.out.println("orElseGet() value : " + orElseValue2);
 
    }
 
    public static String getDefaultValue() {
        System.out.println("executing to get the default value");
        return "I am  here to get the default value";
    }
}

Output:

executing to get the default value
orElse() value : I am  here to get the default value
executing to get the default value
orElseGet() value : I am  here to get the default value

Here, the source optional has null value so it called the default logic getDefaultValue() in both cases.

Letus see now Optional with values.

public class OptionalDiffOrElseGetVsorElse {
    public static void main(String[] args) {
 
        // Optional with null value
        Optional<String> opt13 = Optional.ofNullable("123");
        String orElseValue3 = opt13.orElse(getDefaultValue());
        System.out.println("orElse() value : " + orElseValue3);
 
        // Optional with null value
        Optional<String> opt4 = Optional.ofNullable("789");
        String orElseValue4 = opt4.orElseGet(() -> getDefaultValue());
        System.out.println("orElseGet() value : " + orElseValue4);
 
    }
 
    public static String getDefaultValue() {
 
        System.out.println("executing to get the default value");
        return "I am  here to get the default value";
    }
}

Output:

executing to get the default value
orElse() value : 123
orElseGet() value : 789

In this case,  Both sources optional objects are created with the values which are not null. So, getDefaultValue() method should not be called as we expected but in the output, it printed the content from getDefaultValue() method from orElse() method.

11. Optional filter()


filter() method is used to filter the optional values based on the predicate condition.

public Optional<T> filter(Predicate<? super T> predicate)

Example:

An example to check the given number is even or odd. If the number is even then Optional will have an even number.

public class OptionalFilterExample {
    public static void main(String[] args) {
 
        // Creating optional
        Optional<Integer> op = Optional.ofNullable(1234);
 
        Optional<Integer> evenOrNot = op.filter(number -> number % 2 == 0);
 
        if (evenOrNot.isEmpty()) {
            System.out.println("Odd number");
        } else {
            System.out.println("Even number");
        }
 
    }
}

Output:

Even number

12. Optional.map() -Value Transformations


Optional map() isused to transform the optional into another form.

public class OptionalMapExample {
    public static void main(String[] args) {
 
        // Creating optional
        Optional<String> op = Optional.ofNullable("Welcome reader");
 
        Optional<String> mapOptional = op.map(value -> {
            if (value.contains("Welcome")) {
                return "Articles are good";
            } else {
                return "Welcome to oraclejavacertified.blogspot.com";
            }
 
        });
 
        mapOptional.ifPresent(action -> System.out.println(action));
 
    }
}

Output:

Articles are good

13. Optional.ifPresent() 


Run the another logic if the optional value is present.

If a value is present, invoke the specified consumer with the value, otherwise do nothing.

public void ifPresent(Consumer<? super T> consumer)

Example to convert String to Integer using map() and ifPresent()

public class OptionalIfPresentExample {
    public static void main(String[] args) {
 
        // String value optional
        Optional<String> string = Optional.ofNullable("12345");
 
        // converting string to number
        Optional<Integer> numberOptional = string.map(value -> Integer.parseInt(value));
 
        // printing the number using ifPresent()
        numberOptional.ifPresent(newValue -> System.out.println(newValue));
 
    }
}

Output: 

12345

14. Optional.flatMap()


flatMap(): If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional.

It removes all nested Optional objects and just gets the value from it.

public class OptionalflatmapExample {
    public static void main(String[] args) {
 
        Optional<String> optional1 = Optional.of("Hello Java 8 Optional");
        Optional<Optional<String>> optional2 = Optional.of(optional1);
 
        System.out.println("Optional2 value : " + optional2);
 
        Optional<String> output = optional2.flatMap(value -> value.map(String::toLowerCase));
 
        System.out.println("output value : " + output);
 
    }
}

Output:

Optional2 value : Optional[Optional[Hello Java 8]]
output value : Optional[hello java 8]

15. Optional orElseThrow()


Optional API added a new way to throw the exception if the value not present in the optional object.

orElseThrow() looks as similar to the orElse() and orElseGet() pattern.

orElseThrow() returns a value from the optional else throws the. exception saying “java.util.NoSuchElementException: No value present“.

Syntax:

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
                                    throws X extends Throwable 

orElseThrow() Example:

public class OptionalOrElseThrowExample {
    public static void main(String[] args) {
 
        // orElseThrow() example
        String blank = Optional.ofNullable("").orElseThrow();
        System.out.println(blank);
 
        String value = null;
        String blank2 = Optional.ofNullable(value).orElseThrow();
        System.out.println(blank2);
 
    }
}

Output:

Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.orElseThrow(Optional.java:382)
    at com.oraclejavacertified.blogspot.java8.optional.OptionalOrElseThrowExample.main(OptionalOrElseThrowExample.java:13)

orElseThrow() Custom Exception Example:

public class OptionalOrElseThrowCustomExExample {
    public static void main(String[] args) throws CustomException {
 
        // orElseThrow() runtime IllegalArgumentException example
        String nullValue = null;
        String blank = Optional.ofNullable(nullValue).orElseThrow(IllegalArgumentException::new);
        System.out.println(blank);
 
        // throwing checked exception
        String value = null;
        String blank2 = Optional.ofNullable(value).orElseThrow(CustomException::new);
        System.out.println(blank2);
 
    }
}
 
class CustomException extends Exception {
 
    public CustomException() {
 
    }
}

This will produce the runtime exception and here used the method reference concept added in java 8.

16. Optional New Methods in Newer JDK’s


Optional api is added with new api methods in java 9, java 10, and java 11 versions as below.

Java 9:

public void ifPresentOrElse​(Consumer<? super T> action, Runnable emptyAction): If a value is present, performs the given action with the value, otherwise performs the given empty-based action.

public Optional<T> or​(Supplier<? extends Optional<? extends T>> supplier): If a value is present, returns an Optional describing the value, otherwise returns an Optional produced by the supplying function.

public Stream<T> stream(): If a value is present, returns a sequential Stream containing only that value, otherwise returns an empty Stream.

Java 10:

public T orElseThrow(): If a value is present, returns the value, otherwise throws NoSuchElementException.

Java 11:

public boolean isEmpty(): If a value is not present, returns true, otherwise false.

Friday, September 4, 2020

Java 8 Collectors API Tutorial With Real Time Examples

Java 8 Collectors API, Oracle Java Exam Prep, Oracle Java Tutorial and Materials, Oracle Certification

A quick practical guide to Java 8’s Collectors api. Example programs on various useful reduction operations and accumulating elements into collections

1. Overview


In this tutorial, We’ll be learning to Java 8 Collectors API in-depth with all methods and example programs. Collectors is a public final class that extends Object class.

Collectors class provides various useful reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, etc

This has many methods that are very useful when working with Stream api.

Few methods: toList(), toMap(), toCollection(), joining(), summingInt(), groupingBy() and partitioningBy(), etc.

We will see the example programs on the below Collectors methods and how to use them.

Collectors API Methods:
  1. collect()
  2. toList()
  3. toSet()
  4. toUnmodifiableSet()
  5. toUnmodifiableList(()
  6. toCollection()
  7. toMap()
  8. toUnmodifiableMap()
  9. summingInt()
  10. averagingInt() / averagingLong() / averagingDouble()s
  11. counting()
  12. joining()
  13. groupingBy()
  14. partitioningBy()
  15. toConcurrentMap()
  16. filtering()
  17. flatMapping()
  18. maxBy()
  19. minBy()
  20. reducing()
  21. summarizingDouble() / summarizingInt() / summarizingLong()
  22. teeing()
Note: All methods in the Collectors class are static. So it is good to use static import.

If you are using many methods then use static import.

import static java.util.stream.Collectors.*;

If you are using only a few then use like this.

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.joining;

We will be using the below Employee class in this article.

class Employee {
private int id;
private String name;
private int age;
private String region;
private double sal;

public Employee(int id, String name, int age, String region, double sal) {
this.id = id;
this.name = name;
this.age = age;
this.region = region;
this.sal = sal;
}

// Standard setters and getters
}

Creating an Employee List.

List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee(100, "Sundar", 47, "North America", 450000));
employeeList.add(new Employee(200, "Pichai", 25, "North America", 50000));
employeeList.add(new Employee(300, "Larry", 30, "Asia", 450000));
employeeList.add(new Employee(400, "Page", 59, "Africa", 450000));

2. Java 8 Stream.collect() Example


Java 8’s most powerful stream method is collect() method. Which is also called a Terminal method.

It allows us to perform mutable fold operations (repackaging elements to some data structures and applying some additional logic, concatenating them, etc.) on data elements held in a Stream instance.

The strategy for this operation is provided via Collector interface implementation.

3. Collectors.toList() Example


toList() collector can be used for collecting all Stream elements into a List instance.

Example to collect all employee names into List using toList() method.

List<String> namesList = employeeList.stream().map(e -> e.getName()).collect(Collectors.toList());
System.out.println(namesList);

Output:

[Sundar, Pichai, Larry, Page]

But, there are no guarantees on the type, mutability, serializability, or thread-safety of the List returned.

If you need more control over what type of List should be returned then should use toCollection(Supplier) method.

4. Collectors.toSet() Example


toSet() collector is used for collecting all Stream elements into a Set instance.

Example to collect all the regions into Set.

Set<String> regionSet = employeeList.stream().map(e -> e.getRegion()).collect(Collectors.toSet());
System.out.println(regionSet);

Output:

[Asia, Africa, North America]

But, there are no guarantees on the type, mutability, serializability, or thread-safety of the Set returned.

If you need more control over what type of Set should be returned then should use the toCollection(Supplier) method.

5. Collectors.toUnmodifiableSet() Example


This collects the elements into an unmodifiable set.

The set is created using the toSet() method can be modified.

regionSet.add("hello");
System.out.println(regionSet);

Output:

[Asia, Africa, hello, North America]

toUnmodifiableSet() method works similar to the toSet() but this set can not be modified.

Set<Double> unmodifiableSet = employeeList.stream().map(e -> e.getSal()).collect(Collectors.toUnmodifiableSet());
System.out.println(unmodifiableSet);

Output:

[450000.0, 50000.0]

If we try to modify set then will throw UnsupportedOperationException.

unmodifiableSet.add(10983d);

Exception:

Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:72)

The returned collector does not allow null values. This will throw NullPointerException if it is presented with a null value.

employeeList.add(null);
Set<Employee> empSet = employeeList.stream().collect(Collectors.toUnmodifiableSet());

The above code will cause NullPointerException. The same will be in the case of the toSet() method.

6. Collectors.toUnmodifiableList(() Example


This is similar to the toList() but toUnmodifiableList will collect elements into an unmodifiable List.

List<Double> unmodifiableList = employeeList.stream().map(e -> e.getSal()).collect(Collectors.toUnmodifiableList());
System.out.println(unmodifiableList);

Output:

[450000.0, 50000.0, 450000.0, 450000.0]

This list holds duplicates, unlike Set.

If List has null value then it will throw java.lang.NullPointerException like toUnmodifiableSet.

7. Collectors.toCollection() Example


As you probably already noticed, when using toSet() and toList() collectors, you can’t make any assumptions of their implementations.

If you want to use a custom implementation or LinkedList or TreeSet, you will need to use the toCollection collector with a provided collection of your choice.

Example to collect the names into LinkedList as opposed to default List implementation.

List<String> namesLinkedList = employeeList.stream().map(e -> e.getName()).collect(Collectors.toCollection(LinkedList::new));
System.out.println(namesLinkedList);

Output:

[Sundar, Pichai, Larry, Page]

Another example to collect regions into TreeSet.

Set<String> regionTreeSet = employeeList.stream().map(e -> e.getRegion()).collect(Collectors.toCollection(TreeSet::new));
System.out.println(regionTreeSet);

Output:

[Africa, Asia, North America]

See the output is sorted because TreeSet sorts the values in it.

Note: This method does not work with immutable objects. In such type of cases, We must write custom Collector implementation or Use collectingAndThen().

8. Collectors.toMap() Example


toMap() Syntax:

public static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
Using toMap() method, A stream can be converted into a Map. But, this method needs two parameters.

keyMapper

valueMapper

These two are implementations of Function Functional Interface.

Functional Interface Function has a functional method R apply(T t) that accepts one argument and produces a result.

keyMapper will be used for extracting a Map key from a Stream element, and valueMapper will be used for extracting a value associated with a given key.

Now, We will create a map from a stream such that the map key is emp id and value is corresponding employee object.

keyMapper = (e) -> e.getId()

e refers to the Employee object and getting its id by calling getId() method.

valueMapper =  Function.identity()

This method returns a function that always returns its input argument.

Function.identity() method does take one object as an argument and returns the same object with no change.

Map<Integer, Employee> empMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity()));
System.out.println(empMap);

Output:

{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}

What happens if employeeList has duplicate employees with the same employee id.

Now adding a duplicate emp object with the same emp id but the different name “Larry Page”.

employeeList.add(new Employee(400, "Larry Page", 59, "Africa", 450000));

Added a new emp object with emp id = 400.

Map<Integer, Employee> empDupMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity()));

Will throw the Runtime Exception as follow.

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 400 (attempted merging values Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0] and Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0])
at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:133)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at com.java.w3schools.blog.java8.streams.Java8CollectorExamples.main(Java8CollectorExamples.java:76)

Note: Map does not check the duplicate objects but does not allow duplicate keys.

toMap() function takes 3rd argument as BinaryOperator Functional Interface which has a functional method R apply(T t, U u). This functional method takes two arguments. In our case, the first argument takes the original employee, Second argument takes the duplicate employee and returns the employee object.

Map<Integer, Employee> empDupMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empDupMap);

Output:

Here BinaryOperator will be invoked when there the same key appears. And returning duplicate objects from BinaryOperator apply() method. That replaces the old employee with a new duplicate employee. See the below output.

{400=Employee [id=400, name=Larry Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}

Observe the employee with id 400 has a new name “Larry Page”. If we want to keep existing emp in the map and ignore the duplicate emp then we should return emp instead of sameEmp.

9. Collectors.toUnmodifiableMap() Example


Syntax:

public static <T,K,U> Collector<T,?,Map<K,U>> toUnmodifiableMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
public static <T,K,U> Collector<T,?,Map<K,U>> toUnmodifiableMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)

Returns a Collector that accumulates the input elements into an unmodifiable Map, whose keys and values are the result of applying the provided mapping functions to the input elements.

Map<Integer, Employee> empUnmodifiedMap = employeeList.stream().collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empUnmodifiedMap);

This function will throw NullPointerException if the keyMapper, valueMapper, or mergeFunction is null.

10. Collectors.summingInt() Example


summingInt() Method does the summation of all extracted elements from the stream and returns Integer.

Syntax:

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

Example:

Finding the sum of all emp id’s using summingInt() method.

int sumOfEmpIds = employeeList.stream().collect(Collectors.summingInt((Employee e) -> e.getId()));
System.out.println("Collectors.summingInt : " + sumOfEmpIds);

Output:

Collectors.summingInt : 1000

This method is to sum of int values.

Similar to this, Collector api has methods for summingLong(), summingDouble().

public static <T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper)

summingDouble() Example:

Now, let us see the code to get the sum of salaries of all employees.

double sumOfEmpSalss = employeeList.stream().collect(Collectors.summingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.summingDouble : " + sumOfEmpSalss);

Output:

Collectors.summingDouble : 1400000.0

11. Collectors.averagingInt() / averagingLong() / averagingDouble() Examples


Collectors api has methods to get the average for integer, Long and Double values. These methods become now very handy to perform avg operations.

Internally arithmetic logic is applied for stream elements.

Syntax:

public static <T> Collector<T,?,Double> averagingInt(ToIntFunction<? super T> mapper)
public static <T> Collector<T,?,Double> averagingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T,?,Double> averagingDouble(ToDoubleFunction<? super T> mapper)

Here all these versions of methods return Double rather that its names describe.

Examples:

double avgOfEmpSalss = employeeList.stream().collect(Collectors.averagingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.averagingDouble avg sal: " + avgOfEmpSalss);

Output:

Collectors.averagingDouble avg sal: 350000.0

12. Collectors.counting() Example


Syntax:

public static <T> Collector<T,?,Long> counting()

This method returns a Long value and just counts the values present in the Stream.

long count = employeeList.stream().collect(Collectors.counting());
System.out.println("Collectors.counting() : Count : " + count);

Output:

Collectors.counting() : Count : 4

13. Collectors.joining() Example


Syntax:

public static Collector<CharSequence,?,String> joining()
public static Collector<CharSequence,?,String> joining(CharSequence delimiter)
public static Collector<CharSequence,?,String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

Joining() method does append the all contents of Stream the order they appear.

This is mainly designed for Strings with Stream<String>.

All versions of joining() methods return String.

joining() Example:

String joinedStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining());
System.out.println("joinedStr by using joining() method : "+joinedStr);

Output:

joinedStr by using joining() method : SundarPichaiLarryPage

This joining() method just concatenates without adding any delimiter.

joining(CharSequence delimiter) Example:

If we want to add some delimiter then we should use this variant of the method.

String joinedDelimiterStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining(" * "));
System.out.println("joinedDelimiterStr by using joining(Delimiter) method : "+joinedDelimiterStr);

Output:

joinedDelimiterStr by using joining(Delimiter) method : Sundar * Pichai * Larry * Page

Now observe the output that added * delimiter for each name.

joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) Example:

Example code to add some value to before and after delimiter is applied.

prefix will be added at the beginning after concatenating the delimiter to all values.

suffix will be added at the end after concatenating the delimiter to all values.

String joinePrePostStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining("*", "@", "|"));
System.out.println("joinePrePostStr by using joining(Delimiter) method : "+joinePrePostStr);

Output:

joinePrePostStr by using joining(Delimiter) method : @Sundar*Pichai*Larry*Page|

14. Collectors.groupingBy() Example


This groupingBy function works similar to the Oracle GROUP BY clause.

GroupingBy collector is used for grouping objects by some property and storing results in a Map instance.

We will see the example of the group by an employee region.

Map<String, List<Employee>> groupByRegion = employeeList.stream().collect(Collectors.groupingBy((Employee e) -> e.getRegion()));
System.out.println("groupByRegion :: "+groupByRegion);

Output:

groupByRegion :: {Asia=[Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], Africa=[Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]], North America=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]]}

The below two employees are stored under the “North America” region.

Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]

By default, Map values are stored in the List.

If we want to make value is set then we should specify to the groupingBy() method as below.

Map<String, Set<Employee>> groupByRegionSet = employeeList.stream().collect(Collectors.groupingBy((Employee e) -> e.getRegion(), Collectors.toSet()));

15. Collectors.partitioningBy() Example


Syntax:

public static <T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)

First, Take a look at the syntax.

From syntax, This method returns a Map and Boolean is key, List<T> as value. The returned Map always contains mappings for both false and true keys only.

Map<Boolean, List<Employee>> partitionByAge = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30));
System.out.println("partitionByAge :: "+partitionByAge);

partitioningBy() method takes Predicate Functional Interface which returns Boolean. Because of this, Key is always determined as Boolean and value is Employee object.

Output:

partitionByAge :: {false=[Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], true=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]}

Returned Map value is always a List. If you want to change it to another collection then we must use the below variant of groupingBy() method.

public static <T,D,A> Collector<T,?,Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector< super T,A,D> downstream)

Examples:

// Set as Map value
Map<Boolean, Set<Employee>> partitionByAgeSet = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30, Collectors.toSet()));

// LinkedList as Map value
Map<Boolean, LinkedList<Employee>> partitionByAgeLinedList = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30, Collectors.toCollection(LinkedList::new)));

// TreeSet as Map value
Map<Boolean, TreeSet<Employee>> partitionByAgeTreeSet = employeeList.stream().collect(Collectors.partitioningBy( e -> e.getAge() > 30, Collectors.toCollection(TreeSet::new)));

To make working for TreeSort, the Employee class must implement
Comparable interface. Otherwise, will throw ClassCastException.

Note: If a partition has no elements, its value in the resulting Map will be an empty List.

16. Collectors.toConcurrentMap() Example


We have seen already toMap() in this post. If we want to store the result in Concurrent Map then we should use the toConcurrentMap() method.

Syntax:

public static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
public static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)
public static <T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M> toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapFactory)

Examples:

Map<Integer, Employee> empConcurrentMap = employeeList.stream().collect(Collectors.toConcurrentMap((e) -> e.getId(), Function.identity()));

17. Collectors.filtering() Example


Stream api already has a filter() function. But, this is a very convenient way to do at a single place doing condition and collecting into List or Set.

Based on developer choice, We can choose Collectors or Stream api. Mostly, All use stream api filter() method.

Syntax:

public static <T,A,R> Collector<T,?,R> filtering(Predicate<? super T> predicate, Collector<? super T,A,R> downstream)

Examples:

For all stream elements, the first Predicate will be executed first and next downstream will be applied.

List<Employee> filteredEmpList = employeeList.stream().collect(Collectors.filtering((Employee e) -> e.getAge() > 30, Collectors.toList()));
System.out.println("Collectors.filtering() - filteredEmpList : " + filteredEmpList);

Output:

Collectors.filtering() - filteredEmpList : [Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]

18. Collectors.flatMapping() Example


This is very useful to convert a Collection of Collections into a flat map. The
flatMapping() collectors are most useful when used in a multi-level reduction, such as downstream of a groupingBy or partitioningBy.

Syntax:

public static <T,U,A,R> Collector<T,?,R> flatMapping(Function<? super T,? extends Stream<? extends U>> mapper, Collector<? super U,A,R> downstream)

Example:

Creating LineItem class.

class LineItem {

private int itemId;
private String itemName;
private Date manfacturedDate;

public LineItem(int itemId, String itemName, Date manfacturedDate) {
super();
this.itemId = itemId;
this.itemName = itemName;
this.manfacturedDate = manfacturedDate;
}

public int getItemId() {
return itemId;
}

public void setItemId(int itemId) {
this.itemId = itemId;
}

public String getItemName() {
return itemName;
}

public void setItemName(String itemName) {
this.itemName = itemName;
}

public Date getManfacturedDate() {
return manfacturedDate;
}

public void setManfacturedDate(Date manfacturedDate) {
this.manfacturedDate = manfacturedDate;
}

@Override
public String toString() {
return "LineItem [itemId=" + itemId + ", itemName=" + itemName + ", manfacturedDate=" + manfacturedDate + "]";
}

}

Creating Customer class which has HAS-A relationship to LineIteam with List.

class Customer {
private int id;
private String name;
private boolean active;
private String gender;
private List<LineItem> lineItems;

public Customer(int id, String name, boolean active, String gender, List<LineItem> lineItems) {
super();
this.id = id;
this.name = name;
this.active = active;
this.gender = gender;
this.lineItems = lineItems;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public boolean isActive() {
return active;
}

public void setActive(boolean active) {
this.active = active;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public List<LineItem> getLineItems() {
return lineItems;
}

public void setLineItems(List<LineItem> lineItems) {
this.lineItems = lineItems;
}

}

Creating Customer object 1

LineItem lineItem1 = new LineItem(1001, "Item 1", new Date(2010, 07, 01));
LineItem lineItem2 = new LineItem(1002, "Item 2", new Date(2020, 07, 01));
LineItem lineItem3 = new LineItem(1003, "Item 3", new Date(2030, 07, 01));
LineItem lineItem4 = new LineItem(1004, "Item 4", new Date(2040, 07, 01));
LineItem lineItem5 = new LineItem(1005, "Item 5", new Date(2050, 07, 01));

List<LineItem> lineItemsList1 = new ArrayList<>();
lineItemsList1.add(lineItem1);
lineItemsList1.add(lineItem2);
lineItemsList1.add(lineItem3);
lineItemsList1.add(lineItem4);
lineItemsList1.add(lineItem5);

Customer customer1 = new Customer(100, "Customer 1", true, "M", lineItemsList1);

Creating Customer object 2

LineItem lineItem6 = new LineItem(2001, "Item 6", new Date(2010, 07, 01));
LineItem lineItem7 = new LineItem(2002, "Item 7", new Date(2020, 07, 01));

List<LineItem> lineItemsList2 = new ArrayList<>();
lineItemsList2.add(lineItem6);
lineItemsList2.add(lineItem7);

Customer customer2 = new Customer(200, "Customer 2", true, "F", lineItemsList2);

Creating Customer object 3

LineItem lineItem8 = new LineItem(2003, "Item 8", new Date(2040, 07, 01));
LineItem lineItem9 = new LineItem(3004, "Item 9", new Date(2070, 07, 01));
LineItem lineItem10 = new LineItem(3005, "Item 10", new Date(2200, 07, 01));

List<LineItem> lineItemsList3 = new ArrayList<>();
lineItemsList3.add(lineItem8);
lineItemsList3.add(lineItem9);
lineItemsList3.add(lineItem10);

Customer customer3 = new Customer(300, "Customer 3", true, "M", lineItemsList3);

Creating Customer object 4

Customer customer4 = new Customer(400, "Customer 4", true, "F", new ArrayList<LineItem>());

Adding all 4 countomers to List.

List<Customer> customersList = new ArrayList<>();
customersList.add(customer1);
customersList.add(customer2);
customersList.add(customer3);
customersList.add(customer4);

Using flatMapping() method to find the LineItems by gender.

Map<String, Set<LineItem>> itemsByGender = customersList.stream()
.collect(Collectors.groupingBy((Customer c) -> c.getGender(),
Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.toSet())));

System.out.println("itemsByGender : " + itemsByGender);

Output:

itemsByGender : {F=[LineItem [itemId=2001, itemName=Item 6, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=2002, itemName=Item 7, manfacturedDate=Sun Aug 01 00:00:00 IST 3920]],
 M=[LineItem [itemId=1001, itemName=Item 1, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=1005, itemName=Item 5, manfacturedDate=Tue Aug 01 00:00:00 IST 3950], LineItem [itemId=1004, itemName=Item 4, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=1002, itemName=Item 2, manfacturedDate=Sun Aug 01 00:00:00 IST 3920], LineItem [itemId=1003, itemName=Item 3, manfacturedDate=Fri Aug 01 00:00:00 IST 3930], LineItem [itemId=2003, itemName=Item 8, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=3004, itemName=Item 9, manfacturedDate=Sat Aug 01 00:00:00 IST 3970], LineItem [itemId=3005, itemName=Item 10, manfacturedDate=Sun Aug 01 00:00:00 IST 4100]]}

Finding the count LineItemas count by gender.

Now instead of calling collecting the output of flatMapping() into Set, invoke Collectors.counting() that count the number of Line items by Gender.

Map<String, Long> itemsCountByGender = customersList.stream().collect(Collectors.groupingBy((Customer c) -> c.getGender(),Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.counting())));

Output:

itemsCountByGender {F=2, M=8}

19. Collectors.maxBy() Example


maxBy() method finds the max element from the stream. To find the max element, we must pass the Comparator implementation to this method as argument.

This method does as same as reducing(BinaryOperator.maxBy(comparator)).

Syntax:

public static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> comparator)
This method returns the Optional object. This object avoids NullPointerException by using isPresent() method of Optional class.

Example:

Collectors.maxBy() Example to find highest emp id object.

Comparator<Employee> empComparator = (e1, e2) -> e1.getId() - e2.getId();
Optional<Employee> empMaxOptional = employeeList.stream().collect(Collectors.maxBy(empComparator));
if(empMaxOptional.isPresent()) {
System.out.println("Max Emp : "+empMaxOptional.get());
}

Output:

Max Emp : Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]

20. Collectors.minBy() Example


minBy() is opposite to the maxBy() method. minBy() method is used to find a minimum element from the Stream. We should pass the
Comparator as an argument.

This method does as same as reducing(BinaryOperator.minBy(comparator)).

Syntax:

public static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> comparator)

This method also returns an Optional instance similar to the maxBy() method.

Example:

Collectors.minBy() Example to find min emp id object.

Optional<Employee> empminOptional = employeeList.stream().collect(Collectors.minBy(empComparator));
if (empminOptional.isPresent()) {
System.out.println("Min Emp : " + empminOptional.get());
}

Output:

Min Emp : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]

21. Collectors.reducing() Example


Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator. The result is described as an Optional<T>.

Syntax:

public static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> op)
public static <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> op)
public static <T,U> Collector<T,?,U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

Example:

// Collectors.reducing() Example
Optional<Employee> reducingOptinal = employeeList.stream().collect(Collectors.reducing(BinaryOperator.minBy(empComparator)));
if (reducingOptinal.isPresent()) {
System.out.println("Min Emp using reducing() method : " + reducingOptinal.get());
}

Output:

Min Emp using reducing() method : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]

We can find the max emp element using maxBy static method of BinaryOperator.

22. Collectors.summarizingDouble() / summarizingInt() / summarizingLong() Example


summarizingDouble() method does statistics operations on double values in the stream.

This method returns DoubleSummaryStatistics which holds count, min, max, sum, and the average for all double values in the stream. This helps like a utility method.

Syntax:

public static <T> Collector<T,?,DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)

Example:

DoubleSummaryStatistics doubleSummaryStatistics = employeeList.stream().collect(Collectors.summarizingDouble((Employee e) -> e.getSal()));
System.out.println("Statistics summary on employees salary : "+doubleSummaryStatistics);

Output:

DoubleSummaryStatistics{count=4, sum=1400000.000000, min=50000.000000, average=350000.000000, max=450000.000000}

In the same way for Integer and Long values separate methods provided to get the statistics using summarizingInt() and summarizingLong().

public static <T> Collector<T,?,LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
public static <T> Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)


23. Collectors.teeing() Example


teeing() method is used to combine two Collectors output using a special merger function. This method is added in Java 12.

Syntax:

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)

The above syntax is difficult to understand and simplified as below.

public static Collector teeing(Collector collector1, Collector collector2, BiFunction merger)

Simple words, This method accepts two collectors and one merger function. Merger function takes outputs of two collectors and performs operations. Finally, returns some value or object or maybe collection.

Example:

Collectors teeing() example to find the average of first 100 numbers.

// Converting 1 to 100 numbers into Stream integer.
List<Integer> intList = new ArrayList<>();
IntStream.range(1, 100).forEach(i -> intList.add(i));

// Calling teeing method.
Double average = intList.stream().collect(
Collectors.teeing(Collectors.summingDouble(i -> i), Collectors.counting(), (sum, n) -> sum / n));

System.out.println("Average of first 100 numbers: " + average);

Collector 1: Finds the sum of 100 numbers from the stream.

Collectors.summingDouble(i -> i)

Collector 2: Finds count of numbers in the stream.

Collectors.counting()

Merger: Takes sum and count as input and does the average operation.

(sum, n) -> sum / n)

Output:

Average of first 100 numbers: 50.0


24. Complete Collectors Methods Examples


All Collectors utility class all methods with examples in a single program.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.DoubleSummaryStatistics;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Java 8 Collectors api Examples
 *
 * @author Venkatesh
 *
 */
public class Java8CollectorExamples {

public static void main(String[] args) {

// Creating a Employee List - Example

List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee(100, "Sundar", 47, "North America", 450000));
employeeList.add(new Employee(200, "Pichai", 25, "North America", 50000));
employeeList.add(new Employee(300, "Larry", 30, "Asia", 450000));
employeeList.add(new Employee(400, "Page", 59, "Africa", 450000));

// Collectors.toList() Example

List<String> namesList = employeeList.stream().map(e -> e.getName()).collect(Collectors.toList());
System.out.println(namesList);

// Collectors.toSet() Example
Set<String> regionSet = employeeList.stream().map(e -> e.getRegion()).collect(Collectors.toSet());
System.out.println(regionSet);
regionSet.add("hello");
System.out.println(regionSet);

// Collectors.toUnmodifiableSet() Example

Set<Double> unmodifiableSet = employeeList.stream().map(e -> e.getSal())
.collect(Collectors.toUnmodifiableSet());
System.out.println(unmodifiableSet);
// unmodifiableSet.add(10983d);

// employeeList.add(null);

Set<Employee> empSet = employeeList.stream().collect(Collectors.toUnmodifiableSet());

// Collectors.toUnmodifiableList(() Example
// employeeList.add(null);
List<Double> unmodifiableList = employeeList.stream().map(e -> e.getSal())
.collect(Collectors.toUnmodifiableList());
System.out.println(unmodifiableList);

// Collectors.toCollection() Example

List<String> namesLinkedList = employeeList.stream().map(e -> e.getName())
.collect(Collectors.toCollection(LinkedList::new));
System.out.println(namesLinkedList);

Set<String> regionTreeSet = employeeList.stream().map(e -> e.getRegion())
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(regionTreeSet);

// Collectors.toMap() Example
Map<Integer, Employee> empMap = employeeList.stream()
.collect(Collectors.toMap((e) -> e.getId(), Function.identity()));
System.out.println(empMap);

// with duplicate key. uncomment to work with toMap() for duplicate merger.
// employeeList.add(new Employee(400, "Larry Page", 59, "Africa", 450000));

Map<Integer, Employee> empDupMap = employeeList.stream()
.collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empDupMap);

// Collectors.toUnmodifiableMap() Example
Map<Integer, Employee> empUnmodifiedMap = employeeList.stream()
.collect(Collectors.toMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empUnmodifiedMap);

// Collector.summingInt() Example

int sumOfEmpIds = employeeList.stream().collect(Collectors.summingInt((Employee e) -> e.getId()));
System.out.println("Collectors.summingInt : " + sumOfEmpIds);

// Collector.summingInt() Example

double sumOfEmpSalss = employeeList.stream().collect(Collectors.summingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.summingDouble : " + sumOfEmpSalss);

// Collectors.averagingInt() / averagingLong() / averagingDouble() Examples

double avgOfEmpSalss = employeeList.stream().collect(Collectors.averagingDouble((Employee e) -> e.getSal()));
System.out.println("Collectors.averagingDouble avg sal: " + avgOfEmpSalss);

// Collectors.counting() Example

long count = employeeList.stream().collect(Collectors.counting());
System.out.println("Collectors.counting() : Count : " + count);

// Collectors.joining() Example
String joinedStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining());
System.out.println("joinedStr by using joining() method : " + joinedStr);

String joinedDelimiterStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining(" * "));
System.out.println("joinedDelimiterStr by using joining(Delimiter) method : " + joinedDelimiterStr);

String joinePrePostStr = employeeList.stream().map(e -> e.getName()).collect(Collectors.joining("*", "@", "|"));
System.out.println("joinePrePostStr by using joining(Delimiter) method : " + joinePrePostStr);

// Collectors.groupingBy() Example
Map<String, List<Employee>> groupByRegion = employeeList.stream()
.collect(Collectors.groupingBy((Employee e) -> e.getRegion()));
System.out.println("groupByRegion :: " + groupByRegion);

// groupingBy for set.
Map<String, Set<Employee>> groupByRegionSet = employeeList.stream()
.collect(Collectors.groupingBy((Employee e) -> e.getRegion(), Collectors.toSet()));
System.out.println("groupByRegionSet :: " + groupByRegionSet);

// Collectors.partitioningBy() Example
Map<Boolean, List<Employee>> partitionByAge = employeeList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 30));
System.out.println("partitionByAge :: " + partitionByAge);

// Set as Map value
Map<Boolean, Set<Employee>> partitionByAgeSet = employeeList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 30, Collectors.toSet()));

// LinkedList as Map value
Map<Boolean, LinkedList<Employee>> partitionByAgeLinedList = employeeList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 30, Collectors.toCollection(LinkedList::new)));

// TreeSet as Map value
/*
* Map<Boolean, TreeSet<Employee>> partitionByAgeTreeSet = employeeList.stream()
* .collect(Collectors.partitioningBy(e -> e.getAge() > 30,
* Collectors.toCollection(TreeSet::new)));
*/

// Collectors.toConcurrentMap() Example
Map<Integer, Employee> empConcurrentMap = employeeList.stream()
.collect(Collectors.toConcurrentMap((e) -> e.getId(), Function.identity()));
System.out.println(empConcurrentMap);

// with duplicate key. uncomment to work with toMap() for duplicate merger.
// employeeList.add(new Employee(400, "Larry Page", 59, "Africa", 450000));

Map<Integer, Employee> empDupConcurrentMap = employeeList.stream()
.collect(Collectors.toConcurrentMap((e) -> e.getId(), Function.identity(), (emp, sameEmp) -> sameEmp));
System.out.println(empDupMap);

// Collectors.filtering() Example
List<Employee> filteredEmpList = employeeList.stream()
.collect(Collectors.filtering((Employee e) -> e.getAge() > 30, Collectors.toList()));
System.out.println("Collectors.filtering() - filteredEmpList : " + filteredEmpList);

// Collectors.flatMapping() Example

LineItem lineItem1 = new LineItem(1001, "Item 1", new Date(2010, 07, 01));
LineItem lineItem2 = new LineItem(1002, "Item 2", new Date(2020, 07, 01));
LineItem lineItem3 = new LineItem(1003, "Item 3", new Date(2030, 07, 01));
LineItem lineItem4 = new LineItem(1004, "Item 4", new Date(2040, 07, 01));
LineItem lineItem5 = new LineItem(1005, "Item 5", new Date(2050, 07, 01));

List<LineItem> lineItemsList1 = new ArrayList<>();
lineItemsList1.add(lineItem1);
lineItemsList1.add(lineItem2);
lineItemsList1.add(lineItem3);
lineItemsList1.add(lineItem4);
lineItemsList1.add(lineItem5);

Customer customer1 = new Customer(100, "Customer 1", true, "M", lineItemsList1);

LineItem lineItem6 = new LineItem(2001, "Item 6", new Date(2010, 07, 01));
LineItem lineItem7 = new LineItem(2002, "Item 7", new Date(2020, 07, 01));

List<LineItem> lineItemsList2 = new ArrayList<>();
lineItemsList2.add(lineItem6);
lineItemsList2.add(lineItem7);

Customer customer2 = new Customer(200, "Customer 2", true, "F", lineItemsList2);

LineItem lineItem8 = new LineItem(2003, "Item 8", new Date(2040, 07, 01));
LineItem lineItem9 = new LineItem(3004, "Item 9", new Date(2070, 07, 01));
LineItem lineItem10 = new LineItem(3005, "Item 10", new Date(2200, 07, 01));

List<LineItem> lineItemsList3 = new ArrayList<>();
lineItemsList3.add(lineItem8);
lineItemsList3.add(lineItem9);
lineItemsList3.add(lineItem10);

Customer customer3 = new Customer(300, "Customer 3", true, "M", lineItemsList3);
Customer customer4 = new Customer(400, "Customer 4", true, "F", new ArrayList<LineItem>());

List<Customer> customersList = new ArrayList<>();
customersList.add(customer1);
customersList.add(customer2);
customersList.add(customer3);
customersList.add(customer4);

Map<String, Set<LineItem>> itemsByGender = customersList.stream()
.collect(Collectors.groupingBy((Customer c) -> c.getGender(),
Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.toSet())));
System.out.println("itemsByGender : " + itemsByGender);

Map<String, Long> itemsCountByGender = customersList.stream()
.collect(Collectors.groupingBy((Customer c) -> c.getGender(),
Collectors.flatMapping(customer -> customer.getLineItems().stream(), Collectors.counting())));
System.out.println("itemsCountByGender " + itemsCountByGender);

// Collectors.maxBy() Example
Comparator<Employee> empComparator = (e1, e2) -> e1.getId() - e2.getId();
Optional<Employee> empMaxOptional = employeeList.stream().collect(Collectors.maxBy(empComparator));
if (empMaxOptional.isPresent()) {
System.out.println("Max Emp : " + empMaxOptional.get());
}

// Collectors.minBy() Example
Optional<Employee> empminOptional = employeeList.stream().collect(Collectors.minBy(empComparator));
if (empminOptional.isPresent()) {
System.out.println("Min Emp : " + empminOptional.get());
}

// Collectors.reducing() Example
Optional<Employee> reducingOptinal = employeeList.stream()
.collect(Collectors.reducing(BinaryOperator.minBy(empComparator)));
if (reducingOptinal.isPresent()) {
System.out.println("Min Emp using reducing() method : " + reducingOptinal.get());
}

// Collectors.summarizingDouble() Example
DoubleSummaryStatistics doubleSummaryStatistics = employeeList.stream()
.collect(Collectors.summarizingDouble((Employee e) -> e.getSal()));
System.out.println("Statistics summary on employees salary : " + doubleSummaryStatistics);

// Converting 1 to 100 numbers into Stream integer.
List<Integer> intList = new ArrayList<>();
IntStream.range(1, 100).forEach(i -> intList.add(i));

// Calling teeing method.
Double average = intList.stream().collect(
Collectors.teeing(Collectors.summingDouble(i -> i), Collectors.counting(), (sum, n) -> sum / n));

System.out.println("Average of first 100 numbers: " + average);

}
}

Output:

[Sundar, Pichai, Larry, Page]
[Asia, Africa, North America]
[Asia, Africa, hello, North America]
[50000.0, 450000.0]
[450000.0, 50000.0, 450000.0, 450000.0]
[Sundar, Pichai, Larry, Page]
[Africa, Asia, North America]
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
Collectors.summingInt : 1000
Collectors.summingDouble : 1400000.0
Collectors.averagingDouble avg sal: 350000.0
Collectors.counting() : Count : 4
joinedStr by using joining() method : SundarPichaiLarryPage
joinedDelimiterStr by using joining(Delimiter) method : Sundar * Pichai * Larry * Page
joinePrePostStr by using joining(Delimiter) method : @Sundar*Pichai*Larry*Page|
groupByRegion :: {Asia=[Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], Africa=[Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]], North America=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]]}
groupByRegionSet :: {Asia=[Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], Africa=[Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]], North America=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0]]}
partitionByAge :: {false=[Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]], true=[Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
{400=Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0], 100=Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], 200=Employee [id=200, name=Pichai, age=25, region=North America, sal=50000.0], 300=Employee [id=300, name=Larry, age=30, region=Asia, sal=450000.0]}
Collectors.filtering() - filteredEmpList : [Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0], Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]]
itemsByGender : {F=[LineItem [itemId=2001, itemName=Item 6, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=2002, itemName=Item 7, manfacturedDate=Sun Aug 01 00:00:00 IST 3920]], M=[LineItem [itemId=1001, itemName=Item 1, manfacturedDate=Mon Aug 01 00:00:00 IST 3910], LineItem [itemId=1005, itemName=Item 5, manfacturedDate=Tue Aug 01 00:00:00 IST 3950], LineItem [itemId=1004, itemName=Item 4, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=1002, itemName=Item 2, manfacturedDate=Sun Aug 01 00:00:00 IST 3920], LineItem [itemId=1003, itemName=Item 3, manfacturedDate=Fri Aug 01 00:00:00 IST 3930], LineItem [itemId=2003, itemName=Item 8, manfacturedDate=Thu Aug 01 00:00:00 IST 3940], LineItem [itemId=3004, itemName=Item 9, manfacturedDate=Sat Aug 01 00:00:00 IST 3970], LineItem [itemId=3005, itemName=Item 10, manfacturedDate=Sun Aug 01 00:00:00 IST 4100]]}
itemsCountByGender {F=2, M=8}
Max Emp : Employee [id=400, name=Page, age=59, region=Africa, sal=450000.0]
Min Emp : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]
Min Emp using reducing() method : Employee [id=100, name=Sundar, age=47, region=North America, sal=450000.0]
Statistics summary on employees salary : DoubleSummaryStatistics{count=4, sum=1400000.000000, min=50000.000000, average=350000.000000, max=450000.000000}
Average of first 100 numbers: 50.0