--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."
--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".
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")
}
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.
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.
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.
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.
__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.
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.
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.
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.
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");
...
...
}
――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