首页 > 技术文章 > Lambda和Stream

lukazan 2021-07-15 20:43 原文

lambda表达式是JDK8引入的新功能(语法糖),类似JS中的闭包;通过一个匿名的方法(函数化的语法)简化编写代码的方式。
  • 基本结构 (arguments) -> body 
    • 参数类型可推导时,不需指定类型: (a,b) -> a + b 
    • 当仅有一个参数且类型可推导时,不强制写 () : a -> a + 1 
    • 参数指定类型,必须有括号: (Integer i) -> i + 1 
    • 参数可为空: () -> "hello, lambda" 
    • body需要用 {} 包含语句,当仅有一条语句时可省略
  • lambda表达式与匿名内部类的区别

-

lambda表达式

匿名内部类

类型不同

只支持单抽象方法的接口

接口、抽象类、具体类

是否需要实例化

需要

内存分配

只需分配一次内存,存储在堆的永久区中

类被新建时,需要给对象分配内存

实现原理

编译后,无单独的字节码文件,对应字节码在运行时动态生成

编译后,生成单独的字节码文件

一、函数式接口

函数式接口:只有一个抽象方法的接口,目的是为了某一个单一的操作

函数式接口声明,在接口上添加注解 @FunctionalInterface

  • JDK8之前已存在被标注为函数式接口的接口
    • java.lang.Runnable
    • java.util.Comparator
    • java.util.concurrent.Callable
    • java.io.FileFilter
    • java.security.PrivilegedAction
    • java.beans.PropertyChangeListener
  • JDK8新增的函数式接口(基于java.util.function包下)
    • Function:函数,T -> R
    • BiFunction:函数,(T,U) ->R
    • Predicate:断言/判断,T -> boolean
    • BiPredicate:断言/判断,(T,U) -> boolean
    • Supplier:生产者,() -> T
    • Consumer:消费者,(T) -> ;
    • BiConsumer:消费者,(T,U) ->;
    • UnaryOperator:单元运算,(T) -> T
    • BinaryOperator:二元运算,(T,T) -> T
    • BooleanSupplier, DoubleBinaryOperator, DoubleConsumer, DoubleFunction, DoublePredicate, DoubleSupplier, DoubleToIntFunction, DoubleToLongFunction, DoubleUnaryOperator, IntBinaryOperator, IntConsumer, IntFunction, IntPredicate, IntSupplier, IntToDoubleFunction, IntToLongFunction, IntUnaryOperator, LongBinaryOperator, LongConsumer, LongFunction, LongPredicate, LongSupplier, LongToDoubleFunction, LongToIntFunction, LongUnaryOperator, ToDoubleBiFunction, ToDoubleFunction, ToIntBiFunction, ToIntFunction, ToLongBiFunction, ToLongFunction等

0、总结

  • 如无参数,请使用Supplier(Use Supplier if it takes nothing)

  • 如无返回,请使用Consumer(Use Consumer if it returns nothing)

  • 如两者都无,请使用Runnable(Use Runnable if it does neither)

  • 如两者都有,请使用Function(Use Function if it does both)

  • 如返回布尔值,请使用Predicate(Use Predicate if it returns a boolean)

  • 如以上皆不可以,请使用自定义@FunctionalInteface(Use @FunctionalInteface if none of above works)

1、快速使用

1)简化匿名函数式接口实现

函数式接口:只包含一个抽象方法声明的接口

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});
  • 写法
// 写法1:简化匿名类写法
Collections.sort(names, (String a, String b) -> { 
    return b.compareTo(a)
});
// 写法2:在1的基础上,省略方法参数类型
Collections.sort(names, (a, b) -> { 
    return b.compareTo(a)
});
// 写法3:在2的基础上,单行方法省略return和{}
Collections.sort(names, (a, b) -> b.compareTo(a));
2)方法和构造函数引用
  • Converter接口
@FunctionalInterface
interface Converter<F, T> {
   T convert(F from);
}
  • 写法
