Master Java 8 Streams: From Basics to Advanced Operations
This article introduces Java 8's Stream API, explains why functional streams improve code readability and performance, and provides detailed examples of common operations such as filter, map, flatMap, reduce, collect, Optional handling, parallel processing, and debugging techniques for efficient data processing.
Stream Overview
Java 8 introduced a new Stream API. Unlike I/O streams, it behaves like an Iterable collection with distinct characteristics.
Streams enhance collection functionality, focusing on convenient and efficient aggregation or bulk data operations.
By specifying element operations—e.g., filtering strings longer than 10 characters or extracting first letters—Stream implicitly traverses and transforms data.
Why Use Streams
Functional programming makes code express business intent rather than implementation details, resulting in more readable, maintainable, reliable, and less error‑prone code.
Sample Data Source
public class Data {
private static List<PersonModel> list = null;
static {
PersonModel wu = new PersonModel("wu qi", 18, "男");
PersonModel zhang = new PersonModel("zhang san", 19, "男");
PersonModel wang = new PersonModel("wang si", 20, "女");
PersonModel zhao = new PersonModel("zhao wu", 20, "男");
PersonModel chen = new PersonModel("chen liu", 21, "男");
list = Arrays.asList(wu, zhang, wang, zhao, chen);
}
public static List<PersonModel> getData() {
return list;
}
}Filter
Used to traverse data and test each element.
filter accepts a lambda expression as its predicate.
/**
* Filter all males
*/
public static void fiterSex() {
List<PersonModel> data = Data.getData();
// old way
List<PersonModel> temp = new ArrayList<>();
for (PersonModel person : data) {
if ("男".equals(person.getSex())) {
temp.add(person);
}
}
System.out.println(temp);
// new way
List<PersonModel> collect = data.stream()
.filter(person -> "男".equals(person.getSex()))
.collect(toList());
System.out.println(collect);
}
/**
* Filter all males and age < 20
*/
public static void fiterSexAndAge() {
List<PersonModel> data = Data.getData();
// old way
List<PersonModel> temp = new ArrayList<>();
for (PersonModel person : data) {
if ("男".equals(person.getSex()) && person.getAge() < 20) {
temp.add(person);
}
}
// new ways
List<PersonModel> collect = data.stream()
.filter(person -> {
if ("男".equals(person.getSex()) && person.getAge() < 20) {
return true;
}
return false;
})
.collect(toList());
List<PersonModel> collect1 = data.stream()
.filter(person -> "男".equals(person.getSex()) && person.getAge() < 20)
.collect(toList());
}Map
Map creates a one‑to‑one transformation.
Commonly used and simple.
/**
* Extract all user names
*/
public static void getUserNameList(){
List<PersonModel> data = Data.getData();
// old way
List<String> list = new ArrayList<>();
for (PersonModel persion : data) {
list.add(persion.getName());
}
System.out.println(list);
// new ways
List<String> collect = data.stream().map(person -> person.getName()).collect(toList());
System.out.println(collect);
List<String> collect1 = data.stream().map(PersonModel::getName).collect(toList());
System.out.println(collect1);
List<String> collect2 = data.stream().map(person -> {
System.out.println(person.getName());
return person.getName();
}).collect(toList());
}FlatMap
Similar to map but handles deeper, one‑to‑many transformations.
Map returns a one‑to‑one mapping; flatMap handles one‑to‑many.
Method signatures differ:
Stream map(Function mapper)
Stream flatMap(Function<? super T, ? extends Stream<? extends R> mapper)
public static void flatMapString() {
List<PersonModel> data = Data.getData();
// different return type
List<String> collect = data.stream()
.flatMap(person -> Arrays.stream(person.getName().split(" ")))
.collect(toList());
List<Stream<String>> collect1 = data.stream()
.map(person -> Arrays.stream(person.getName().split(" ")))
.collect(toList());
// using map then flatMap
List<String> collect2 = data.stream()
.map(person -> person.getName().split(" "))
.flatMap(Arrays::stream)
.collect(toList());
// another way
List<String> collect3 = data.stream()
.map(person -> person.getName().split(" "))
.flatMap(str -> Arrays.asList(str).stream())
.collect(toList());
}Reduce
Similar to recursion; used for numeric or string aggregation.
public static void reduceTest(){
// sum with initial value 10
Integer reduce = Stream.of(1,2,3,4)
.reduce(10, (count, item) -> {
System.out.println("count:"+count);
System.out.println("item:"+item);
return count + item;
});
System.out.println(reduce);
Integer reduce1 = Stream.of(1,2,3,4)
.reduce(0, (x, y) -> x + y);
System.out.println(reduce1);
String reduce2 = Stream.of("1","2","3")
.reduce("0", (x, y) -> (x + "," + y));
System.out.println(reduce2);
}Collect
Collect generates common data structures such as List, Set, Map, or custom collections from a stream.
/**
* toList
*/
public static void toListTest(){
List<PersonModel> data = Data.getData();
List<String> collect = data.stream()
.map(PersonModel::getName)
.collect(Collectors.toList());
}
/**
* toSet
*/
public static void toSetTest(){
List<PersonModel> data = Data.getData();
Set<String> collect = data.stream()
.map(PersonModel::getName)
.collect(Collectors.toSet());
}
/**
* toMap
*/
public static void toMapTest(){
List<PersonModel> data = Data.getData();
Map<String, Integer> collect = data.stream()
.collect(Collectors.toMap(PersonModel::getName, PersonModel::getAge));
// custom value transformation
data.stream()
.collect(Collectors.toMap(per -> per.getName(), value -> {
return value + "1";
}));
}
/**
* Specify collection type
*/
public static void toTreeSetTest(){
List<PersonModel> data = Data.getData();
TreeSet<PersonModel> collect = data.stream()
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(collect);
}
/**
* Grouping
*/
public static void toGroupTest(){
List<PersonModel> data = Data.getData();
Map<Boolean, List<PersonModel>> collect = data.stream()
.collect(Collectors.groupingBy(per -> "男".equals(per.getSex())));
System.out.println(collect);
}
/**
* Joining
*/
public static void toJoiningTest(){
List<PersonModel> data = Data.getData();
String collect = data.stream()
.map(personModel -> personModel.getName())
.collect(Collectors.joining(",", "{", "}"));
System.out.println(collect);
}
/**
* Custom reduction
*/
public static void reduce(){
List<String> collect = Stream.of("1","2","3").collect(
Collectors.reducing(new ArrayList<>(), x -> Arrays.asList(x), (y, z) -> {
y.addAll(z);
return y;
}));
System.out.println(collect);
}Optional
Optional is a new container type designed to replace null values, reducing null‑related errors.
It provides methods such as of, ofNullable, isPresent, ifPresent, orElse, orElseGet, and orElseThrow.
public static void main(String[] args) {
PersonModel personModel = new PersonModel();
// old style null check
Optional<Object> o = Optional.of(personModel);
System.out.println(o.isPresent()?o.get():"-");
// nullable name
Optional<String> name = Optional.ofNullable(personModel.getName());
System.out.println(name.isPresent()?name.get():"-");
// ifPresent example
Optional.ofNullable("test").ifPresent(na -> {
System.out.println(na+"ifPresent");
});
// orElse examples
System.out.println(Optional.ofNullable(null).orElse("-"));
System.out.println(Optional.ofNullable("1").orElse("-"));
// orElseGet examples
System.out.println(Optional.ofNullable(null).orElseGet(() -> {
return "hahah";
}));
System.out.println(Optional.ofNullable("1").orElseGet(() -> {
return "hahah";
}));
// orElseThrow example
System.out.println(Optional.ofNullable("1").orElseThrow(() -> {
throw new RuntimeException("ss");
}));
// multi‑level checks with Optional
EarthModel earthModel1 = new EarthModel();
Optional.ofNullable(earthModel1)
.map(EarthModel::getTea)
.map(TeaModel::getType)
.isPresent();
// processing list inside Optional
Optional.ofNullable(new EarthModel())
.map(EarthModel::getPersonModels)
.map(pers -> pers.stream()
.map(PersonModel::getName)
.collect(toList()))
.ifPresent(per -> System.out.println(per));
List<PersonModel> models = Data.getData();
Optional.ofNullable(models)
.map(per -> per.stream()
.map(PersonModel::getName)
.collect(toList()))
.ifPresent(per -> System.out.println(per));
}Concurrency
Replace stream with parallelStream or parallel to enable parallel processing.
Parallel performance depends on data size, source structure, boxing, CPU core count, and per‑element processing time.
private static int size = 10000000;
public static void main(String[] args) {
System.out.println("-----------List-----------");
testList();
System.out.println("-----------Set-----------");
testSet();
}
/**
* Test list processing
*/
public static void testList(){
List<Integer> list = new ArrayList<>(size);
for (Integer i = 0; i < size; i++) {
list.add(new Integer(i));
}
// old way
long start = System.currentTimeMillis();
for (Integer i : list) {
// no operation
}
System.out.println(System.currentTimeMillis() - start);
// sequential stream
long start1 = System.currentTimeMillis();
list.stream().collect(Collectors.toList());
System.out.println(System.currentTimeMillis() - start1);
// parallel stream
long start2 = System.currentTimeMillis();
list.parallelStream().collect(Collectors.toList());
System.out.println(System.currentTimeMillis() - start2);
}
/**
* Test set processing
*/
public static void testSet(){
List<Integer> list = new ArrayList<>(size);
for (Integer i = 0; i < size; i++) {
list.add(new Integer(i));
}
// old way
long start = System.currentTimeMillis();
for (Integer i : list) {
// no operation
}
System.out.println(System.currentTimeMillis() - start);
// sequential stream
long start1 = System.currentTimeMillis();
list.stream().collect(Collectors.toSet());
System.out.println(System.currentTimeMillis() - start1);
// parallel stream
long start2 = System.currentTimeMillis();
list.parallelStream().collect(Collectors.toSet());
System.out.println(System.currentTimeMillis() - start2);
}Debugging
Stream operations are chained (e.g., list.map.filter.map.collect). Lazy evaluation returns a Stream; eager evaluation returns a concrete result.
peek allows inspection of each element while preserving the stream.
private static void peekTest(){
List<PersonModel> data = Data.getData();
// peek prints each name during the stream
data.stream().map(per -> per.getName())
.peek(p -> {
System.out.println(p);
})
.collect(toList());
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
