Skip to content

Java函数式接口、Stream流与集合框架完全指南

目录


一、函数式接口

1.1 基础概念

定义: 有且仅有一个抽象方法的接口

检测注解: @FunctionalInterface

使用前提: 必须有函数式接口做方法参数传递或者返回值返回

1.2 四大核心函数式接口

1. Supplier<T> - 供给型接口

作用: 供给数据,想要什么就给什么

核心方法: T get() - 返回指定类型的数据

使用场景: 用于生成数据、获取数据

代码示例:

java
public class Demo01Supplier {
    public static void main(String[] args) {
        method(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int[] arr = {5,34,4,5,76,7};
                Arrays.sort(arr);
                return arr[arr.length-1];
            }
        });
        
        System.out.println("=======================");
        
        method(()->{
            int[] arr = {5,34,4,5,76,7};
            Arrays.sort(arr);
            return arr[arr.length-1];
        });
    }

    public static void method(Supplier<Integer> supplier){
        System.out.println(supplier.get());
    }
}

2. Consumer<T> - 消费型接口

作用: 消费数据,对数据进行操作

核心方法: void accept(T t) - 消费一个指定泛型的数据

使用场景: 对数据进行处理、输出、存储等操作

代码示例:

java
public class Demo02Consumer {
    public static void main(String[] args) {
        method(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s.length());
            }
        },"abcdefg");

        System.out.println("===================");

        method(s-> System.out.println(s.length()),"abcdefg");
    }

    public static void method(Consumer<String> consumer, String s){
        consumer.accept(s);
    }
}

3. Function<T,R> - 转换型接口

作用: 根据一个类型的数据得到另一个类型的数据

核心方法: R apply(T t) - 根据类型T参数获取类型R的结果

使用场景: 类型转换、数据映射

代码示例:

java
public class Demo03Function {
    public static void main(String[] args) {
        method(new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return integer+"";
            }
        },100);

        System.out.println("=======================");

        method(integer -> integer+"",1000);
    }

    public static void method(Function<Integer,String> function, int a){
        String s = function.apply(a);
        System.out.println(s+1);
    }
}

4. Predicate<T> - 判断型接口

作用: 判断数据是否满足条件

核心方法: boolean test(T t) - 用于判断的方法,返回值为boolean

使用场景: 条件判断、数据过滤

代码示例:

java
public class Demo04Predicate {
    public static void main(String[] args) {
        method(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length()==7;
            }
        },"abcdefg");

        System.out.println("===================");

        method(s->s.length()==7,"abcdefg");
    }

    public static void method(Predicate<String> predicate, String s){
        boolean test = predicate.test(s);
        System.out.println(test);
    }
}

1.3 四大函数式接口对比表

接口名称接口类型核心方法作用使用场景
Supplier<T>供给型T get()供给数据生成数据、获取数据
Consumer<T>消费型void accept(T t)消费数据数据处理、输出、存储
Function<T,R>转换型R apply(T t)类型转换类型转换、数据映射
Predicate<T>判断型boolean test(T t)条件判断条件判断、数据过滤

1.4 泛型与包装类

泛型特点:

  • 泛型只能写引用数据类型
  • 如果操作基本类型数据,需要使用包装类
  • 泛型的作用是统一类型

基本类型与包装类对应表:

基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

二、Stream

2.1 Stream流概述

定义: Stream流是"流水线"的流,不是IO流的流

作用: 用于对集合/数组进行链式操作,简化代码

优势:

  • 代码简洁
  • 链式调用
  • 函数式编程风格

Stream流示意图:

Stream流示意图

2.2 Stream流的获取

1. 从数组获取

方法: Stream<T> of(T...t)

代码示例:

java
Stream<String> stream1 = Stream.of("樱桃小丸子", "喜洋洋", "猫和老鼠", "黑猫警长");

2. 从集合获取

方法: stream() - Collection接口中的方法

代码示例:

java
ArrayList<String> list = new ArrayList<>();
list.add("古力娜扎");
list.add("迪丽热巴");
list.add("马尔扎哈");
Stream<String> stream2 = list.stream();

2.3 Stream流常用方法

1. forEach - 遍历

方法签名: void forEach(Consumer<? super T> action)

作用: 逐一处理(遍历)

