Write Java8-like code in Java8

Overview (what are you talking about)

--When I'm reviewing the code, I'm using Java8 features (Stream API, Optional, etc.), but I often see code that is written in the same way as before Java7. ――I want everyone to write __Java8-like code __. It's a waste if not. (* The standard of "Java 8-ish" is relatively (quite?) Personal opinion is included) ――It's hard to convey even if you use all the words, so let's make an example. __ ← this __ ――By the way, let's make an example of "forcibly using Java 8 features, which makes it worse."

Target audience

--People who are writing code in Java8 but can't get out of it because the writing style before Java7 is so ingrained. ――People who use Stream API or Optional for anything because they have a strong feeling that "for and null check should never be used"

Notice The content in this article is nothing new, and some languages other than Java can be written in a more sophisticated way. This article is for "people who have migrated from Java 7 or earlier to Java 8 but are not accustomed to writing using Stream and Optional".

Patterns that can be more Java 8-like

It is assumed that there is a product object like this and some processing is performed on it.

Item.java


//Product class
public class Item {
  String name; //Product name
  BigDecimal price; //Product price
  String currency; //currency in the price field("JPY"And"USD")
}

Just replaced the extended for statement with forEach

Suppose you want to do something like "convert some data and store it in another list". Before Java7, it looks like ↓.

How to write before java7


//Convert Japanese Yen products to USD
BigDecimal jpyToUsd = BigDecimal.valueOf(100);

List<Item> usdItems = new ArrayList<>();

for(Item jpyItem : jpyItems) {
    Item usdItem = new Item();
    usdItem.setName(jpyItem.getName());
    usdItem.setPrice(jpyItem.getPrice().multiply(jpyToUsd));
    usdItem.setCurrency("USD");
    usdItems.add(usdItem);
}

So, as a result of being told, "Because it's Java 8, do it with Stream without using for", it becomes like this.

for Each only


BigDecimal jpyToUsd = BigDecimal.valueOf(100);

List<Item> usdItems = new ArrayList<>();

jpyItems.forEach(jpyItem -> {
    Item usdItem = new Item();
    usdItem.setName(jpyItem.getName());
    usdItem.setPrice(jpyItem.getPrice().multiply(jpyToUsd));
    usdItem.setCurrency("USD");
    usdItems.add(usdItem);
});

What you are doing is no different from the extended for statement. Rewriting this with the "intermediate operation" and "termination operation" of Stream in mind, it becomes like this.

Java8-ish


BigDecimal jpyToUsd = BigDecimal.valueOf(100);

//Realized by combining map and collect without using forEach
List<Item> usdItems = jpyItems.stream()
        .map(jpyItem -> {
            Item usdItem = new Item();
            usdItem.setName(jpyItem.getName());
            usdItem.setPrice(jpyItem.getPrice().multiply(jpyToUsd));
            usdItem.setCurrency("USD");

            return usdItem;
        })
        .collect(Collectors.toList());

(* The countermeasures for the fact that the contents of the map are fat will be described later)

By doing this

--Transforming one data to generate another (map (...)) --The converted data is collected into a Collection (collect (...))

It becomes clearer, which improves readability. forEach can do anything and is useful, but consider if there is another suitable method.

lambda is big

In the previous example, the lambda in the map is large, the method chain of the Stream becomes long, the whole is hard to see, and the readability is low.

If you ask, "Then, should I take out lambda as a variable?"

Variableize lambda


Function<Item,Item> convertToUsdItem = jpyItem -> {
    Item usdItem = new Item();
    usdItem.setName(jpyItem.getName());
    usdItem.setPrice(jpyItem.getPrice().multiply(jpyToUsd));
    usdItem.setCurrency("USD");

    return usdItem;
};

List<Item> usdItems = jpyItems.stream()
    .map(convertToUsdItem)
    .collect(Collectors.toList());

Stream is still refreshing, but it's less testable when you want to test the processing of convertToUsdItem, so it's more convenient to use a normal method, and it's easier to read personally. (Readability may be a matter of taste)

Cut out as a normal method


public Item convertToUsdItem(Item jpyItem) {
    Item usdItem = new Item();
    usdItem.setName(jpyItem.getName());
    usdItem.setPrice(jpyItem.getPrice().multiply(jpyToUsd));
    usdItem.setCurrency("USD");

    return usdItem;
}