//原始写法
Converter<String, Integer> converter0 = new Converter<String, Integer>() {
          @Override
          public Integer convert(String from) {
                return Integer.valueOf(from);
          }
};

//lamada表达式写法:(from) -> Integer.valueOf(from)返回一个接口实现类
Converter<String, Integer> converter1 = (from) -> Integer.valueOf(from);
Integer converted1 = converter1.convert("123");

// 更进一步的简化写法
Converter<String, Integer> converter2 = Integer::valueOf;
Integer converted2 = converter2.convert("123");
  • 实现Person类构造函数的引用
class Person {
    String firstName;
    String lastName;
    Person() {}
    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

interface PersonFactory<P extends Person> {
   P create(String firstName, String lastName);
}

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
3) lambda访问范围
  • 局部变量
  • 外部变量在lambda内部使用时,会在编译时隐式声明final修饰
int num = 1;    // 等同于final int num = 1;
Converter<Interge, String> converter = (from) String.valueOf(from + num);
  • 成员变量与静态成员变量
  • 支持在lambda内部读写操作
public class VarTest {
    int num;
    static staticNum;
    @Test
    public void test() {
        Converter<Integer, String> converter = (from) -> {
            num = 1;
            return String.valueOf(from + num);
        };
        Converter<Integer, String> staticConverter = (from) -> {
            staticNum = 2;
            return String.valueOf(from + staticNum);
        };
    }
}
  • default接口方法
  • 编译不支持

2、内置函数式接口

1. Predicate<T>

  • 布尔类型函数,处理复杂逻辑判断,test方法为执行方法
    • and(Predicate p):与其他Predicate对象组成全满足关系
    • or(Predicate p):与其他Predicate对象组成满足任一即可关系
    • negate():结果取反
// 判断整数是否在[60,100]区间内
Predicate<Integer> predicate = i -> i >= 60 && i <= 100;
// test方法最终执行逻辑判断
predicate.test(10);    // false
predicate.test(100);    // true

2. Function<T, R>

  • 接收单一参数返回单一结果,apply方法为执行方法
    • andThen(Function f):下一个执行Function
    • compose(Function f):在此之前执行Function
Function<Integer, Integer> add = x -> x + 10;
Function<Integer, Integer> mul = x -> x * 2;
add.andThen(mul).apply(3);    // (3 + 10) * 2 = 26

3. Supplier<T>

  • 获得一个给定类型的结果,无入参
Supplier<Integer> supplier = () -> 1;
supplier.get();    // 1

4. Consumer<T>

  • 接收单一入参无返回结果
    • andThen(Consumer c):链式追加
Consumer<Person> consumer = p -> {
    p.setAge(p.getAge() + 1);
}
consumer.accept(new Person("Test", 1));

5. Comparators

  • 接收两个同类型参数,比较大小
List<Person> list = ListUtils.list(new Person("a", 33), new Person("d", 15), 
                                   new Person("b", 22), new Person("e", 15));
// 按age降序,再按name倒序
list.sort(Comparator.comparing(Person::getAge)
          .thenComparing(Person::getName).reversed());

6. Optionals

  • 存储一个对象值,解决NPE问题
// 传统写法
if (user == null) {
    doSomething();
}
// 优雅写法
Optional.ofNullable(user).ifPresent(u -> doSomething());

// orEles-不论Optional对象是否含空值,都会执行调用
// orElseGet-则只会在Optional对象含空值时调用
Optional.ofNullable(null).orElse(createNewUser());
Optional.ofNullable(null).orElseGet(createNewUser());

// filter 结果为true则返回对象,否则返回含空值的Optional
Optional.ofNullable(new User("a"))
    .filter(u -> StringUtils.equals("a", user.getName()));

// orElseThrow 存在值则返回,否则抛出异常
Optional.ofNullable(null)
    .orElseThrow(() -> {
        // do something
        return new RuntimeException("NPE");
    });

