← Java tutorials

LESSON 30 / 40 · FREE TUTORIAL

The Block Data Pipeline

Use Lambda and Stream to generate mining briefing

100–120 minutesThis chapter uses JDK 21 and does not require the preview function.Aggregation and procedure practice

WHAT YOU WILL MAKE

Create mining statistics briefing MiningReport

The mine car brings a batch of records: screening, conversion, sorting, statistics, each through a section of pipeline. Organize the data with clear behavioral rules, and then calculate the answers by hand so that there is evidence for each number in the briefing. Prerequisites: List, Map, record, method reference and loop. Today, let the mining data ride on the mine cart and pass through screening, processing, sorting and statistics stations in sequence.

Before you start: It is recommended to complete Lessons 24–29 first

Parameter arrow expression writes temporary rules
  1. LambdaPassing behavior as value
  2. PredicateOne parameter, returns true or false
  3. BiFunctionTwo parameters, return result

01 / 16

Before starting: predict first, then let the program answer

This chapter uses JDK 21 and does not require the preview function. The story is borrowed from the block world, and the example runs on the ordinary Java console; if the values ​​​​and rules are course models, they will be clearly stated.

Each common example is a complete independent file, saved according to the file name marked on the box. Place each example in a different folder to avoid duplicate names of auxiliary classes. When the complete project is divided into sections, they are connected sequentially in the same file.

Terminal command: replace the Chinese placeholder with the actual file name and class name

javac -encoding UTF-8 ExampleFile.java
java ExampleClass
  • Prediction: Write the expected output first, or draw out the changes in references and data.
  • Verification: Check line by line after running, and then only change one condition.
  • Explanation: Explain whether the results come from Java rules, course contracts, or input data.

02 / 16

Lambda is a short piece of behavior

Lambda is not a new method, but a shorter transitive behavior. ore -> ore.length() >= 2 means receiving ore and then answering whether the condition is true.

Parameter arrow expression writes temporary rules
  1. LambdaPassing behavior as value
  2. PredicateOne parameter, returns true or false
  3. BiFunctionTwo parameters, return result

L30Example01.java · Complete independent example

public class L30Example01 {
    public static void main(String[] args) {
        java.util.function.Predicate<String> longName = name -> name.length() >= 2;
        java.util.function.BiFunction<Integer, Integer, Integer> sum = (x, y) -> x + y;
        System.out.println(longName.test("diamond"));
        System.out.println(sum.apply(3, 4));
    }
}

Output

true
7

03 / 16

Stream pipeline is divided into three sections

Stream describes the data processing method: source provides elements, intermediate operation building steps, and terminates the operation request result. It does not save a copy of the data that can be read over and over again like a List does.

The source passes through intermediate operations and is finally initiated by the termination operation
  1. SourceList.stream provides elements
  2. Intermediate operationsfilter、map、sorted
  3. Terminate operationtoList、sum、count

L30Example02.java · Complete independent example

import java.util.*;
public class L30Example02 {
    public static void main(String[] args) {
        List<String> ores = List.of("Coal", "diamond", "Iron ore");
        var pipeline = ores.stream().filter(name -> name.length() >= 2);
        System.out.println(pipeline.toList());
    }
}

Output

[Coal, diamond, Iron ore]
Acceptance comparison table
PhaseExampleEffect
Sourceores.streamProvide elements
Intermediate operationsfilter map sortedDescription processing
Terminate operationtoList sum countProduce results

04 / 16

filter only retains matching elements

filter expresses retention conditions: elements that meet the conditions enter the result. Filtering does not delete the original list members, nor should the source be secretly modified when judging conditions.

Predicate returns true records and continues to move forward
  1. filterSelect elements by true or false
  2. New resultsCatch with termination operation
  3. Original Listwill not be deleted as a result

L30Example03.java · Complete independent example

import java.util.*;
record MinedBlock(String ore, int amount, int value) {
    MinedBlock {
        if (ore == null || ore.isBlank() || amount < 0 || amount > 1_000_000
                || value < 0 || value > 1_000_000)
            throw new IllegalArgumentException("Check mineral name, quantity and unit price");
        ore = ore.strip();
    }
}
public class L30Example03 {
    public static void main(String[] args) {
        List<MinedBlock> blocks = List.of(new MinedBlock("Coal", 2, 1),
            new MinedBlock("diamond", 3, 10));
        var rare = blocks.stream().filter(b -> b.value() >= 5).toList();
        System.out.println(rare.size());
        System.out.println(blocks.size());
    }
}