List<Item> usdItems = jpyItems.stream()
    .map(this::convertToUsdItem)
    .collect(Collectors.toList());

Using method references, you can write a Stream as simply as lambda.

I'm using only some fields of the target object, but I haven't map

For example, when considering a process such as "output the product name as standard for products whose product name starts with" A "", I sometimes see a code like this.

python


items.steam()
    .filter(item -> item.getName().startsWith("A"))
    .forEach(item -> System.out.println(item.getName()));

Since you want to use only the product name, you do not need to route the item object to the end, so it is easier to follow up by extracting the name field with map first.

Extract only name with map and process


items.stream()
    .map(Item::getName)
    .filter(name -> name.startsWith("A"))
    .forEach(System.out::println);

By doing this,

—— You can understand at an early stage that “only product names are handled” --Method references can make your code simpler

There are merits such as.

I made a method that returns null and checked for null by its caller

When processing the return value of findFirst of Stream API or when using the method of the library for Java8 that returns Optional, you have to use Optional regardless of whether or not. However, when I was just learning Java8, it seems that when I try to implement a method myself, I often don't think "let's set the return type to Optional".

For example, suppose you create a method like "Get exchange rate information from the cache and return the default value (1) if the cache does not exist".

Get exchange rates from cash(null version)



public RateInfo getFromCache(String fromCurrency, String toCurrency) {
    //Returns its RateInfo object if it exists in the cache, null otherwise
}

public BigDecimal retrieveRate(String fromCurrency, String toCurrency) {

    RateInfo rateInfo = getFromCache(fromCurrency, toCurrency);

    if(rateInfo != null) {
        return rateInfo.getRate();
    }

    return BigDecimal.ONE;
}

Here, the getFromCache method behaves as if it" returns its RateInfo object if it exists in the cache, or null if it does not exist. " Therefore, an if statement for null check is required at the caller. And sometimes I don't check the null and it gets slimy.

That's why Optional comes into play here.

Get exchange rates from cash(Optional version)


public Optional<RateInfo> getFromCache(String fromCurrency, String toCurrency) {
    //The result of trying to retrieve the RateInfo object from the cache is Optional<RateInfo>Return as an object
}

public BigDecimal retrieveRate(String fromCurrency, String toCurrency) {
    Optional<RateInfo> rateInfo = getFromCache(fromCurrency, toCurrency);

    return rateInfo.map(RateInfo::getRate).orElse(BigDecimal.ONE);
}

By doing this,

--Null check or object existence check can be forced on the method caller --You can forcibly retrieve the value without checking with rateInfo.get (), but such code can be played by static analysis. --You can write code neatly by using methods such as ʻorElse`

There are merits such as.

Patterns that should not be forced to use Java 8 features

__for and null check A case that is easy to fall into when it looks like a man who absolutely kills __. Java8's Stream and Optional are often not as cool as those in other languages, and I'm addicted to trying to cover them.

Replace for statement with index with Stream

For example

for statement with index


for(int i=0; i < items.size(); i++) {
    System.out.println(
        String.valueOf(i) + " : " + items.get(i).getName());
}

As a result of enthusiasm about processing like "I'll do without a for statement!"

Do your best with Stream


//Loop counter generation with IntStream
IntStream.range(0, items.size())
    .forEach(i -> System.out.println(
        String.valueOf(i) + " : " + items.get(i).getName());

//Do your best with Atomic Integer and for Each
AtomicInteger i = new AtomicInteger();
items.forEach(item -> System.out.println(
        String.valueOf(i.getAndIncrement()) + " : " + item.getName());

It becomes like. The former is originally intended to be expressed as Stream processing of items, but since it is accessed using get (i) in the IntStream for index generation, it is not different from a normal for statement and is difficult to read. (I used to write this way, but I stopped it for the reason mentioned above)

The latter is the latter, and "use AtomicInteger because primitive type int cannot be incremented in lambda" is not recommended because it seems to deviate from the original usage of AtomicInteger.

Collection operation with index in Java ([Kotlin this](http://qiita.com/opengl-8080/items/36351dca891b6d9c9687#indexed%E7%B9%B0%E3%82%8A%E8%BF%94%E3] % 81% 97% E5% 87% A6% E7% 90% 86% E3% 81% AB% E3% 83% AB% E3% 83% BC% E3% 83% 97% E3% 82% A4% E3% 83 % B3% E3% 83% 87% E3% 83% 83% E3% 82% AF% E3% 82% B9% E3% 82% 82% E6% B8% A1% E3% 81% 99)) I wish I could use it Unfortunately, it hasn't been implemented yet, so for now it seems better to use the for statement obediently rather than forcibly using the Stream API.

ʻOptional.ofNullable` just for null checking