特点: 终结方法,使用后Stream流不能继续使用

代码示例:

java
Stream<String> stream = Stream.of("cherry", "apple", "banana", "orange");
stream.forEach(s -> System.out.println(s));

2. count - 统计

方法签名: long count()

作用: 统计元素个数

特点: 终结方法

代码示例:

java
Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵", "啄木鸟");
System.out.println(stream.count());

3. filter - 过滤

方法签名: Stream<T> filter(Predicate<? super T> predicate)

作用: 根据条件过滤元素

特点: 返回新的Stream流对象

代码示例:

java
Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵", "啄木鸟","灌篮高手","七龙珠");
stream.filter(s -> s.length()>3).forEach(s -> System.out.println(s));

4. limit - 截取

方法签名: Stream<T> limit(long maxSize)

作用: 获取Stream流对象中的前n个元素

特点: 返回新的Stream流对象

代码示例:

java
Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵", "啄木鸟","灌篮高手","七龙珠");
stream.limit(3).forEach(s -> System.out.println(s));

5. skip - 跳过

方法签名: Stream<T> skip(long n)

作用: 跳过Stream流对象中的前n个元素

特点: 返回新的Stream流对象

代码示例:

java
Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵", "啄木鸟","灌篮高手","七龙珠");
stream.skip(3).forEach(s -> System.out.println(s));

6. concat - 合并

方法签名: static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

作用: 将两个流合并为一个流

代码示例:

java
Stream<String> stream1 = Stream.of("熊出没", "数码宝贝", "神厨小福贵");
Stream<String> stream2 = Stream.of("火力少年王","开心宝贝","神奇宝贝");
Stream.concat(stream1,stream2).forEach(s -> System.out.println(s));

7. collect - 转换为集合

方法签名: collect(Collector collector)

作用: 将Stream流转换为集合

代码示例:

java
Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵", "啄木鸟");
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);

8. distinct - 去重

方法签名: Stream<T> distinct()

作用: 元素去重复

注意: 需要重写hashCode和equals方法

代码示例:

java
Stream<String> stream = Stream.of("熊出没", "数码宝贝", "七龙珠","七龙珠");
stream.distinct().forEach(s -> System.out.println(s));

9. map - 类型转换

方法签名: Stream<R> map(Function<T,R> mapper)

作用: 转换流中的数据类型

代码示例:

java
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.map(integer-> integer+"").forEach(s -> System.out.println(s+1));

2.4 Stream流方法分类

终结方法: 返回值类型不再是Stream接口本身,使用后不能继续链式调用

  • forEach
  • count
  • collect

延迟方法: 返回值类型仍然是Stream接口本身,可以继续链式调用

  • filter
  • limit
  • skip
  • concat
  • distinct
  • map

三、方法引用

3.1 方法引用概述

定义: 在Lambda表达式的基础上再次简化

使用条件:

  1. 被引用的方法需要在重写的方法中被引用
  2. 被引用的方法从参数上以及返回值上要和所在的重写的方法一样
  3. 干掉重写方法的参数位置以及->,以及被引用方法的参数,将调用方法的.改成::

3.2 方法引用类型

1. 对象名引用成员方法

格式: 对象::成员方法名

代码示例:

java
public class Demo02Method {
    public static void main(String[] args) {
        method(new Supplier<String>() {
            @Override
            public String get() {
                return " abcdefg ".trim();
            }
        });

        System.out.println("=====================");

        method(" abcdefg "::trim);
    }

    public static void method(Supplier<String> supplier) {
        String result = supplier.get();
        System.out.println(result);
    }
}

2. 类名引用静态方法

格式: 类名::静态成员方法

代码示例:

java
public class Demo03Method {
    public static void main(String[] args) {
        method(new Supplier<Double>() {
            @Override
            public Double get() {
                return Math.random();
            }
        });

        System.out.println("===================");

        method(Math::random);
    }

    public static void method(Supplier<Double> supplier) {
        Double result = supplier.get();
        System.out.println(result);
    }
}

3. 类构造引用

格式: 构造方法名称::new

代码示例:

java
public class Demo04Method {
    public static void main(String[] args) {
        method(new Function<String, Person>() {
            @Override
            public Person apply(String s) {
                return new Person(s);
            }
        },"张三");

        System.out.println("=====================");

        method(Person::new,"张三");
    }