Output

1
2

05 / 16

map changes elements into another form

map like workbench recipe. Each input element is converted once, the number usually remains the same, but the type can change.

Records can become names, tags or values
  1. mapConvert elements into new forms
  2. Return typeCan differ from source
  3. HashMapIt is a key-value container, and the concept is different

L30Example04.java · Complete independent example

import java.util.*;
public class L30Example04 {
    public static void main(String[] args) {
        List<String> ores = List.of("diamond", "iron");
        System.out.println(ores.stream().map(s -> s.toUpperCase(Locale.ROOT)).toList());
        System.out.println(ores.stream().map(String::length).toList());
    }
}

Output

[DIAMOND, IRON]
[7, 4]

06 / 16

sorted control reporting order

Natural ordering is suitable for strings and numbers. Complex records should be clearly sorted by which field, and a second rule can be added when the scores are the same.

Comparator describes comparison rules
  1. Sorting fieldWhether to specify unit price or subtotal
  2. Second ruleSame points according to mineral name
  3. Original sequencesorted does not modify the source

L30Example05.java · Complete independent example

import java.util.*;
record MinedBlock(String ore, int amount, int value) {
    MinedBlock {
        if (ore == null || ore.isBlank() || amount < 0 || amount > 1_000_000
                || value < 0 || value > 1_000_000)
            throw new IllegalArgumentException("Check mineral name, quantity and unit price");
        ore = ore.strip();
    }
}
public class L30Example05 {
    public static void main(String[] args) {
        List<MinedBlock> blocks = List.of(new MinedBlock("B", 1, 5),
            new MinedBlock("A", 1, 5), new MinedBlock("C", 1, 10));
        var names = blocks.stream()
            .sorted(Comparator.comparingInt(MinedBlock::value).reversed()
                .thenComparing(MinedBlock::ore))
            .map(MinedBlock::ore).toList();
        System.out.println(names);
        System.out.println(blocks.get(0).ore());
    }
}

Output

[C, A, B]
B

07 / 16

Numerical flow is responsible for summation and averaging

The object stream must first be mapped into a numerical stream before it can be directly summed. You can use mapToInt for quantities, and mapToLong for potentially large values; determine the value range first every time.

mapToInt converts the object into an integer stream
  1. long multiplicationConvert first and then calculate
  2. sumThe empty value stream is 0
  3. averageThere may be no result, use orElse

L30Example06.java · Complete independent example

import java.util.*;
record MinedBlock(String ore, int amount, int value) {
    MinedBlock {
        if (ore == null || ore.isBlank() || amount < 0 || amount > 1_000_000
                || value < 0 || value > 1_000_000)
            throw new IllegalArgumentException("Check mineral name, quantity and unit price");
        ore = ore.strip();
    }
}
public class L30Example06 {
    public static void main(String[] args) {
        List<MinedBlock> blocks = List.of(new MinedBlock("diamond", 3, 10),
            new MinedBlock("Iron", 7, 3));
        long total = blocks.stream().mapToLong(b -> (long) b.amount() * b.value()).sum();
        System.out.println(total);
        System.out.println(blocks.stream().mapToInt(MinedBlock::amount).average().orElse(0));
        System.out.println(List.<MinedBlock>of().stream()
            .mapToLong(MinedBlock::amount).sum());
    }
}

Output

51
5.0
0

08 / 16

distinct limit and skip

Sort first and then limit to get the top three; first limit and then sort will only sort the first three of the original list. Pipeline order is algorithmic order.

Deduplication and interception shaping final report
  1. distinctDeduplication according to equality rules
  2. sorted and limitChange the answer in sequence
  3. skipSkip the specified amount

L30Example07.java · Complete independent example

import java.util.*;
public class L30Example07 {
    public static void main(String[] args) {
        List<Integer> values = List.of(3, 1, 3, 2);
        System.out.println(values.stream().distinct().sorted().limit(2).toList());
        System.out.println(values.stream().limit(2).sorted().toList());
        System.out.println(values.stream().distinct().sorted().skip(1).toList());
    }
}

Output

[1, 2]
[1, 3]
[2, 3]

09 / 16

toList catches the final result

Java 21's Stream.toList returns an unmodifiable list. It can be read, but adding or removing it will fail.

The pipeline result is usually a new unmodifiable list
  1. toListReturn to unmodifiable list
  2. Variable copynew ArrayList<>(result)
  3. Element statusContainer read-only does not mean depth is immutable