Some older libraries and methods available before Java 7 can return null as a return value. For such a method, if you just want to check if the return value is null, and you feel like "I don't write ʻif (a! = Null)` for anything! ", The following code is displayed. It's done.

Evaluate by wrapping the return value in Optional



// getText()Can return null
String text = getText();

//Wrap it in Optional and check it. Do not use the value of the contents
if(Optional.ofNullable(text).isPresent()) {
    return "OK";   
}

return "NG";

ʻOptional.isPresent () It's okay to use it, but it's a bit more annoying because the code is longer than the normal null check. I think that ʻif (text! = Null) is fine here.

A little worrisome part-how to use like the default argument

Optional is mainly used as a "method return value" as a design concept, and other uses (for example, used as an argument type) are not recommended. r.f stackoverflow - Why java.util.Optional is not Serializable, how to serialize the object with such fields

Certainly, even if you use the Optional type as an argument, the optional object that you passed may be null, so it is not very delicious. However, there is something about the argument, "Isn't it possible to use this kind of Optional?" That's how it's used like __default argument __.

Express a pseudo default argument with Optional


//Returns the target product converted to the price of the specified currency
public Item convert(Item item, String toCurrency) {
     //Use USD if no currency is specified
     String currency = Optional.ofNullable(toCurrency).orElse("USD");

     ...
     ...
}

There may be an opinion that "overload can be used", but overload cannot be used for the implementation method of Interface of the third party library specified as a callback method, so I am considering how to use it like this.

Postscript (2017/08/30)

As pointed out in Comment, it may be better to use another method.

Use the "default value if null" method


//Returns the target product converted to the price of the specified currency
public Item convert(Item item, String toCurrency) {
     // java.util.Objects.toString
     String currency1 = Objects.toString(toCurrency,"USD");

     // org.apache.commons.lang3.ObjectUtils.defaultIfNull
     String currency2 = ObjectUtils.defaultIfNull(toCurrency, "USD");

     ...
     ...
}

Finally

――It is interesting to explore various writing styles by considering that both Stream API and Optional are not "just adding method classes" but "writing styles that are more efficient and easier to understand than before". ――However, there are many parts that are not cool at the time of Java 8, so it is important to search for “just right usage” instead of using it blindly.

Recommended Posts

Write Java8-like code in Java8
Write flyway callbacks in Java
Java in Visual Studio Code
Guess the character code in Java
Java Spring environment in vs Code
Write test code in Spring Boot
Script Java code
Java code TIPS
Partization in Java
Changes in Java 11
Rock-paper-scissors in Java
Java sample code 04
All same hash code string in Java
Java sample code 01
Java character code
Pi in Java
[Mac] Install Java in Visual Studio Code
FizzBuzz in Java
Add --enable-preview option in Java in Visual Studio Code
Technology for reading Java source code in Eclipse
Static code analysis with Checkstyle in Java + Gradle
Code to escape a JSON string in Java
Try using Sourcetrail (win version) in Java code
Write Selenium Java binding code using Silk WebDriver
[AtCoder Problem-ABC001] C-Do wind observation in Java [Code]
How to write Java String # getBytes in Kotlin?
[Java beginner's anguish] Hard-to-test code implemented in Junit
[Mac] Install Java in Visual Studio Code (VS Code)
[java] sort in list
Read JSON in Java
Interpreter implementation in Java
Make Blackjack in Java
Rock-paper-scissors app in Java
Constraint programming in Java
Put java8 in centos7
Combine arrays in Java
"Hello World" in Java
Callable Interface in Java
Comments in Java source
Azure functions in java
Format XML in Java
Simple htmlspecialchars in Java
Boyer-Moore implementation in Java
Hello World in Java
Use OpenCV in Java
webApi memorandum in java
Type determination in Java
Ping commands in Java
Various threads in java
Zabbix API in Java
ASCII art in Java
Compare Lists in Java
POST JSON in Java
Express failure in Java
Implement CustomView in code
Create JSON in Java
Date manipulation in Java 8
What's new in Java 8
Use PreparedStatement in Java
What's new in Java 9,10,11
Parallel execution in Java