    public static void method(Function<String,Person> function, String name){
        Person person = function.apply(name);
        System.out.println(person);
    }
}

4. 数组引用

格式: 数组的数据类型[]::new

代码示例:

java
public class Demo05Method {
    public static void main(String[] args) {
        method(new Function<Integer, int[]>() {
            @Override
            public int[] apply(Integer len) {
                return new int[len];
            }
        },10);

        System.out.println("=====================");

        method(int[]::new,10);
    }

    public static void method(Function<Integer,int[]> function, int len){
        int[] arr = function.apply(len);
        System.out.println(arr.length);
    }
}

3.3 方法引用对比表

引用类型格式示例使用场景
对象名引用成员方法对象::成员方法名" abcdefg "::trim通过对象调用成员方法
类名引用静态方法类名::静态成员方法Math::random调用静态方法
类构造引用构造方法名称::newPerson::new创建对象
数组引用数组的数据类型[]::newint[]::new创建数组

四、集合框架

4.1 集合概述

定义: 集合是一种容器,类似于数组

特点:

  1. 长度可变
  2. 只能存引用类型数据(基本类型会自动装箱)
  3. 有强大的方法操作元素

分类:

1. 单列集合

特点: 元素只由一部分构成

示例: list.add("元素")

2. 双列集合

特点: 元素由两部分构成 -> key(键) 和 value(值) -> 键值对

示例: map.put(1,"涛哥")

集合框架结构图:

集合框架结构图

4.2 Collection接口

概述: Collection是单列集合的顶级接口

使用方式:

java
Collection<E> 集合名 = new 实现类集合对象<E>()

泛型:

  • 概述:<E> 叫做泛型
  • 作用:统一集合中元素的数据类型
  • 特点:
    • 泛型只能写引用类型
    • 如果操作基本类型数据,请写包装类
    • 如果不写泛型,元素默认类型为Object类型

五、常见面试题

5.1 函数式接口面试题

⭐ 问题1:什么是函数式接口?

答案: 函数式接口是指有且仅有一个抽象方法的接口。可以使用@FunctionalInterface注解进行检测,编译器会验证该接口是否确实只包含一个抽象方法。

示例:

java
@FunctionalInterface
public interface MyFunction {
    void process(String s);
}

⭐⭐ 问题2:Java 8提供了哪些内置的函数式接口?

答案: Java 8在java.util.function包中提供了四大核心函数式接口:

  1. Supplier<T>:供给型接口,无参数,返回结果
  2. Consumer<T>:消费型接口,有参数,无返回值
  3. Function<T,R>:转换型接口,有参数,有返回值
  4. Predicate<T>:判断型接口,有参数,返回boolean

⭐⭐⭐ 问题3:Lambda表达式和方法引用有什么区别?

答案: 方法引用是Lambda表达式的进一步简化形式。

区别:

  • Lambda表达式:(s) -> System.out.println(s)
  • 方法引用:System.out::println

使用条件: 方法引用要求被引用的方法在参数和返回值上必须与重写的方法一致。

5.2 Stream流面试题

⭐ 问题1:Stream流和IO流有什么区别?

答案:

  • Stream流:是"流水线"的流,用于对集合/数组进行链式操作
  • IO流:用于数据的输入输出操作

Stream流主要用于数据处理,IO流主要用于数据传输。

⭐⭐ 问题2:Stream流的终结方法和延迟方法有什么区别?

答案:

终结方法:

  • 返回值类型不再是Stream接口本身
  • 调用后不能继续链式调用
  • 例如:forEach、count、collect

延迟方法:

  • 返回值类型仍然是Stream接口本身
  • 调用后可以继续链式调用
  • 例如:filter、limit、skip、map、distinct

⭐⭐⭐ 问题3:如何理解Stream流的"链式操作"?

答案: Stream流的链式操作是指多个延迟方法可以连续调用,形成一个处理流水线,最后由终结方法触发执行。

示例:

java
list.stream()
    .filter(s -> s.startsWith("张"))
    .filter(s -> s.length() == 3)
    .forEach(s -> System.out.println(s));

这种方式的优点:

  1. 代码简洁
  2. 逻辑清晰
  3. 函数式编程风格

⭐⭐⭐ 问题4:distinct方法去重原理是什么?