// map:对实例值操作,并封装为Optional返回
// flatMap:对实例值操作,不封装Optional返回
Optional.ofNullable("jack")
    .map((s) -> s.toUpperCase())
    .orElse("NPE");
Optional.ofNullable("jack")
    .map((s) -> Optional.ofNullable(s.toUpperCase()))
    .orElse("NPE");

二、方法引用

1、快速使用

  • 语法
    • 构造方法引用: Type::new 
    • 数组构造方法引用:Type[]::new
    • 静态方法引用:Type::methodName
    • 实例对象的实例方法引用:instanceName::methodName
    • 实例类型的实例方法引用:Type::methodName
// 方法引用写法
Function<String, Integer> function = Integer::new;
// lambda写法
function = str -> new Integer(str);
// 传统写法
function = new Function<String, Integer>() {
    public Integer apply(String str) {
        return new Integer(str);
    }
}

// 数组构造方法引用
Function<Integer, String[]> function = String[]::new;
// lambda写法
function = length -> new String[length];
// 传统写法
function = new Function<Integer, String[]>() {
    public String[] apply(Integer length) {
        return new String[length];
    }
}

// 静态方法引用
Function<Integer, String[]> function = String[]::new;
// lambda写法
function = length -> new String[length];
// 传统写法
function = new Function<Integer, String[]>() {
    public String[] apply(Integer length) {
        return new String[length];
    }
}

// 实例对象的实例方法引用
List list = Arrays.asList("bikaqiu", "bikabika", "bika");
Predicate<String> predicate = list::contains;

// 实例类型的实例方法引用
Function<String, Integer> function = String::length;

2、方法引用

语法: Type::methodName 或 instanceName::methodName ,后者methodName为new

1. 构造方法引用

语法: Type::new 

// 整数String转换为Integer
// 方法引用写法
Function<String, Integer> f = Integer::new;
// lambda写法
f = s -> new Interger(s);
// 传统写法
f = new Function() { 
      public Integer apply(String s) { 
              return new Integer(s);
    }
}

2. 数组构造方法引用

语法: Type[]::new 

// 构造指定长度的String数组
// 方法引用写法
Function<Integer, String[]> f = String[]::new;
// lambada
f = length -> new String[length];

3. 静态方法引用

// lambda写法
Function<String,Integer> f = s -> Integer.parseInt(s);
// 方法引用写法
Function<String,Integer> f = Integer::parseInt;

4. 实例方法引用

// 判断List中是否存在指定String
List<String> names = Arrays.asList(new String[]{"Tom", "summery"});
Predicate check = names::contains;
check.test("Tom");    // true

5. 类型方法引用

// 输出String的长度
Function<String, Integer> f = String::length;
System.out.println(f.apply("TomCat"));
// 输出List中每个String元素的长度
List<String> names = Arrays.asList(new String[]{"Tom", "summery"});
names.stream().map(String::length).forEach(System.out::println);
// 切割字符串
BiFunction<String, String, String[]> sp = String::split;
String[] apply = sp.apply("this.is.Test,sdjsk.sdfav", "\\.");

三、Stream

Streams API详解

JDK8引入的新特性, java.util.stream 包中,是一组支持串行并行聚合操作的元素,即集合/迭代器的增强版

  • stream():串行执行流对象操作
  • paralleStream():并行执行流对象操作
  • Arrays.stream(T array):将数组转换为流对象操作
  • 特性
    • 单次处理,即一次处理完毕后,当前Stream关闭
    • 支持并行操作
  • 流的基本步骤
    • 获取数据源
    • 数据转换:每次转换原有Stream对象不改变,会返回新Stream对象
    • 执行操作获取结果

1、方法

1. forEach

  • 流中止操作,遍历每个元素
List<String> list = Arrays.asList("B", "C", "H", "A");
// 简写list.stream().forEach(System.out::println);
list.stream().forEach(e -> System.out.println(e));

2. filter

  • 流中间操作,接收一个Predicate对象,对每一个元素进行过滤,常用配合collect或其他操作
