Java 8 Functional Interfaces and Stream API: Practical Examples and Usage

This article introduces Java 8's lambda expressions, functional interfaces, and the Stream API, explaining key concepts such as lazy and eager evaluation, and demonstrates common stream operations like collect, filter, map, flatMap, max/min, count, and reduce with concrete code examples.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Java 8 Functional Interfaces and Stream API: Practical Examples and Usage

Introduction

Java 8's most significant feature is the introduction of lambda expressions, enabling functional programming where immutable values are processed by functions to produce new values.

1. Important Functional Interfaces in Java

1.1 What is a Functional Interface?

A functional interface is an interface that contains exactly one abstract method and can be used as the type of a lambda expression. It is typically annotated with @FunctionalInterface, which causes the compiler to verify that the interface has only one abstract method. Default and static methods are allowed.

1.2 Common Java 8 Functional Interfaces

Examples of using various functional interfaces are shown below.

public class Test {
    public static void main(String[] args) {
        Predicate<Integer> predicate = x -> x > 185;
        Student student = new Student("9龙", 23, 175);
        System.out.println("9龙的身高高于185吗?:" + predicate.test(student.getStature()));

        Consumer<String> consumer = System.out::println;
        consumer.accept("命运由我不由天");

        Function<Student, String> function = Student::getName;
        String name = function.apply(student);
        System.out.println(name);

        Supplier<Integer> supplier = () -> Integer.valueOf(BigDecimal.TEN.toString());
        System.out.println(supplier.get());

        UnaryOperator<Boolean> unaryOperator = uglily -> !uglily;
        Boolean apply2 = unaryOperator.apply(true);
        System.out.println(apply2);

        BinaryOperator<Integer> operator = (x, y) -> x * y;
        Integer integer = operator.apply(2, 3);
        System.out.println(integer);

        test(() -> "我是一个演示的函数式接口");
    }

    /**
     * Demonstrates custom functional interface usage
     * @param worker
     */
    public static void test(Worker worker) {
        String work = worker.work();
        System.out.println(work);
    }

    public interface Worker {
        String work();
    }
}
//9龙的身高高于185吗?:false
//命运由我不由天
//9龙
//10
//false
//6
//我是一个演示的函数式接口

The above code demonstrates lambda usage and a custom functional interface.

Note: Student::getName is a method reference of the form ClassName::methodName , which is a concise way to write lambda expressions.

All examples are based on three classes: OutstandingClass (class), Student (student), and SpecialityEnum (specialty).

2. Common Stream Operations

2.1 collect(Collectors.toList())

Converts a stream to a list (also toSet(), toMap(), etc.). This is an eager operation.

public class TestCase {
    public static void main(String[] args) {
        List<Student> studentList = Stream.of(
                new Student("路飞", 22, 175),
                new Student("红发", 40, 180),
                new Student("白胡子", 50, 185)
        ).collect(Collectors.toList());
        System.out.println(studentList);
    }
}
//[Student{name='路飞', age=22, stature=175, specialities=null}, 
// Student{name='红发', age=40, stature=180, specialities=null}, 
// Student{name='白胡子', age=50, stature=185, specialities=null}]

2.2 filter

Filters elements using a Predicate. This is a lazy operation.

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        List<Student> list = students.stream()
                .filter(stu -> stu.getStature() < 180)
                .collect(Collectors.toList());
        System.out.println(list);
    }
}
//[Student{name='路飞', age=22, stature=175, specialities=null}]

2.3 map

Transforms each element using a Function. This is a lazy operation.

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        List<String> names = students.stream()
                .map(student -> student.getName())
                .collect(Collectors.toList());
        System.out.println(names);
    }
}
//[路飞, 红发, 白胡子]

2.4 flatMap

Flattens multiple streams into a single stream. This is a lazy operation.

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        List<Student> studentList = Stream.of(students,
                asList(
                        new Student("艾斯", 25, 183),
                        new Student("雷利", 48, 176)))
                .flatMap(students1 -> students1.stream())
                .collect(Collectors.toList());
        System.out.println(studentList);
    }
}
//[Student{name='路飞', age=22, stature=175, specialities=null}, 
// Student{name='红发', age=40, stature=180, specialities=null}, 
// Student{name='白胡子', age=50, stature=185, specialities=null}, 
// Student{name='艾斯', age=25, stature=183, specialities=null}, 
// Student{name='雷利', age=48, stature=176, specialities=null}]

2.5 max and min