答案: distinct方法去重依赖于元素的hashCode()equals()方法。

原理:

  1. 首先调用元素的hashCode()方法
  2. 如果hashCode相同,再调用equals()方法比较
  3. 只有hashCode和equals都相同,才认为是重复元素

注意: 对于自定义类型,必须重写hashCode()equals()方法。

5.3 集合框架面试题

⭐ 问题1:集合和数组有什么区别?

答案:

特性数组集合
长度固定可变
存储类型基本类型、引用类型只能存引用类型
方法丰富
效率相对较低

⭐⭐ 问题2:什么是泛型?为什么要使用泛型?

答案: 泛型是一种类型参数化机制,用于统一集合中元素的数据类型。

优点:

  1. 类型安全:编译时检查类型,避免运行时ClassCastException
  2. 消除强制类型转换
  3. 提高代码可读性和复用性

示例:

java
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);

⭐⭐ 问题3:单列集合和双列集合有什么区别?

答案:

单列集合:

  • 元素只由一部分构成
  • 例如:List、Set
  • 添加方法:add(element)

双列集合:

  • 元素由键和值两部分构成
  • 例如:Map
  • 添加方法:put(key, value)

六、经典练习题

6.1 函数式接口练习题

练习1:使用Supplier获取数组最大值

题目: 使用Supplier接口作为方法的参数,用Lambda表达式求出int数组中的最大值。

参考答案:

java
public class Exercise01 {
    public static void main(String[] args) {
        int[] arr = {5,34,4,5,76,7};
        
        Integer max = getMax(()->{
            Arrays.sort(arr);
            return arr[arr.length-1];
        });
        
        System.out.println("最大值:" + max);
    }

    public static Integer getMax(Supplier<Integer> supplier) {
        return supplier.get();
    }
}

练习2:使用Consumer处理字符串

题目: 使用Consumer接口处理字符串,输出字符串的长度和大写形式。

参考答案:

java
public class Exercise02 {
    public static void main(String[] args) {
        String str = "Hello World";
        
        processString(str, s -> System.out.println("长度:" + s.length()));
        processString(str, s -> System.out.println("大写:" + s.toUpperCase()));
    }

    public static void processString(String s, Consumer<String> consumer) {
        consumer.accept(s);
    }
}

练习3:使用Function进行类型转换

题目: 使用Function接口将String类型转换为Integer类型。

参考答案:

java
public class Exercise03 {
    public static void main(String[] args) {
        String numStr = "12345";
        
        Integer num = convert(numStr, s -> Integer.parseInt(s));
        System.out.println("转换结果:" + num);
    }

    public static Integer convert(String s, Function<String, Integer> function) {
        return function.apply(s);
    }
}

练习4:使用Predicate判断字符串

题目: 使用Predicate接口判断字符串长度是否大于5。

参考答案:

java
public class Exercise04 {
    public static void main(String[] args) {
        String str = "HelloWorld";
        
        boolean result = checkString(str, s -> s.length() > 5);
        System.out.println("长度是否大于5:" + result);
    }

    public static boolean checkString(String s, Predicate<String> predicate) {
        return predicate.test(s);
    }
}

6.2 Stream流练习题

练习1:Stream流综合练习

题目:

  1. 第一个队伍只要名字为3个字的成员姓名
  2. 第一个队伍筛选之后只要前3个人
  3. 第二个队伍只要姓张的成员姓名
  4. 第二个队伍筛选之后不要前2个人
  5. 将两个队伍合并为一个队伍
  6. 打印整个队伍的姓名信息

参考答案:

java
public class Exercise05 {
    public static void main(String[] args) {
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");

        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");

        Stream<String> stream1 = one.stream();
        Stream<String> stream2 = two.stream();
        
        Stream<String> streamOne = stream1.filter(s -> s.length() == 3).limit(3);
        Stream<String> streamTwo = stream2.filter(s -> s.startsWith("张")).skip(2);

        Stream.concat(streamOne, streamTwo).forEach(s -> System.out.println(s));
    }
}

练习2:Stream流去重练习

题目: 创建一个包含重复元素的Person集合,使用Stream流去重。

参考答案:

java
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person {
    private String name;
    private Integer age;
}