L30Example08.java · Complete independent example

import java.util.*;
public class L30Example08 {
    public static void main(String[] args) {
        List<String> result = List.of("Coal", "diamond").stream().toList();
        List<String> editable = new ArrayList<>(result);
        editable.add("Iron");
        System.out.println(result);
        System.out.println(editable);
    }
}

Output

[Coal, diamond]
[Coal, diamond, Iron]

BadToList.java · Intentional error example

import java.util.*;
public class BadToList {
    public static void main(String[] args) {
        var result = List.of("Coal").stream().toList();
        result.add("Iron");
    }
}

10 / 16

Stream can only be consumed once

Stream is like a one-time minecart route. After reaching the end point, you cannot go back to the starting point and run again.

The stream has been closed after the termination operation.
  1. Disposable StreamCan no longer be consumed after termination
  2. Retain sourceList can repeatedly create new streams
  3. Report againRestart from stream()

BadStreamReuse.java · Intentional error example

import java.util.*;
public class BadStreamReuse {
    public static void main(String[] args) {
        var stream = List.of("Coal", "Iron").stream();
        stream.count();
        stream.toList();
    }
}

L30Example09.java · Complete independent example

import java.util.*;
public class L30Example09 {
    public static void main(String[] args) {
        List<String> ores = List.of("Coal", "Iron");
        System.out.println(ores.stream().count());
        System.out.println(ores.stream().toList());
    }
}

Output

2
[Coal, Iron]

11 / 16

Avoid modifying external collections

If the external List is secretly modified in forEach, the data flow direction is divided into two. Returning results is easier to read and test than hiding side effects.

Pipeline steps should return results based on input
  1. Avoid hidden side effectsReturn result expression collection
  2. Do not modify the sourceThe traversal contract requires no interference
  3. forEachSuitable for actions that must be performed

L30Example10.java · Complete independent example

import java.util.*;
public class L30Example10 {
    public static void main(String[] args) {
        List<String> ores = List.of("Coal", "diamond", "Iron ore");
        List<String> rare = ores.stream().filter(s -> s.length() >= 2).toList();
        System.out.println(rare);
    }
}

Output

[Coal, diamond, Iron ore]

12 / 16

Complex Lambda is extracted as a method

After Lambda exceeds one or two lines, extract it into a named method. The main process is clearer and the rules can be tested individually.

Name Description Rules Tests can be run individually
  1. Naming methodExplain complex selection rules
  2. Method referencePut named actions into pipes
  3. Independent testingCan be verified without the entire report

L30Example11.java · Complete independent example

import java.util.*;
record MinedBlock(String ore, int amount, int value) {
    MinedBlock {
        if (ore == null || ore.isBlank() || amount < 0 || amount > 1_000_000
                || value < 0 || value > 1_000_000)
            throw new IllegalArgumentException("Check mineral name, quantity and unit price");
        ore = ore.strip();
    }
}
public class L30Example11 {
    static boolean isRare(MinedBlock b) {
        return b != null && b.value() >= 5;
    }
    public static void main(String[] args) {
        List<MinedBlock> blocks = List.of(new MinedBlock("Coal", 2, 1),
            new MinedBlock("diamond", 3, 10));
        System.out.println(blocks.stream().filter(L30Example11::isRare)
            .map(MinedBlock::ore).toList());
    }
}

Output

[diamond]

13 / 16

Challenge Blueprint Mining Statistical Briefing

The challenge program is called MiningReport. Each record saves the mineral name, quantity, and single block value, and the Stream generates the total blocks, rare list, total value, and highest value mineral.

Filter, Transform, Sort and Aggregate to Compose Report
  1. Total quantity14+7+4+3+20=48
  2. Total value14+21+20+30+0=85
  3. Highest subtotalDiamond record is 30

Acceptance conditions

  • The total number of blocks is 48.
  • Rare minerals are arranged from high to low in terms of single piece value.
  • The total value is calculated by multiplying the quantity by the unit price.
  • The highest value mineral is selected based on the total value of the entire record.
  • When the list is empty, the highest value is displayed as None.
Acceptance comparison table
MineralsQuantityUnit priceSubtotal
Coal14114
Iron ore7321
Gold ore4520
diamond31030
Cobblestone2000

14 / 16

Mining Statistics Program

Create a new MiningReport.java, compile and run using Java 21.

