Java函数式编程

1. 课程介绍

1.1 课程介绍和环境配置

课程介绍

  • 开发环境和数据对象的介绍
  • 函数式编程介绍
  • 函数式编程的特点

流的常见操作符和应用场景

  • 什么是流
  • 基础操作符
  • Optional流
  • 收集器之集合对象
  • 分组统计和聚合函数
  • 收集阶段的操作符
  • 排序

高阶操作符

  • flatMap
  • reduce

1.2 函数式编程介绍

​ 函数式编程是一种是一种编程范式,它将计算视为函数的运算,并避免变化状态和可变数据。它是一种声明式编程范式,也就是说,编程是用表达式或声明而不是语句来完成的。

​ Lamda表达式:(a, b) -> a + b

​ 比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

userList.stream().skip(1).peek(user -> log.debug("user: {}", user.getUsername())).collect(Collectors.toList())
函数式带来哪些好处
  • addActionListener
//java7
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        doSomethingwith(e);
    });
}
  //java8                       
button.addActionListener(e->doSomethingwith(e)) ;
  • sort
//java7
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});
//java8  
Collections.sort(list, (o1,o2)->o1.compareTo(o2));
  • 给出符合以下条件的作者列表
    • 页数最多的前三条
    • 除了书名为葵花宝典的
@Test
public void givenListOfBooks_thenExplainTheAdvantageOfFunction() {
    String authorA = "张三";
    String authorB = "李四";
    String authorC = "王五";
    String authorD = "前朝太监";
    List<Book> books = Arrays.asList(
            new Book("C++编程", authorA, 1216),
            new Book("C#编程", authorA, 365),
            new Book("Java编程", authorB, 223),
            new Book("Ruby编程", authorB, 554),
            new Book("21天精通Python", authorB, 607),
            new Book("21天精通Go", authorC, 352),
            new Book("葵花宝典", authorD, 1200),
            new Book("编程圣经", authorA, 320)
    );
    List<Book> booksFiltered = new ArrayList<>();
    for (Book book : books){
        if (! "葵花宝典".equals(book.getTitle())) {
            booksFiltered.add(book);
        }
    }
    booksFiltered.sort(new Comparator<Book>() {
        @Override
        public int compare(Book o1, Book o2) {
            return o2.getPages().compareTo(o1.getPages());
        }
    });
    for (int i=0; i<3; i++) {
        System.out.println(booksFiltered.get(i).getAuthor());
    }
    // 函数式编程
    books.stream()
            .filter(b -> ! "葵花宝典".equals(b.getTitle()))
            .sorted((b1, b2) -> b2.getPages().compareTo(b1.getPages()))
            .limit(3)
            .map(Book::getAuthor)
            .distinct()
            .forEach(System.out::println);
}

1.3 函数式编程的特点

纯函数
  • 函数的执行没有副作用
  • 返回值仅依赖于输入参数
// 类似于该方法的 返回值仅依赖于输入参数 
static class ObjectWithPureFunction {
    public int sum(int a, int b) {
        return a + b;
    }
}

//nextValue不唯一确定返回值,依赖于成员变量value
static class ObjectWithNonPureFunction {
    private int value = 0;
    public int sum(int nextValue) {
        this.value += nextValue;
        return this.value;
    }
}
高阶函数
  • 函数的参数可以是一个/多个函数
@Test
public void givenFunctionalInterface() {
    val higherOrderFunctionClass = new HigherOrderFunctionClass();
    IFactory<User> factory = higherOrderFunctionClass.createFactory(
            () -> User.builder().id(100L).name("imooc").build(),
            (user) -> { 
                log.debug("用户信息:{}", user);
                user.setMobile("13012345678");
            });
}
  • 函数的返回值也可以是一个函数
