Last time: Java8 ~ forEach and lambda expression ~
Next is the Stream API edition.
It is an API introduced from Java8 that can describe processing (aggregation operation) for arrays and collections. Follow the procedure below for a set of elements.
It's completely different from how to write up to Java7, so I'll summarize it using an example.
--The order file has the item code and order quantity --The product master file has the product code and product name. --Create a program that reads a certain order file and outputs the product code, product name extracted from the product master, and order quantity to the file. --If there is no product code record in the product master, leave the product name in the output file blank. --Sort in ascending order of product code
--CSV format --Do not include header lines
--File items are `` product code, order quantity''
order.csv
BBJ001,300
AES010,20
BBJ005,100
BBJ001,50
DIH999,10
AES010,150
--File items should be `` product code, product name''
item.csv
BBJ001,Ballpoint pen black
BBJ005,Ballpoint pen red
AES010,eraser
--CSV format --Do not include header lines --File items should be `` product code, product name, quantity''
order_collected.csv
DIH999,,10
BBJ005,Ballpoint pen red,100
BBJ001,Ballpoint pen black,300
BBJ001,Ballpoint pen black,50
AES010,eraser,20
AES010,eraser,150
If you drop this example into your code, I think the procedure would be like this.
Stream <String> java.nio.file.Files # lines (Path)
to generate a Stream with all the lines in the file.
Before Java7, Reader etc. were generated and read one by one.
Java7 version file reading
BufferedReader reader = new BufferedReader(new FileReader("files/stream/item.csv"));
String line = null;
while ((line = reader.readLine()) != null) {
//Various processing for one line
}
Now this.
Stream API version file reading
Stream<String> itemStream = Files.lines(Paths.get("files/stream/item.csv"));
Decompose each element of the Stream created in the previous step.
Stream<String[]> itemStream2 = itemStream .map(line -> line.split(","));
<R> Stream<R> map(Function<? super T,? extends R> mapper)
The intermediate operation map
returns a Stream that is the result of applying an argument lambda expression to each element of the Stream.
This time we are passing the lambda expression line-> line.split (",")
.
Process line.split (",")
for each element line
of Stream <String> itemStream
and stream of type Stream <String []>
Has been generated.
BBJ001,Ballpoint pen black// ⇒ [BBJ001,Ballpoint pen black]
BBJ005,Ballpoint pen red// ⇒ [BBJ005,Ballpoint pen red]
・ ・ ・
The important thing is that ** Stream intermediate operations return Stream **. Intermediate operations and termination operations can be performed on the results of intermediate operations.
If it is Stream as it is, it will be difficult to handle with the product name assignment in the later procedure, so convert it to Map.
Map<String, String> items = itemStream2.collect(Collectors.toMap(item -> item[0], item -> item[1]));
<R,A> R collect(Collector<? super T,A,R> collector)
The termination operation collect
returns the result of applying a certain rule (Collector) to each element of Stream.
Frequently used rules are available in Collectors
.
Here, Collector toMap (<Lambda expression that creates a Map Key>, <Lambda expression that creates a Map Value>)
that returns a Map is used.
Since itemStream2 is a Stream of String []
, we generated a Map with the 0th element of the array as the Key and the 1st element as the Value.
** Intermediate operations of Stream return Stream **, so you can write a chain from the creation of Stream to the conversion to Map.
//Read product name file
Map<String, String> items = Files.lines(Paths.get("files/stream/item.csv") // Stream<String>
//Convert one line of file to an array(Preparation for Map)
.map(line -> line.split(",")) // Stream<String[]>
//Convert to Map
.collect(Collectors.toMap(item -> item[0], item -> item[1])); // Map<String, String>
Next, we will start processing the order file. It is the same as the product name master up to the point where one line of data is decomposed.
Stream<String> orderStream = Files.lines(Paths.get("files/stream/order.csv"))
Stream<OrderDto> orderStream2 = orderStream
//Convert one line of file to an array(Preparation for Dto)
.map(line -> line.split(","))
//Convert array to delivery Dto
.map(array -> makeOrder(array));
After splitting with ",", I mapped it to OrderDto with makeOrder (String [])
.
private static OrderDto makeOrder(String[] array) {
OrderDto order = new OrderDto();
int idx = 0;
order.setItemCode(array[idx++]);
order.setOrderNum(Integer.parseInt(array[idx++]));
return order;
}
Sort in ascending order of product code.
Stream<OrderDto> orderStream3 = orderStream2.sorted((item1, item2) -> item1.getItemCode().compareTo(item2.getItemCode()));
Stream<T> sorted(Comparator<? super T> comparator)
Comparator
defines comparison rules between objects Functional interface.
If you implement the interface Comparable
, it has no argumentsYou can also use
sorted ()
.
You can set a default sort order in the interface Comparable
and use Comparator
to redefine it concisely with a lambda expression when needed.
Search the product map by product code, and if it hits, fill in the product name, and if it does not hit, fill in the empty string.
Stream<OrderDto> orderStream4 = orderStream2.map(order -> {
order.setItemName(Optional.ofNullable(items.get(order.getItemCode())).orElse(""));
return order;
});
Use map again. Optional is not explained in detail here, but it is used as a function to determine the default value when it is Null.
Now that the data is complete, output it to a file.
try (BufferedWriter writer = new BufferedWriter(new FileWriter("files/stream/order_collected.csv")) ) {
orderList.forEach(order -> {
try {
writer.write(makeLine(order));
writer.newLine();
} catch (IOException e) {e.printStackTrace();return;}
});
}
void forEach(Consumer<? super T> action)
The termination operation forEach
used in the previous explanation of the lambda expression executes the operation specified by the argument for each element.
public class StreamMain {
public static void main(String[] args) {
try (Stream<String> orderStream = Files.lines(Paths.get("files/stream/order.csv"));
Stream<String> itemStream = Files.lines(Paths.get("files/stream/item.csv"))){
//Read order file
Map<String, String> items = itemStream
//Convert one line of file to an array(Preparation for Map)
.map(line -> line.split(","))
//Convert to Map
.collect(Collectors.toMap(item -> item[0], item -> item[1]));
//Read product name file
Stream<OrderDto> orderList = orderStream
//Convert one line of file to an array(Preparation for Dto)
.map(line -> line.split(","))
//Convert array to delivery Dto
.map(array -> makeOrder(array))
//sort
.sorted((item1, item2) -> item1.getItemCode().compareTo(item2.getItemCode()))
//matching
.map(order -> {
order.setItemName(Optional.ofNullable(items.get(order.getItemCode())).orElse(""));
return order;
});
//output
try (BufferedWriter writer = new BufferedWriter(new FileWriter("files/stream/order_collected.csv")) ) {
orderList.forEach(order -> {
try {
writer.write(makeLine(order));
writer.newLine();
} catch (IOException e) {e.printStackTrace();return;}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static String makeLine(OrderDto order) {
StringBuilder line = new StringBuilder();
line.append(order.getItemCode());
line.append(',');
line.append(order.getItemName());
line.append(',');
line.append(order.getOrderNum());
return line.toString();
}
private static OrderDto makeOrder(String[] array) {
OrderDto order = new OrderDto();
int idx = 0;
order.setItemCode(array[idx++]);
order.setOrderNum(Integer.parseInt(array[idx++]));
return order;
}
}
In solving the example, I first organized the procedure.
- Read the product name master
- Break down one line into product code and product name
- key: product code, value: map of product name
Probably, in Java7 or earlier code, even Map is generated at the time of for loop to read the product name master.
Java7
for (/*Set of elements*/) {
//Various processing for one element
}
By the way, the Stream API operates on a set of elements.
Typical operations are map
(convert an element to another element), filter
(extract the one that meets the conditions from the elements / unused this time), `` sortedFor example,
(sort elements).
Furthermore, since the return value of the intermediate operation is Stream, it is suitable to first create a set of elements and then operate continuously.
Java8
Stream obj = //Convert a set of elements to Stream
//Converting elements in obj
.map(/*Conversion rules*/)
//Filtering
.filter(/*Extraction rules*/);
When using the Stream API, it seems that you should think about the procedure so that it is a combination of simple operations on the elements.
The following article was very helpful. Write Java8-like code in Java8
I wanted to find the total order quantity for each product code, but I gave up because I couldn't find out how to write it well. I will leave the memorial code of the version I forced, so please let me know if you have any good hands.
//Read product name file
Stream<OrderDto> orderList = orderStream
//Convert one line of file to an array(Preparation for Dto)
.map(line -> line.split(","))
//Convert array to delivery Dto
.map(array -> makeOrder(array));
//Group by product code for aggregation
Map<String, List<OrderDto>> grouping = orderList.collect(Collectors.groupingBy(order->order.getItemCode()));
//Sum the order quantity for each grouped element
orderList = grouping.values()
.stream()
.map(orders->{
//Pick up any one of the elements
OrderDto order = orders.stream().findAny().get();
//Store the total number of all elements in the order quantity
order.setOrderNum(orders.stream().mapToInt(oo ->oo.getOrderNum()).sum());
return order;
})
//sort
.sorted((item1, item2) -> item1.getItemCode().compareTo(item2.getItemCode()))
//Set product name
.map(order -> {
order.setItemName(Optional.ofNullable(items.get(order.getItemCode())).orElse(""));
return order;
});
Recommended Posts