前言
Java SE 8.0 发布于 2014-3。1.8 给我们带来的变革还是挺大的,新增 Api 和语法不仅提升了性能,也改变了我们的编码习惯。最好的地方是提升了开发效率。
interface 支持静态方法和默认方法。
Lambda 表达式让我们基本告别匿名内部类,并且 Lambda 表达式在字节码上的处理带来的性能提升大于使用匿名内部类。
Stream 让我们如同操作 mysql 一样处理数据集
更方便的使用重复注解
新增的日期 Api 用的人都说好。
还有一些新增 api、更智能的类型推断 、数据结构的优化及虚拟机的优化,等等
interface 支持静态方法和默认方法。
Lambda 表达式会和函数式接口挂钩的,所以先看看新的接口怎么玩吧。
public interface InterfaceDemo {
// 定义的方法
int getAge();
// 默认方法
default String getName(){
return "我叫阿良,我是一名剑客";
}
// 静态方法
static String getGender(){
return "男";
}
public static void main(String[] args) {
// 使用匿名内部类
InterfaceDemo interfaceDemo =new InterfaceDemo() {
@Override
public int getAge() {
return 0;
}
};
System.out.println(interfaceDemo.getAge());
System.out.println(interfaceDemo.getName());
System.out.println(InterfaceDemo.getGender());
// 使用 Lambda 表达式
InterfaceDemo interfaceDemo1 = ()-> 10;
System.out.println(interfaceDemo1.getAge());
System.out.println(interfaceDemo1.getName());
}
}
-
函数式接口
- 仅有一个抽象方法
- 可以有多个默认方法和静态方法
为了在编译阶段就能检测接口是否符合函数式接口定义,可以增加注解 @FunctionalInterface。如果出现多个抽象方法,语法错误
@FunctionalInterface
public interface InterfaceDemo {
int getAge();
default String getName(){
return "我叫阿良,我是一名剑客";
}
default String getName1(){
return "我叫阿良,我是一名剑客";
}
static String getGender(){
return "男";
}
static String getGender1(){
return "男";
}
}
Lambda 表达式
其实语法很简单
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
public class Demo {
@Test
public void run1() {
// Lambda 表达式
// (t1)->{
// return t1.getId();
// };
Function<UserDTO,Integer> function = (t1)->{
return t1.getId();
};
}
}
使用的时候不必过意追求 lambda 语法糖的使用,先用最基本语法的就行。孰能生巧,慢慢的别的形式的语法糖你也会了。
Stream
写代码的时候呢,我们可能会遇到这样的需求
- 筛选出年龄大于 20 的用户
- 将数据集分组,男的一组,女的一组
- 将数据集分组,找出男组中年龄最大用户,女组年龄最大用户
- 对数据集进行分组求和,分组求平局值
- 对数据集排序
- 对数据集转化为别的类型
- 对数据集断言,年龄有没有大于 25 的
- 对数据集进行去重
- 等等
以上操作是不是和操作 mysql 差不多,Stream 的引入将简化我们的操作,并行 Stream 还可以对大数据集分片计算,最终合并,提高效率。听听是不是就觉得有点意思。
创建 Stream 流
- List.Stream 创建流
@Test
public void run6() {
final List<String> strings = Arrays.asList("1", "2", "3", "4");
// 比较常见的一种
final Stream<String> stream = strings.stream();
}
- Stream.of 创建 Stream 流
@Test
public void run1() {
final Stream<String> stream = Stream.of("陈平安", "宁姚");
}
- Stream.iterate 生成 Stream 流
// 流是无限的,所以要结合 limit(n) 来限制个数,否则生成 Long.MAX_VALUE 个
Stream.iterate(2, n -> n + 2).limit(5)
- Stream.generate 也是无限流
// 生成随机数,流是无限的,所以要结合 limit(n) 来限制个数,否则生成 Long.MAX_VALUE 个
Stream.generate(() -> Math.random()).limit(5);
Stream.filter 对数据集进行过滤
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO implements Serializable {
private static final long serialVersionUID = 7240658208223103787L;
private Integer id;
private String username;
private Integer age;
private Gender gender;
}
private List<UserDTO> userDTOS;
@Before
public void before() {
userDTOS = new ArrayList<>(16);
userDTOS.add(UserDTO.builder().age(18).id(1).username("陈平安").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(19).id(2).username("宁姚").gender(Gender.FEMALE).build());
userDTOS.add(UserDTO.builder().age(21).id(4).username("周米粒").gender(Gender.FEMALE).build());
userDTOS.add(UserDTO.builder().age(23).id(6).username("李宝瓶").gender(Gender.FEMALE).build());
userDTOS.add(UserDTO.builder().age(22).id(5).username("阮秀").gender(Gender.FEMALE).build());
userDTOS.add(UserDTO.builder().age(20).id(3).username("崔东山").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(30).id(7).username("齐静春").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(32).id(9).username("阿良").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(31).id(8).username("陈清都").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(33).id(10).username("左右").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(41).id(12).username("崔诚").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(42).id(13).username("李希圣").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(34).id(11).username("裴钱").gender(Gender.FEMALE).build());
userDTOS.add(UserDTO.builder().age(44).id(15).username("崔瀺").gender(Gender.MALE).build());
userDTOS.add(UserDTO.builder().age(43).id(14).username("文圣老爷").gender(Gender.MALE).build());
}
以上是每个 api 会用到的一些数据
筛选出年龄大于 21 ,小于 30 的用户
List<UserDTO> collect = userDTOS.stream()
.filter(userDTO -> userDTO.getAge() < 30)
.filter(userDTO -> userDTO.getAge() > 21)
.collect(Collectors.toList());
上述写法尽管没有错误,但是可以优化 filter ,减少迭代次数
Predicate<UserDTO> predicate= userDTO -> userDTO.getAge() < 30;
// and(与),or(或),negate(非)
Predicate<UserDTO> and = predicate.and(userDTO -> userDTO.getAge() > 21);
final List<UserDTO> collect = userDTOS.stream().filter(and).collect(Collectors.toList());
System.out.println(collect);
上述只是举个例子,也可以在一个断言中写所有的逻辑
Stream.map
// 将数据集中的每个项转换为另一个对象, UserDTO->UserBO
final List<UserBO> collect = userDTOS.stream()
.map(userDTO -> UserBO.builder().age(userDTO.getAge()).name(userDTO.getUsername()).build())
.collect(Collectors.toList());
Stream.sorted
将数据集中排序
@Test
public void sorted() {
final List<UserDTO> collect = userDTOS.stream()
// 根据 id 排序(升序),然后 reversed 取反,最终也就是降序
.sorted(Comparator.comparing(UserDTO::getId).reversed())
.collect(Collectors.toList());
}
Stream.distinct 对数据集去重,根据 equals 去重,所以对象需要重写 equals方法
final Stream<String> a1 = Stream.of("a1", "a2", "a1", "a2", "a3", "a4");
a1.distinct().collect(Collectors.toList());
peek 对数据集处理,区别 forEach 就是他返回新的 Stream
System.out.println(Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList()));
打印的是
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR
[THREE, FOUR]
Stream 操作中,有些 api 可以返回新的 Stream 例如 filter,peek,map, 这些中间操作(返回新的 Stream 流)的api会串起来执行,等到最终操作一起运算返回结果。
可以中间看看 reduce 和 collect
Stream.reduce
@Test
public void run1() {
final Stream<String> a1 = Stream.of("a1", "a3", "a5");
// t1 为每次累计的结果,第一次即为 aaa
final String aaa = a1.reduce("aaa", (t1, t2) -> {
System.out.println(t1);
System.out.println(t2);
StringJoiner stringJoiner = new StringJoiner("--");
stringJoiner.add(t1).add(t2);
return stringJoiner.toString();
});
// aaa--a1--a3--a5
System.out.println(aaa);
}
下面这个操作在并行流中会遇到,并行流实际是用的 ForkJoinPool ,并行流会使用这个线程池中的线程对数据集分片进行计算,然后再将分片结果合并
@Test
public void run2() {
final Stream<Integer> limit = Stream.iterate(1, UnaryOperator.identity()).limit(100000);
// BinaryOperator 合并操作,只在并行流中生效
System.out.println(limit.parallel().reduce(2, (t1, t2) -> t1 + t2, BinaryOperator.maxBy((t1, t2) ->
{
System.out.println(t2);
System.out.println(t1);
return t2 - t1;
}
)));
}
这个操作可以看出来,使用 ForkJoin 进行数据处理,对数据进行分片处理,然后合并
@Test
public void run6() {
final Stream<Integer> limit = Stream.iterate(1, UnaryOperator.identity()).limit(100);
final Integer reduce = limit.parallel().reduce(0, (t1, t2) -> {
System.out.println(t1);
System.out.println(t2);
return t1 + t2;
});
System.out.println(reduce);
}
下面就是简单的对结果进行累加,累积的结果是第一个参数 left(left,right)
@Test
public void run6() {
final Stream<Integer> limit = Stream.iterate(1, UnaryOperator.identity()).limit(100);
final Integer reduce = limit.reduce(0, (t1, t2) -> {
System.out.println(t1);
System.out.println(t2);
return t1 + t2;
});
System.out.println(reduce);
}
collect
- 求平均值
userDTOS.stream().collect(Collectors.averagingInt(UserDTO::getId));
userDTOS.stream().collect(Collectors.averagingLong(UserDTO::getId));
userDTOS.stream().collect(Collectors.averagingDouble(UserDTO::getId));
- 求其中最大值的项
Optional 也要了解下,这个挺好用的
@Test
public void max() {
System.out.println(userDTOS.stream().max(Comparator.comparingInt(UserDTO::getId)));
System.out.println(userDTOS.stream().collect(Collectors.maxBy(Comparator.comparingInt(UserDTO::getId))));
System.out.println(userDTOS.stream().collect(Collectors.maxBy(Comparator.comparing(UserDTO::getId))));
System.out.println(userDTOS.stream().collect(Collectors.maxBy((t1, t2) -> t1.getId() - t2.getId())));
}
先来分析下 <R, A> R collect(Collector<? super T, A, R> collector),有的时候看 Api 也能看昏头,因为你不知道返回值是什么,都是泛型。缩小范围,看 R ,既 Collector 中第三个泛型,看这个基本就知道最终操作是啥了
- 有的时候我们会对 collector 操作的结果再做处理,可以按照下面这样处理
@Test
public void collectingAndThen() {
// 先找出年龄最大的项,t2 为 Optional,然后对这个进行操作
String collect = userDTOS.stream().collect(
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(UserDTO::getId)), t2 -> {
return t2.get().getId() + "-最大 id";
}));
System.out.println(collect);
}
- 使用 collect 进行计数
@Test
public void counting() {
Long collect = userDTOS.stream().collect(Collectors.counting());
System.out.println(collect);
// 不适用 collect 进行计数
System.out.println(userDTOS.stream().count());
}
- 使用 collect 进行分组
当对并行流分组是,使用 groupingByConcurrent 用法和 groupingBy 一样,只是返回的 Map 为 ConcurrentMap
@Test
public void groupingBy1() {
Map<Gender, List<UserDTO>> collect = userDTOS.stream()
// 依据性别分组
.collect(Collectors.groupingBy(t1 -> t1.getGender()));
}
有的时候呢,我们对年龄分组之后,还想再按照年龄继续分组
@Test
public void groupingBy2() {
final Map<Gender, Map<Integer, List<UserDTO>>> collect =
userDTOS.parallelStream()
.collect(Collectors.groupingBy(t1 -> t1.getGender(), Collectors.groupingBy(t2 -> t2.getAge())));
System.out.println(collect);
}
- 有的时候呢,我们只想知道帅哥中最大年龄,美女中最大年龄
@Test
public void groupingBy3() {
final Map<Gender, Optional<Integer>> collect = userDTOS.stream()
.collect(Collectors.groupingBy(UserDTO::getGender,
Collectors.mapping(UserDTO::getAge, Collectors.maxBy(Comparator.comparingInt(t1->t1.intValue())))));
System.out.println(collect);
}
- 我现在只想知道帅哥中最大年龄的是谁,最小年龄的是谁
@Test
public void groupingBy4() {
final Map<Gender, Optional<UserDTO>> collect = userDTOS.stream()
.collect(Collectors.groupingBy(UserDTO::getGender,
Collectors.mapping(Function.identity(), Collectors.maxBy(Comparator.comparingInt(UserDTO::getAge)))));
System.out.println(collect);
}
看了之后是不是感觉 Stream 是不是很强大啊
- 可能会对字符串进行拼接的操作
@Test
public void joining2() {
final Stream<String> a1 = Stream.of("a1", "a2", "a3", "a4", "a5");
final String collect = a1.collect(Collectors.joining("-"));
// a1-a2-a3-a4-a5
System.out.println(collect);
}
@Test
public void joining1() {
final Stream<String> a1 = Stream.of("a1", "a2", "a3", "a4", "a5");
final String collect = a1.collect(Collectors.joining());
// a1a2a3a4a5
System.out.println(collect);
}
@Test
public void joining3() {
final Stream<String> a1 = Stream.of("a1", "a2", "a3", "a4", "a5");
final String collect = a1.collect(Collectors.joining("-", "前缀", "后缀"));
// 前缀a1-a2-a3-a4-a5后缀
System.out.println(collect);
}
上述操作都是基本操作,有的时候我们需要对一个 bean 中的某个字段进行拼接,这种骚操作 collect 也能完成
@Test
public void run4() {
final String collect = userDTOS.stream().collect(
Collectors.mapping(UserDTO::getUsername, Collectors.joining("-")));
System.out.println(collect);
}
看到这里,你可能都忍不住要敲代码试试了, Stream 怎么是无比强大
- 找到 Stream 中的最大或最小项
@Test
public void maxBy() {
final Optional<UserDTO> collect = userDTOS.stream().collect(
Collectors.maxBy(Comparator.comparing(UserDTO::getAge)));
System.out.println(collect.get());
}
@Test
public void minBy() {
final Optional<UserDTO> collect = userDTOS.stream().collect(
Collectors.minBy(Comparator.comparing(UserDTO::getAge)));
System.out.println(collect.get());
}
- partitioningBy,类似分组,只是 key 是 boolean
@Test
public void partitioningBy() {
final Map<Boolean, List<UserDTO>> collect = userDTOS.stream().collect(Collectors.partitioningBy(userDTO -> userDTO.getGender().equals(Gender.MALE)));
}
@Test
public void partitioningBy2() {
final Map<Boolean, Optional<UserDTO>> collect = userDTOS.stream().collect(Collectors.partitioningBy(userDTO -> userDTO.getGender().equals(Gender.MALE), Collectors.maxBy(Comparator.comparing(UserDTO::getAge))));
final Map<Boolean, Optional<UserDTO>> collect2 =
userDTOS.stream().collect(
Collectors.partitioningBy(userDTO -> userDTO.getGender().equals(Gender.MALE), Collectors.maxBy(Comparator.comparingInt(UserDTO::getAge))));
}
- 有的时候我们还会对数据进行求和
平均数,最大值,最小值 都给你计算了,舒服
@Test
public void run6() {
IntSummaryStatistics collect = userDTOS.stream().collect(
Collectors.summarizingInt(UserDTO::getId));
final double average = collect.getAverage();
System.out.println(average);
System.out.println(collect.getCount());
System.out.println(collect.getMax());
System.out.println(collect.getMin());
System.out.println(collect.getSum());
}
- 求数据集中年龄最大的项,Collectors.reducing
@Test
public void reduce() {
final Optional<UserDTO> collect = userDTOS.stream().collect(
Collectors.reducing(BinaryOperator.maxBy(Comparator.comparingInt(UserDTO::getId))));
System.out.println(collect.get());
}
至此 Stream 流大致过完,墙裂推荐,花一周时间好好研究下,能提升开发效率,