interface IFactory<T> {
    T create();
}
interface IProducer<T> {
    T produce();
}
interface IConfigurator<T> {
    void configure(T t);
}
static class HigherOrderFunctionClass {
    public <T> IFactory<T> createFactory(IProducer<T> producer, IConfigurator<T> configurator) {
        return () -> {
            T instance = producer.produce();
            configurator.configure(instance);
            return instance;
        };
    }

测试代码

@Test
public void givenFunctionalInterface_thenFunctionAsArgumentsOrReturnValue() {
    val higherOrderFunctionClass = new HigherOrderFunctionClass();
    IFactory<User> factory = higherOrderFunctionClass.createFactory(
            () -> User.builder().id(100L).name("imooc").build(),
            (user) -> {
                log.debug("用户信息:{}", user);
                user.setMobile("13012345678");
            });
    User user = factory.create();
    assertEquals("imooc",user.getName());
    assertEquals(100L,user.getId());
    assertEquals("13012345678",user.getMobile());
   
}
Lamda表达式
  • 箭头左边是参数列表,右边是函数体
  • 方法引用class::method

静态方法引用

//Combine combine = (a, b) -> User.combine(a, b);
Combination combination = User::combine;

参数方法引用

//BiFunction<String, String, Integer> paramRef = (a, b)->a.indexOf(b);
BiFunction<String, String, Integer> paramRef = String::indexOf;

实例方法引用

User user = User.builder( ).username(" zhangsan").build();
//Supplier<String> supplier = () -> user.getUsername();
Supplier<String> supplier = user:getUsername;

构造器引用

//Supplier<User> supplie = () -> new User();
Supplier<User> supplier = User:new;
Java函数式接口
  • 有且仅有一个未实现的非静态方法的接口叫做"函数式接口"
interface IProducer<T> {
    T produce();
}
interface IConfigurator<T> {
    void configure(T t);
}


() -> User.builder().id(100L).name("imooc").build()
    
(user) -> {
    log debug(“用户信息:{}" , user);
     user.setMobile("13012345678");
}

内建的函数式接口
Function
  • 表示一个函数(方法) ,它接受-个单- -的参 数并返回一个单一的值。

Function<Long, Long> some = (value)-> value + 3;

Long resultLambda = some .apply((long) 8);

Predicate
  • 表示一个简单的函数,它只取一个值作为参数,并返回真或假。

Predicate predicate = (value)-> value != null;

UnaryOperator
  • 代表一个操作,它接受-个参数,并返回-一个相同类型的参数。

UnaryOperator unaryOperator = (user)-> {
user.setName(“New Name’”);
return user;
};

BinaryOperator
  • 代表-一个接受两个参数并返回一个值的操作

BinaryOperator binaryOperator = (a, b)->a+b;

Supplier
  • 代表一个函数,它提供了某种类型的值。

Supplier supplier = ()-> (int) (Math.random() * 1000D);

Consumer
  • 代表一个接受-个参数而不返回任何值的函数。

Consumer consumer = (value)-> log .debug(“{}”, value);

函数的组合
  • and/or
Predicate<String> startsWithA = (text) -> text.startsWith("A");
Predicate<String> endsWithX = (text) -> text.endsWith("x");
Predicate<String> startsWithAAndEndsWithX = startsWithA.and(endsWithX);
  • compose/andthen
Function<Integer, Integer> squareOp = (value) -> value * value;
Function<Integer, Integer> doubleOp = (value) ->2 * value;
Function<Integer, Integer> doubleThenSquare = squareOp .compose(doubleOp);

2. 重新认识”流“

2.1 什么是流和创建流的方式

建立流的几种方式
  • Arrays.stream
val list = Arrays.stream(arrayOfUsers).peek(user -> log.debug("user: " , user.getUsername())).collect(Collectors.toList());
  • Collection.stream
val list = userList.stream().peek(user -> log.debug("user: {}" , user.getUsername())).collect( Collectors.toList());
  • Stream.of
val list = Stream.of(arrayOfUsers[0], arrayOfUsers[1], arrayofUsers[2])
    .peek(user -> log.debug("user: {}", user.getUsername()))
    .collect(Collectors.toList());
  • Stream.iterate
val list = Stream.iterate(0, n -> n + 1)
        .limit(10)
        .peek(n -> log.debug("the number is : {}", n)).collect(Collectors.toList());
  • Stream.generate
val list = Stream.generate(() -> Math.random())
       .limit(10)
       .peek(n -> log.debug("the number is : {}" , n)).collect(Collectors.toList());
  • StreamSupport.stream
val itr = userList.iterator();

Spliterator<User> spliterator = Spliterators.spliteratorUnknownSize(itr, Spliterator.NONNULL); 
Stream<User> userStream = StreamSupport.stream(spliterator, false);

val list = userStream
         .peek(user -> log.debug("user: {}" , user.getUsername())).collect(Collectors.toList());
  • IntStream
// range左闭右开
val list = IntStream.range(0,5)
        .boxed()
        .peek( i -> log.debug("the number is {}", i)).collect(Collectors.toList());
// range左闭右闭 0-5 包括5
 list = IntStream.rangeClosed(0,5)
        .boxed()
        .peek( i -> log.debug("the number is {}", i)).collect(Collectors.toList());
  • Stream.builder()
/ 类似于 Stream.of()
Stream.Builder<User> userStreamBuilder = Stream.builder();
val list = userStreamBuilder
        .add(arrayOfusers[0]).add(arrayOfusers[1]).add(arrayOfUsers[2]).build()
        .skip(1)
        .peek(user -> log.debug("user: {}", user.getUsername())
        .collect(Collectors.toList());

2.2 基础操作符

  • filter, map, peek, findAny, findFirst 中间操作符
  • forEach, anyMatch, noneMatch,allMatch 中断操作符
  • count, min, max 具有统计意义的中断操作符
1. filter
val first  = userList.stream()
        .filter(user -> user.getUsername().equals ("Lisi"))
        .findFirst();
assertTrue (first.isPresent);
assertEquals("lisi", first.getUsername());
2. map
List<String> userDTOS = userList.stream().
        map(user -> UserDT0.builder().
                username(usere.getUsernam())
                .name(user.getName())
                .enabled(user.isEnabled() ? "M" : " #")
                .mobile(user.getMobile())
                .build).map(UserDT0:getMobilel)
.collect(toList());
assertEquals(3, userDTOS.size());
3. forEach
userList.stream().forEach(user ->
                user.setEnabled(true));
assertTrue(userList.get(1).isEnabled());
4. forEachOrdered
userList.stream()
    .sorted(Comparator.comparing(User::getUsername))
    .forEachOrdered(user -> log.debug("user:{}", user));
5. anyMatch
val existed  = userList.stream()
        .anyMatch(user -> user.getMobile().startsWith("130"));
assertTrue(existed) ;
6. allMatch noneMatch
boolean allMatched = userList.stream()
        .allMatch(user -> user.getMobile().startsWith("13"));
assertTrue(allMatched);
boolean notMaStched = userList.stream()
        .noneMatch(user -> user.getMobile().startsWith("130"));
assertFalse(notMatched);

2.3 Optional流-异常和空值处理

  • isPresent, isEmpty(Java9新增)
  • orElse, orElseGet, orElseThrow, or(Java9新增)
  • ifPresent, ifPresentOrElse(Java9新增)

orElse是一个常量 orElseGet类似于一个工厂 or表示重新构建一个 Optional对象出来

static Optional<Profile> findByUsername(String username) {
    return Arrays.stream(arrayOfUsers)
            .filter(user -> !"zhangsan".equals(username) && user.getUsername().equals(username))
            .findAny()
            .map(user -> new Profile(user.getUsername(), "Hello, " + user.getName()));
}

ifPresent 只处理有值的情况, ifPresentOrElse有值没值都需要处理

  • ifPresent
repo.findByUsername("zhangsan")
        .map(User::getUsername).ifPresent(username -> {
    log.debug("username: {}", username);
    assertEquals("zhansan", username);
});
  • ifPresentOrElse
repo.findByUsername("zhansan")
        .map(User::getUsername).ifPresentOrElse(
        username -> {
            log.debug("username:{}", username);
            assertEquals("zban5an", username);
        },
        () -> log.debug("cannot reach clse block"));

2.4 收集器-集合对象收集器

收集为一个集合对象– toList, toSet, toMap, toCollection

  • 准备数据
private static final User[] arrayOfUsers = {
        User.builder().id(1L).username("zhangsan").name("张三").age(30).enabled(true).mobile("13000000001").build(),
        User.builder().id(2L).username("lisi").name("张三").age(32).enabled(false).mobile("13000000002").build(),
        User.builder().id(3L).username("wangwu").name("王五").age(41).enabled(true).mobile("13000000003").build(),
};
private List<User> userList = Arrays.asList(arrayOfUsers);
  • toSet()
Set<String> usernames = userList.stream()
        .map(User::getName)
        .collect(toSet());
assertEquals(2, usernames.size());
  • toMap

Map<String, User> userMap = userList.stream()
        .collect(toMap(
                User::getUsername,
                user -> user
        ));
assertTrue(userMap.containsKey("lisi"));


// 处理重复值 
// 第三个参数为用于合并的函数,当前为保存原有的 
Map<String, User> duplicateMap = Stream.concat(userList.stream(), userList.stream())
        .peek(user -> log.debug("username, {}", user.getUsername()))
        .collect(toMap(
                User::getUsername,
                user -> user,
                (existing, replace) -> existing
        ));
assertEquals(3, duplicateMap.keySet().size());

// 第四个参数为用于构建Map的工厂,默认是HashMap
TreeMap<String, User> sortedMap = Stream.concat(userList.stream(), userList.stream())
        .peek(user -> log.debug("username, {}", user.getUsername()))
        .collect(toMap(
                User::getUsername,
                user -> user,
                (existing, replace) -> existing,
                TreeMap::new
        ));
assertEquals("lisi", sortedMap.keySet().stream().findFirst().get());
  • totoCollection
// toCollection 可以定制化符合Collection接口的对象
Comparator<User> byAge = Comparator.comparing(User::getAge);
TreeSet<User> users = userList.stream()
        .collect(toCollection(() -> new TreeSet<>(byAge)));
assertEquals(30, users.stream().map(User::getAge).findFirst().orElse(-1));

2.5 分组统计和聚合函数

  • 聚合计算-averagingXXX, summingXXX, maxBy, counting
  • 分组统计– groupingBy
@Test
public void givenUsers_withSimpleSolarFunction_thenGetResult() {
    //求平均数
    double avg = userList.stream().collect(averagingDouble(User::getAge));
    assertEquals((30 + 32 + 41) /3.0, avg);
    //求和
    int sum = userList.stream().collect(summingInt(User::getAge));
    assertEquals((30 + 32 + 41), sum);
    
     //计数,求和,最小值 最大值等全部求出来
    DoubleSummaryStatistics stat = userList.stream().collect(summarizingDouble(User::getAge));
    assertEquals((30 + 32 + 41.0), stat.getSum());
    assertEquals((30 + 32 + 41) /3.0, stat.getAverage());
}
  • 按每个年龄段分组
Map<Integer, List<User>> map = userList.stream().collect(
        groupingBy(
                user -> (int) Math.floor(user.getAge() / 10.0) * 10
        )
);
  • 分组之后·在做统计
Map<Integer, DoubleSummaryStatistics> map = userList.stream().collect(
        groupingBy(
                user -> (int) Math.floor(user.getAge() / 10.0) * 10,
                summarizingDouble(User::getAge)
        )
);
log.debug("result: {}", map);
  • 分组之后·对list做映射操作
Map<Integer, List<UserDto>> result = userList.stream().collect(
        groupingBy(
                user -> (int) Math.floor(user.getAge() / 10.0) * 10,
                mapping(
                        user -> new UserDto(
                            user.getId(),
                            user.getUsername() + ":" + user.getName()
                        ),
                        toList()
                )
        )
);
log.debug("result: {}", result);

2.6 收集器mapping, joining和collectingAndThen

mapping
  • mapping 收集阶段进行转换,和中间操作map很像,不过有两个参数,第一个是转换操作,第二个是做处理的。第二个参数还是一个collector,可以不断的给链起来
public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                           Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return new CollectorImpl<>(downstream.supplier(),
                               (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
                               downstream.combiner(), downstream.finisher(),
                               downstream.characteristics());
}
  • 分组之后进行过滤
@Test
public void givenStrings_thenMappingAndFiltering_theChainThemTogether() {
    // List.of Java9新增api
    List<String> strings = List.of("bb", "ddd", "cc", "a");
    Map<Integer, TreeSet<String>> result = strings.stream()
            .collect(groupingBy(
                    String::length,
                    mapping(
                            String::toUpperCase,
                            //filtering 为jdk11中新增加api 和中间操作filter类似
                            filtering(
                               // 长度为1的组被过滤掉了
                                    s -> s.length() > 1,
                                    toCollection(TreeSet::new)
                            )
                    )
            ));
    log.debug("result: {}", result);
}
collectingAndThen

collectingAndThen和mapping类型 mapping1是先进行操作在聚合 collectingAndThen是先聚合 譬如 生成一个集合 在进行操作

  • 如何分组生成一个用户集合,并且想要每组用户的平均值
@Test
public void givenUsers_whenGroupingByAgeAndCollectingAndThen_thenGetCustomWithStream() {
    // collectingAndThen 其实就是在最后在做一个单一操作
    Map<Integer, UserStat> map = userList.stream().collect(
            groupingBy(
                    user -> (int)Math.floor(user.getAge() / 10.0) * 10,
                    collectingAndThen(
                            toList(),
                            list -> {
                                double average = list.stream().collect(averagingDouble(User::getAge));
                                return new UserStat(average, list);
                            })
            )
    );
    log.debug("map {}", map);
    assertEquals(2, map.get(30).getUsers().size());
    assertEquals(31.0, map.get(30).getAverage());
}
joining
@Test
public void givenUsers_withJoining_thenGetString() {
    Map<String, String>  requestParams = Map.of(
            "name", "张三",
            "username", "zhangsan",
            "email", "zhangsan@local.dev"
    );
    val url = requestParams.keySet().stream()
            .map(key -> key + "=" + requestParams.get(key))
            .sorted()
            .collect(joining(
                    "&", //分割符
                    "http://local.dev/api?",  //前缀
                    ""   //后缀
            ));
    assertEquals("http://local.dev/api?email=zhangsan@local.dev&name=张三&username=zhangsan", url);
}

2.7 排序

  • 简单类型使用sorted
List<String> list = Arrays.asList( "One", "Abc", "BCD");
val sortedListDefaultSortOperator = list.stream()
        .sorted()
        .collect(toList());
  • sorted可以传入Comparator
val sortedListWithFunction = list.stream()
        .sorted((a, b) -> a.compareTo(b)).collect(toList());
  • 倒序
val descSortedListWithComparator = list.stream()
        .sorted( Comparator.reverseOrder())
        .collect(toList());
  • 自定义排序
val descSortedListWithComparatorComparing = userList.stream()
        .sorted(Comparator.comparing(
                user -> user.getUsername(),(a, b) -> a.compareTo(b)).collect(toList());
  • 中文排序
Collator sortedByZhCN = Collator.getInstance(Locale.SIMPLIFIED_CHINESE);

val sortedUserByChinese = userList.stream().
        sorted(Comparator.comparing(
                User::getName,
                sortedByZhCN
        ))
        .collect(toList());

3. 流的高级操作

3.1 高阶操作符flatMap

高阶 不是说它技术多高阶,而是它是处理高阶流的,处理流的嵌套的

  • 数据准备
private static final User[] arrayOfUsers = {
        User.builder()
                .id(1L)
                .username("zhangsan")
                .name("张三")
                .age(30)
                .enabled(true)
                .mobile("13000000001")
                .roles(List.of("ROLE_ADMIN", "ROLE_USER"))
                .build(),
        User.builder()
                .id(2L)
                .username("lisi")
                .name("李四")
                .age(32)
                .enabled(false)
                .mobile("13000000002")
                .roles(List.of("ROLE_ADMIN"))
                .build(),
        User.builder()
                .id(3L)
                .username("wangwu")
                .name("王五")
                .age(41)
                .enabled(true)
                .mobile("13000000003")
                .roles(List.of("ROLE_USER"))
                .build(),
};
private List<User> userList;
static class ThirdPartyApi {
    static Optional<Profile> findByUsername(String username) {
        return Arrays.stream(arrayOfUsers)
                .filter(user -> !"zhangsan".equals(username) && user.getUsername().equals(username))
                .findAny()
                .map(user -> new Profile(user.getUsername(), "Hello, " + user.getName()));
    }
}
@AllArgsConstructor
@Data
static class Profile {
    private String username;
    private String greeting;
}
@BeforeEach
void setup() {
    userList = Arrays.asList(arrayOfUsers);
}
  • 场景一:获取所有的Role列表集合
//  获取每个用户所有的Role列表
@Test
public void givenUsersWithRoles_whenParentChild_withoutFlatMap() {
    val users = userList.stream()
            .map(User::getRoles)
            .peek(roles -> log.debug("roles {}", roles)).collect(toList());
    log.debug("users: {}", users);
}

// 获取所有的Role列表集合
@Test
public void givenUsersWithRoles_withFlatMap() {
    val users = userList.stream()
            .flatMap(user -> user.getRoles().stream()).peek(role -> log.debug("roles {}", role))
            .collect(toList());
    log.debug("users: {}", users);
}
  • 场景二: map映射获取到Option对象转换为普通对象
@Test
    public void givenUsers_withOptional_thenWithStream() {
        val profiles = userList.stream()
                .map(user -> ThirdPartyApi.findByUsername(user.getUsername())).peek(profile -> log.debug(" profile:{}", profile))
                .collect(toList());
        log.debug(" profiles:{}", profiles);
    }

// map映射获取到Option对象转换为普通对象
@Test
    public void givenUsers_withOptional_thenFlatMapWithStream() {
        val profiles = userList.stream()
                .map(user -> ThirdPartyApi.findByUsername(user.getUsername()))
                .flatMap(Optional::stream)
                .peek(profile -> log.debug(" profile:{}", profile))
                .collect(toList());
        log.debug(" profiles:{}", profiles);
    }

// map映射获取到Option对象转换为普通对象 和上面相同
@Test
    public void givenUsers_withOptional_thenFlatMapWithStream2() {
        val profiles = userList.stream()
                .flatMap(user -> ThirdPartyApi.findByUsername(user.getUsername()).stream())
                .peek(profile -> log.debug(" profile:{}", profile))
                .collect(toList());
        log.debug(" profiles:{}", profiles);
    }

3.2 reduce

  • 执行归集操作–某种程度上和Collect 作用类似

  • 设计上,reduce 应该和不可变对象一起工作。

  • 如果使用可变对象,也可以得到结果,但是不是线程安全的

  • 而且通常意义上来说, reduce 的性能要弱于 collect

  • 但 reduce 是一个非常灵活的选项,在各个语言和框架中有广泛应用

  • 准备数据

private static final User[] arrayOfUsers = {
        User.builder().id(1L).username("zhangsan").name("张三").enabled(true).age(30).mobile("13000000001").build(),
        User.builder().id(2L).username("lisi").name("李四").enabled(false).age(32).mobile("13000000002").build(),
        User.builder().id(3L).username("wangwu").name("王五").enabled(true).age(41).mobile("13000000003").build(),
};
private List<User> userList;
@BeforeEach
void setup() {
    userList = Arrays.asList(arrayOfUsers);
}
  • 使用collect 和 reduce 实现求和
// reduce 是存函数的,处理的是不可变元素
Integer sumByReduce = userList
        .stream()
        .map(User::getAge)
        .reduce(0, (Integer acc, Integer curr) -> {
            log.debug("acc {}, curr: {}", acc, curr);
            return acc + curr;
        });
assertEquals(103, sumByReduce);


// collect 使用可变的容器
MutableInt sumByCollect = userList.stream().collect(
        MutableInt::new,
        (MutableInt container, User user) -> container.add(user.getAge()),
        MutableInt::add
);
assertEquals(103, sumByCollect.getValue());
  • 两数求和
@Test
public void givenTwoNumber_withoutStream_thenSumSuccess() {
    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
    int sum = 0;
    for (Integer number : numbers) {
        sum += number;
    }
    assertEquals(45, sum);
}
@Test
public void givenTwoNumber_withReduce_thenSumSuccess() {
    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
    int sum = numbers.stream().reduce(0, (acc, curr) -> acc + curr);
    assertEquals(45, sum);
}
  • 找出最大的
// 元素中只有一个元素
@Test
public void givenUsers_thenReduceToMaxId() {
    Optional<User> userOptional = userList.stream()
            .reduce((acc, curr) -> {
                log.debug("acc {}, curr {}", acc, curr);
                return acc.getId() > curr.getId() ? acc : curr;
            });
    assertTrue(userOptional.isPresent());
    assertEquals(3L, userOptional.get().getId());
}
  • reduce自己实现reduce
@Test
public void givenUsers_thenReduceToList() {
    List<User> list = userList.parallelStream().reduce(
            // 初始值 
            Collections.emptyList(),
            (acc, curr) -> {
                List<User> newAcc= new ArrayList<>();
                newAcc.addAll(acc);
                newAcc.add(curr);
                return newAcc;
            },
            // combiner 这个函数的作用主要是考虑并行流
            // 并行流的情况下,一个流会分成多个分片进行处理
            // 每一个分片会产生一个临时的中间结果
            // combiner 的作用是把这些中间结果再合并成一个最终结果
            (left, right) -> {
                List<User> merged= new ArrayList<>();
                merged.addAll(left);
                merged.addAll(right);
                return merged;
            }
    );
    assertEquals(3, list.size());
}

3.3 实际应用的难点和知识回顾

  • UserRepo
@Repository
public interface UserRepo extends JpaRepository<User, Long> {
    Optional<User> findOptionalByUsername(String username);
    Stream<User> findByAgeGreaterThan(Integer age);
}
  • UserResource
package com.imooc.stream.rest;

import com.imooc.stream.domain.PageableResult;
import com.imooc.stream.domain.User;
import com.imooc.stream.domain.dto.AddUserDTO;
import com.imooc.stream.domain.dto.UpdateUserDTO;
import com.imooc.stream.domain.dto.UserDTO;
import com.imooc.stream.repo.UserRepo;
import com.imooc.stream.util.Try;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.data.domain.Pageable;
import org.springframework.data.util.Pair;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api")
@RestController
public class UserResource {
    private final UserRepo userRepo;

    @GetMapping("/users")
    public PageableResult<User> getAllUsers(Pageable pageable) {
        val userPageable = userRepo.findAll(pageable);
        return new PageableResult<>(userPageable.getTotalElements(), userPageable.getContent());
    }

    @Transactional(readOnly = true)
    @GetMapping("/users/by-age/{age}")
    public List<UserDTO> getUsersByRoleName(@PathVariable Integer age) {
        return userRepo.findByAgeGreaterThan(age)
                .map(user -> UserDTO.builder()
                        .name(user.getName())
                        .enabled(String.valueOf(user.isEnabled()))
                        .mobile(user.getMobile())
                        .username(user.getUsername())
                        .build())
                .collect(Collectors.toList());
    }

    @GetMapping("/users/{username}")
    public ResponseEntity<UserDTO> getUserByUsername(@PathVariable String username) {
        return userRepo.findOptionalByUsername(username)
                .map(mapUserToDto())
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping("/users")
    public ResponseEntity<UserDTO> addUser(@RequestBody AddUserDTO addUserDTO) {
        if(userRepo.findOptionalByUsername(addUserDTO.getUsername()).isPresent()) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST);
        }
        val toAdd = User.builder()
                .password(addUserDTO.getPassword())
                .enabled(addUserDTO.getEnabled())
                .username(addUserDTO.getUsername())
                .name(addUserDTO.getName())
                .mobile(addUserDTO.getMobile())
                .email(addUserDTO.getEmail())
                .build();
        val saved = userRepo.save(toAdd);
        return mapUserToDto().apply(saved);
    }

    @PutMapping("/users/{username}")
    public ResponseEntity<UserDTO> updateUserByUsername(@PathVariable String username, @RequestBody UpdateUserDTO updateUserDTO) {
        return userRepo.findOptionalByUsername(username)
                .map(saveUser(updateUserDTO))
                .map(mapUserToDto())
                .orElse(ResponseEntity.notFound().build());
    }

    private Function<User, ResponseEntity<UserDTO>> mapUserToDto() {
        return user -> {
            val dto = UserDTO.builder()
                    .username(user.getUsername())
                    .name(user.getName())
                    .enabled(user.isEnabled() ? "激活" : "禁用")
                    .mobile(user.getMobile())
                    .build();
            return ResponseEntity.ok().body(dto);
        };
    }

    @DeleteMapping("/users/{username}")
    public void deleteUserByUsername(@PathVariable String username) {
        userRepo.findOptionalByUsername(username)
                .map(User::getId)
                .ifPresent(userRepo::deleteById);
    }

    private Function<User, User> saveUser(UpdateUserDTO updateUserDTO) {
        return user -> {
            val toSave = user
                    .withMobile(updateUserDTO.getMobile())
                    .withName(updateUserDTO.getName())
                    .withEmail(updateUserDTO.getEmail());
            return userRepo.save(toSave);
        };
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值