// 过滤过于大于50和小于0的元素,取最大值
List<Integer> list = Arrays.asList(40, 2, 33, 55, 32, 14, 5, 10, -10, -33);
Integer max = list.stream().filter(i -> i <= 50 && i >= 0).max(Integer::compareTo).get();

3. sorted

  • 流中间操作,对集合进行排序并返回流对象;该操作不改变原集合对象顺序,仅返回一个排序过的流对象视图
List<String> list = Arrays.asList("B", "C", "H", "A");
// 默认自然排序
list.stream().sorted().forEach(System.out::println);
// 降序
list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
// 自定义排序
list.stream().sorted(Comparator.comparing(String::toString)).forEach(System.out::println);

4. map

  • 流中间操作,将流中的每个元素对应到另一个对象中
List<String> list = Arrays.asList("Bob", "Car", "Height", "ALICE");
# 将字符串变为全小写输出
list.stream().map(s -> s.toLowerCase(Locale.CHINA)).forEach(System.out::println);

5. match

  • 流中止操作,判断某一种规则是否与流对象中的元素匹配,返回Boolean值
List<String> list = Arrays.asList("Bob", "Car", "Height", "ALICE");
// 判断元素中是否任一满足首字符为B
list.stream().anyMatch(s -> s.startsWith("B"));    // true
// 判断元素中是否都满足首字符为B
list.stream().allMatch(s -> s.startsWith("B"));    // false
// 判断元素中是否都不满足首字符为B
list.stream().noneMatch(s -> s.startsWith("B"));    // false

6. count

  • 流中止操作,返回当前流对象中元素数目
List<String> list = Arrays.asList("Bob", "Car", "Height", "ALICE");
// 满足开头首字符为B或A的元素数目
list.stream().filter(s -> s.startsWith("B") || s.startsWith("A")).count(); // 2

7. reduce

  • 规约,可作为累加器、累乘器
// Optional<T> reduce(BinaryOperator<T> accumulator);
Arrays.asList(1, 2, 3).stream().reduce((a, b) -> a + b).get());    // 6
// T reduce(T identity, BinaryOperator<T> accumulator);    相对上一个使用多了一个初始值设置
Arrays.asList(1, 2, 3).stream().reduce(0, (a, b) -> a + b));    // 6

8. concat

  • 流连接
// 连接两个流并打印
Stream.concat(Stream.of(1, 2), Stream.of(3)).forEach(System.out::print);    // 123

9. peek

  • 生成包含原Stream流所有元素的新Stream流,且新Stream流每个元素被消费前执行peek给定的消费函数
    // 打印元素前先打印元素-1的值
    Stream.of(2, 4).peek(x -> System.out.print(x - 1)).forEach(System.out::print);    // 1234

10. max/min

  • 获取最大或最小元素
  • 存在多个最值相同的元素,则返回首个最值元素
    List<Integer> list = Arrays.asList(40, 2, 33, 55, 32, 14, 5, 10);
    Integer max = list.stream().max(Integer::compareTo).get();

11. limit

  • 截取前N个元素
// 截取前四位取最小值
List<Integer> list = Arrays.asList(40, 2, 33, 55, 32, 14, 5, 10, -10, -33);
list.stream().limit(4).forEach(System.out::println);

12. collect

将处理后的Stream流组装成集合

// 过滤负数元素
List<Integer> list = Arrays.asList(33, -10, 60 ,-4, 6, 80 , -33);
List<Integer> collect = list.stream().filter(i -> i >= 0).collect(Collectors.toList());
// User[id,name,age]
// 将List<User>转换为Map<id, name>
userList.stream().collect(Collectors.toMap(User::getId, User::getName));
// 将List<User>转换为Map<id, User>
userList.stream().collect(Collectors.toMap(User::getId, User -> User));
userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
// List<User>存在key冲突问题转换
userList.stream().collect(Collectors.toMap(User::getId, Function.identity(), (k1, k2) -> k2));

