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);
};
}
}