The Stream pipeline generates four verifiable results
  1. Clean up the source firstReject null lists and filter empty records
  2. Screen rareThe unit price is at least 5
  3. Final stable sortingUnit price in descending order, then by name

MiningReport.java · Paragraph 1/2, sequentially connected to the same file

import java.util.*;
record MinedBlock(String ore, int amount, int value) {
    MinedBlock {
        if (ore == null || ore.isBlank() || amount < 0 || amount > 1_000_000
                || value < 0 || value > 1_000_000)
            throw new IllegalArgumentException("Check mineral name, quantity and unit price");
        ore = ore.strip();
    }
}
public class MiningReport {
static boolean isRare(MinedBlock block) {
    return block != null && block.value() >= 5;
}
static long totalValue(MinedBlock block) {
    return (long) block.amount() * block.value();
}
static List<MinedBlock> clean(List<MinedBlock> blocks) {
    if (blocks == null || blocks.size() > 1_000_000)
        throw new IllegalArgumentException("The record table cannot be null And at most 100 Ten thousand");
    return blocks.stream().filter(Objects::nonNull).toList();
}
static long totalBlocks(List<MinedBlock> blocks) {
    return clean(blocks).stream().mapToLong(MinedBlock::amount).sum();
}
static long valueOfAll(List<MinedBlock> blocks) {
    return clean(blocks).stream().mapToLong(MiningReport::totalValue).sum();
}
static List<String> rareLabels(List<MinedBlock> blocks) {

MiningReport.java · Paragraph 2/2, sequentially connected to the same file

    return clean(blocks).stream().filter(MiningReport::isRare)
        .sorted(Comparator.comparingInt(MinedBlock::value).reversed()
            .thenComparing(MinedBlock::ore))
        .map(block -> block.ore() + "x" + block.amount()).toList();
}
static String bestOre(List<MinedBlock> blocks) {
    return clean(blocks).stream()
        .sorted(Comparator.comparingLong(MiningReport::totalValue).reversed()
            .thenComparing(MinedBlock::ore))
        .map(MinedBlock::ore).findFirst().orElse("None");
}
    public static void main(String[] args) {
        List<MinedBlock> blocks = List.of(
            new MinedBlock("Coal", 14, 1), new MinedBlock("Iron ore", 7, 3),
            new MinedBlock("Gold ore", 4, 5), new MinedBlock("diamond", 3, 10),
            new MinedBlock("Cobblestone", 20, 0));
        System.out.println("Total blocks=" + totalBlocks(blocks));
        System.out.println("Rare=" + rareLabels(blocks));
        System.out.println("Total value=" + valueOfAll(blocks));
        System.out.println("Highest value=" + bestOre(blocks));
    }
}

Output

Total blocks=48
Rare=[diamondx3, Gold orex4]
Total value=85
Highest value=diamond

15 / 16

Expansion tasks and unit review

Lessons 26 to 30 completed key value statistics, collection deduplication, random simulation, exception recovery, file archiving and data pipeline.

The third-level task continues to expand the data pipeline
  1. Pipeline explanationExplain the input and output of each step
  2. Empty resultThe total number is 0, the highest is none
  3. Boundary rulesQuantity, unit price and total number of records

Third gear operation

  • Wooden Pickaxe: Filter a list of integers for even numbers and sum them.
  • Iron Pickaxe: Sort items by name length, if the length is the same, then sort items in alphabetical order.
  • Diamond Pickaxe: Generate top five, average points and underachievement lists from player records.
Acceptance comparison table
Self-evaluationCanStill need to practice
I can read Stream pipe□□
I can choose filter and map□□
I will handle empty results□□

16 / 16

Own acceptance record

Beautiful output is the starting point, and being able to explain boundaries is considered mastery. Select one case each from normal, borderline and error, write down your expectations before running, and leave evidence after running.

Leave a basis for judgment
CategoryThe operation you selectedCheck key points
NormalA common input or a successful actionIs the result consistent line by line?
BorderRange endpoints and adjacent valuesIs there one more step or one less thing?
ErrorIllegal quantity, missing object or bad dataWhether it is explicitly rejected and whether the status is maintained
  • Bug records: triggering conditions, actual phenomena, causes, and minimum repairs.
  • Illustrated record: describe the starting point, end point and meaning of each arrow.
  • Adaptation record: only change one rule, and modify expectations and tests at the same time.

TAKE IT WITH YOU

Take the lesson offline

The complete lesson, code, diagrams and exercises, in your preferred language.