注意点(踩坑)

  • 使用Collectors.toMap(key, value)时,要注意key不能重复,否则默认的合并策略会抛出异常提示重复KEY:Duplicate key
  • 源码
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
  return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

private static <T> BinaryOperator<T> throwingMerger() {
    return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
  • 解决办法:手动指定合并策略,走重载方法
List<JSONObject> list = new ArrayList<>(10);
list.add(buildJSONObject("1", "n1"));
list.add(buildJSONObject("1", "n2"));
list.add(buildJSONObject("2", "n3"));
list.stream
    .collect(Collectors.toMap(d -> d.getString("id"), 
                              d -> d, (oldValue, newValue) -> newValue));
  • 使用该方法返回Map时,要避免Value为NULL值,否则在合并时会抛出NPE异常
  • 源码
public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                             Function<? super T, ? extends U> valueMapper,
                             BinaryOperator<U> mergeFunction,
                             Supplier<M> mapSupplier) {
    BiConsumer<M, T> accumulator
        = (map, element) -> map.merge(keyMapper.apply(element),
                                      valueMapper.apply(element), mergeFunction);
    return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}

// Map.merge()
default V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    Objects.requireNonNull(value);    // 此处就是Value为NULL时抛出NPE的地方
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
    remappingFunction.apply(oldValue, value);
    if(newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}
  • 解决方式:在collect调用前使用filter过滤Value为NULL的情况;或者Value回写Map时做判断NULL初始化
list.stream
    .filter(Objects::nonNull)
    .collect(Collectors.toMap(d -> d.getString("id"), d -> d, (oldValue, newValue) -> newValue));

mapToInt/mapToLong/mapToDouble

  • 汇总数据对象:IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics
  • 对应方法:getCount(总数)、getMax(最大值)、getMin(最小值)、getAverage(平均值)
List<Integer> list = Arrays.asList(33, -10, 60 ,-4, 6, 80 , -33);
IntSummaryStatistics summary = list.stream().mapToInt(Integer::intValue).summaryStatistics();

2、用法

  • 遍历索引
List<String> list = Arrays.asList("a", "b", "c", "d");
Stream.iterate(0, i -> i + 1).limit(list.size()).forEach(i -> {
    System.out.println(i + ":"+ list.get(i));
});
  • 分组统计
/* [Person{c='A', d=10, e=75}, Person{c='B', d=24, e=61}, Person{c='D', d=16, e=20}, 
 * Person{c='A', d=17, e=99}, Person{c='C', d=20, e=67}, Person{c='D', d=10, e=14}, 
 Person{c='B', d=4, e=41}, Person{c='A', d=29, e=35}, Person{c='A', d=6, e=61}, 
 Person{c='D', d=4, e=42}]
 */
// 按C进行分组,并对D汇总
Map<String, Integer> d = list.stream()
    .collect(Collectors.groupingBy(Person::getC, Collectors.summingInt(Person::getD)));
// {A=62, B=28, C=20, D=30}

// 按C进行分组,并对E汇总(不支持BigDecimal,使用reducing解决)
Map<String, BigDecimal> e = list.stream().collect(Collectors.groupingBy(Person::getC, Collectors.reducing(BigDecimal.ZERO, Person::getE ,BigDecimal::add)));
// {A=270, B=102, C=67, D=76}

// 一次返回以分组统计好的通类型对象
List<Person> collect = group.entrySet().stream().map(entry -> {
    Person p = new Person();
    p.setC(entry.getKey());
    p.setD(entry.getValue().stream().map(Person::getD).reduce(Integer::sum).orElse(0));
    p.setE(entry.getValue().stream().map(Person::getE).reduce(BigDecimal.ZERO, BigDecimal::add));
    return p;
}).collect(Collectors.toList());
// [Person{c='A', d=75, e=304}, Person{c='B', d=4, e=17}, Person{c='D', d=36, e=255}]

推荐阅读