Finds the maximum or minimum element using a Comparator. This is an eager operation that returns an Optional.

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        Optional<Student> max = students.stream()
                .max(Comparator.comparing(stu -> stu.getAge()));
        Optional<Student> min = students.stream()
                .min(Comparator.comparing(stu -> stu.getAge()));
        if (max.isPresent()) {
            System.out.println(max.get());
        }
        if (min.isPresent()) {
            System.out.println(min.get());
        }
    }
}
//Student{name='白胡子', age=50, stature=185, specialities=null}
//Student{name='路飞', age=22, stature=175, specialities=null}

Use max.isPresent() to check existence, max.orElse(new Student()) to provide a default, or max.orElseGet(() -> new Student()) with a supplier.

2.6 count

Counts elements, usually after a filter. This is an eager operation.

public class TestCase {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        long count = students.stream()
                .filter(s1 -> s1.getAge() < 45)
                .count();
        System.out.println("年龄小于45岁的人数是:" + count);
    }
}
//年龄小于45岁的人数是:2

2.7 reduce

The reduce operation aggregates a stream into a single value; many other terminal operations are built on it.

public class TestCase {
    public static void main(String[] args) {
        Integer reduce = Stream.of(1, 2, 3, 4)
                .reduce(0, (acc, x) -> acc + x);
        System.out.println(reduce);
    }
}
//10

3. Advanced Collectors

3.1 Collecting to a Value

Collectors are generic utilities that transform a stream into a complex result. Examples use static imports from java.util.stream.Collectors.

public class CollectorsTest {
    public static void main(String[] args) {
        List<Student> students1 = new ArrayList<>(3);
        students1.add(new Student("路飞", 23, 175));
        students1.add(new Student("红发", 40, 180));
        students1.add(new Student("白胡子", 50, 185));

        OutstandingClass ostClass1 = new OutstandingClass("一班", students1);
        List<Student> students2 = new ArrayList<>(students1);
        students2.remove(1);
        OutstandingClass ostClass2 = new OutstandingClass("二班", students2);
        Stream<OutstandingClass> classStream = Stream.of(ostClass1, ostClass2);
        OutstandingClass outstandingClass = biggestGroup(classStream);
        System.out.println("人数最多的班级是:" + outstandingClass.getName());

        System.out.println("一班平均年龄是:" + averageNumberOfStudent(students1));
    }

    private static OutstandingClass biggestGroup(Stream<OutstandingClass> outstandingClasses) {
        return outstandingClasses.collect(
                maxBy(comparing(ostClass -> ostClass.getStudents().size())))
                .orElseGet(OutstandingClass::new);
    }

    private static double averageNumberOfStudent(List<Student> students) {
        return students.stream().collect(averagingInt(Student::getAge));
    }
}
//人数最多的班级是:一班
//一班平均年龄是:37.666666666666664

3.2 Collecting to Partitions

Using Collectors.partitioningBy to split a collection into two groups based on a predicate.

public class PartitioningByTest {
    public static void main(String[] args) {
        // Assume List<Student> students is initialized
        Map<Boolean, List<Student>> listMap = students.stream().collect(
                Collectors.partitioningBy(student -> student.getSpecialities().contains(SpecialityEnum.SING)));
    }
}

3.3 Grouping

Using Collectors.groupingBy to group elements by an arbitrary key, similar to SQL's GROUP BY.

public class GroupingByTest {
    public static void main(String[] args) {
        // Assume List<Student> students is initialized
        Map<SpecialityEnum, List<Student>> listMap =
                students.stream().collect(Collectors.groupingBy(student -> student.getSpecialities().get(0)));
    }
}

3.4 Joining Strings

Concatenates strings from a stream using Collectors.joining(), optionally specifying a delimiter, prefix, and suffix.

public class JoiningTest {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(3);
        students.add(new Student("路飞", 22, 175));
        students.add(new Student("红发", 40, 180));
        students.add(new Student("白胡子", 50, 185));

        String names = students.stream()
                .map(Student::getName)
                .collect(Collectors.joining(",", "[", "]"));
        System.out.println(names);
    }
}
//[路飞,红发,白胡子]

Conclusion

This article demonstrates how Java 8's functional programming features and Stream API provide clear, concise ways to process collections, and encourages readers to refactor existing code to take advantage of these capabilities.

Combining multiple stream operations yields powerful, chainable workflows that can be adapted to various business scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaLambdaStreamsFunctional InterfacesCollectors
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.