public class Exercise06 {
    public static void main(String[] args) {
        Stream<Person> stream = Stream.of(
            new Person("张三", 18), 
            new Person("张三", 18), 
            new Person("张三", 28)
        );
        
        stream.distinct().forEach(person -> System.out.println(person));
    }
}

练习3:Stream流转集合练习

题目: 将Stream流转换为List集合。

参考答案:

java
public class Exercise07 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("熊出没", "数码宝贝", "神厨小福贵", "啄木鸟");
        
        List<String> list = stream.collect(Collectors.toList());
        System.out.println(list);
    }
}

6.3 方法引用练习题

练习1:对象名引用成员方法

题目: 使用方法引用实现字符串trim操作。

参考答案:

java
public class Exercise08 {
    public static void main(String[] args) {
        method(" abcdefg "::trim);
    }

    public static void method(Supplier<String> supplier) {
        String result = supplier.get();
        System.out.println(result);
    }
}

练习2:类名引用静态方法

题目: 使用方法引用获取随机数。

参考答案:

java
public class Exercise09 {
    public static void main(String[] args) {
        method(Math::random);
    }

    public static void method(Supplier<Double> supplier) {
        Double result = supplier.get();
        System.out.println(result);
    }
}

练习3:构造方法引用

题目: 使用方法引用创建Person对象。

参考答案:

java
public class Exercise10 {
    public static void main(String[] args) {
        method(Person::new, "张三");
    }

    public static void method(Function<String, Person> function, String name) {
        Person person = function.apply(name);
        System.out.println(person);
    }
}

七、学习建议

7.1 学习路径

推荐学习顺序:

  1. 函数式接口 → 理解Lambda表达式的基础
  2. Stream流 → 掌握链式操作和数据处理
  3. 方法引用 → 进一步简化Lambda表达式
  4. 集合框架 → 理解集合体系结构

7.2 重点难点

重点:

  1. ⭐⭐⭐ 四大函数式接口的使用
  2. ⭐⭐⭐ Stream流的常用方法
  3. ⭐⭐⭐ 方法引用的四种形式
  4. ⭐⭐ 集合框架的基本概念

难点:

  1. ⭐⭐⭐ 理解函数式编程思想
  2. ⭐⭐⭐ Stream流的链式操作
  3. ⭐⭐ 方法引用的使用条件
  4. ⭐⭐ 泛型的理解

7.3 实践建议

多练习:

  • 每个函数式接口都要写示例代码
  • Stream流的每个方法都要亲自测试
  • 方法引用要对比Lambda表达式理解

多思考:

  • 为什么需要函数式接口?
  • Stream流的优势是什么?
  • 方法引用简化了什么?

多应用:

  • 在实际项目中使用Stream流处理集合
  • 使用函数式接口简化代码
  • 使用方法引用提高代码可读性

7.4 常见错误

错误1: 忘记Stream流是终结方法

java
Stream<String> stream = Stream.of("a", "b", "c");
stream.count();
stream.forEach(s -> System.out.println(s));

错误原因: count()是终结方法,调用后Stream流已关闭。

错误2: 方法引用参数不匹配

java
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println);

正确: forEach需要Consumer接口,println方法参数和返回值匹配。

错误3: 泛型使用基本类型

java
List<int> list = new ArrayList<>();

正确: 应该使用包装类 List<Integer> list = new ArrayList<>();

7.5 扩展学习

下一步学习:

  1. Collection接口的常用方法
  2. List集合(ArrayList、LinkedList)
  3. Set集合(HashSet、TreeSet)
  4. Map集合(HashMap、TreeMap)

推荐资源:

  • Java官方文档
  • 《Java核心技术》
  • 《Effective Java》

总结

本文档涵盖了Java函数式接口、Stream流、方法引用和集合框架的核心知识点,包括:

四大函数式接口:Supplier、Consumer、Function、Predicate ✅ Stream流操作:获取、过滤、映射、收集等常用方法 ✅ 方法引用:四种引用形式的使用 ✅ 集合框架:单列集合和双列集合的基本概念 ✅ 面试题:常见面试问题及详细解答 ✅ 练习题:实战练习巩固知识点

学习建议:

  • 理解函数式编程思想
  • 多写代码实践
  • 注意Stream流的终结方法特性
  • 掌握方法引用的使用条件

祝学习顺利!🎉