Java8新特性
Lambda
lambda是一个匿名函数,我们可以理解为lambda表达式是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更加简洁,更加灵活的代码
Java四大内置函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T>费型接口 | T | void | 对类型为T的对象引用操作,包含方法void accept(T t) |
Supplier<T>供给型接口 | 无 | T | 返回类型为T的对象,包含方法T get() |
Function<T, R>函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象,包含方法R apply(T t) |
Predicate<T>断定型接口 | T | boolean | 确定类型为T的对象是否满足某个约束,并返回boolean值,包含方法boolean test(T t) |
方法引用
使用场景当要传递给lambda体的操作,已经有实现的方法了,就可以使用方法引用
方法引用,本质上就是lambda表达式,而lambda表达式作为函数式接口的实例,所以方法引用也是函数式接口的实例
方法引用具体分为三种格式
-
对象::非静态方法
-
public class User { private String name; public User(String name) { this.name = name; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } }
-
User user = new User("wuhunyu"); Supplier<String> supplier = user::getName; System.out.println(supplier.get()); // wuhunyu
-
-
类::静态方法
-
Consumer<String> consumer = System.out::println; consumer.accept("wuhunyu"); // wuhunyu
-
Comparator<Integer> comparator = Integer::compare; System.out.println(comparator.compare(2, 2)); // 0
-
Function<Double, Long> function = Math::round; System.out.println(function.apply(100D)); // 100
-
-
类::非静态方法
-
类直接调用非静态方法需要满足接口的第一个参数是函数体内方法的调用方
-
public class User { private String name; public User(String name) { this.name = name; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void print() { System.out.println("wuhunyu"); } }
-
User user = new User(1, "wuhunyu"); Function<User, String> function = User::getName; System.out.println(function.apply(user));
-
BiConsumer<User, String> biConsumer = User::setName; User user = new User(1, "wuhunyu"); biConsumer.accept(user, "无魂雨"); System.out.println(user);
-
Consumer<User> consumer = User::print; consumer.accept(new User(1, "wuhunyu"));
-
前面两种方式要求接口中抽象方法的形参列表和返回值类型与方法引用方法的形参列表和返回值类型相同。比如Supplier<String>
接口下的String get()
方法与User
类下的String getName()
形参列表和返回值都相同
第三种方式要求参数列表中的第一个参数必须是方法体内方法执行的调用方
构造器引用
函数式接口抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型
// 空参构造
Supplier<User> supplier = User::new;
User user1 = supplier.get();
// 一个参数的构造
Function<Integer, User> function = User::new;
User user2 = function.apply(1);
// 两个参数的构造
BiFunction<Integer, String, User> biFunction = User::new;
User user3 = biFunction.apply(1, "wuhunyu");
数组引用
数组构造与构造器引用类似,将数组类型看成是一个类,数组引用和构造器引用就一样了
Function<Integer, String[]> function = String[]::new;
String[] strings = function.apply(10);
System.out.println(Arrays.toString(strings)); // [null, null, null, null, null, null, null, null, null, null]
Stream
stream流是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤,映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询,也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式
为什么需要Stream?
NoSQL中的数据需要在java层面去处理
Stream和Collection集合的区别?
Colection是一种静态的内存数据结构,而Stream是有关计算的。即集合讲的是数据,Stream讲的是计算。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算
注意:
- stream本身不会存储元素
- stream不会改变源对象。相反,它会返回一个持有结果的新stream对象
- stream操作是延迟执行的。这意味着它会等到需要结果的时候才执行
操作stream流的三个步骤
- 创建stream
- 通过一个数据源(一个集合或一个数组)获取一个流对象
- 中间操作
- 一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作)
- 一旦执行终止操作,就会执行所有的中间操作,并产生结果
- 产生结果的流对象不能被再次使用
Stream流的创建方式
-
list集合
-
List<Integer> list = new ArrayList<>(); // 创建一个顺序流 Stream<Integer> stream = list.stream(); // 创建一个并行流 Stream<Integer> parallelStream = list.parallelStream();
-
顺序流与并行流的区别在于并行流由于并发执行,不保证处理的结果顺序与数据源顺序一致
-
-
数组
-
int[] ints = new int[10]; IntStream stream = Arrays.stream(ints);
-
流的泛型可以是任意类型,根据数组的类型改变
-
-
Stream的of方法
-
Stream<String> stringStream = Stream.of("1", "2", "3", "4", "5");
-
-
无限流
-
// iterate方法 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f){...} // generate方法 public static<T> Stream<T> generate(Supplier<T> s){...}
-
无限流提供两个方法,它们需要开发者提供终止条件
-
按字面意思上理解,iterate方法可以用于迭代,generate方法可以用于数据生成
-
// iterate,迭代从0开始的整数,限制个数为10个 Stream.iterate(0, item -> item + 1).limit(10).forEach(System.out::println); // 随机生成10个随机数 Stream.generate(Math::random).limit(10).forEach(System.out::println);
-
Stream流的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而是在终止操作时一次性全部处理,被称为"惰性求值"
筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的hashCode()和equal()去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉前n个元素的流。若流中元素不足n个,则返回一个空流。与limit()互补 |
测试数据
// 提供空参构造,全参构造,getter,setter,toString等方法,equal指定为id相同
public class User {
private int id;
private String name;
private int age;
private double salary;
}
// 测试数据
public static List<User> generate() {
List<User> users = new ArrayList<>();
users.add(new User(1001, "马化腾", 34, 6000.38));
users.add(new User(1002, "马云", 12, 9876.12));
users.add(new User(1003, "刘强东", 33, 3000.82));
users.add(new User(1004, "雷军", 26, 7657.37));
users.add(new User(1005, "李彦宏", 65, 5555.32));
users.add(new User(1006, "比尔盖茨", 42, 9500.43));
users.add(new User(1007, "任正非", 26, 4333.32));
users.add(new User(1008, "扎克伯格", 35, 2500.32));
return users;
}
// filter
// 打印salary大于7000的所有员工信息
users.parallelStream().filter(user -> user.getSalary() > 7000).forEach(System.out::println);
// limit
// 打印salary大于7000的前两位员工
users.parallelStream().filter(user -> user.getSalary() > 7000).limit(2).forEach(System.out::println);
// skip
// 跳过前5个员工信息
users.stream().skip(5).forEach(System.out::println);
// distinct
// 去重,会去除后续重复的数据,以下添加的数据(name为'马尔扎哈'的记录)被去重掉了
users.add(new User(1008, "马尔扎哈", 21, 8000));
users.parallelStream().distinct().forEach(System.out::println);
注意:
- 流一旦产生结果,便不能被再次使用
映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream |
mapToInt(ToLongFucntion f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
// 打印名称长度大于3的所有名称(所有名称而不是所有记录)
users.stream().map(User::getName).filter(name -> name.length() > 3).forEach(System.out::println);
// mapToInt, mapToLong, mapToDouble都是map的变体,不同在于前者支持对映射做隐式转换
users.stream().mapToInt(User::getId).forEach(System.out::println);
users.stream().mapToLong(User::getAge).forEach(System.out::println);
users.stream().mapToDouble(User::getSalary).forEach(System.out::println);
排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,按比较器顺序排序 |
// 自然排序
List<Integer> list = Arrays.asList(6, 5, 4, 3, 2, 1);
list.stream().sorted().forEach(System.out::println);
// 比较器排序,按年龄倒叙
users.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(System.out::println);
Stream流的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值
流进行了终止操作,不能再次使用
匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
// allMatch,返回一个boolean,判断集合中是否区全部满足条件
boolean allMatch = users.stream().allMatch(user -> user.getAge() > 18);
System.out.println(allMatch); // false
// anyMatch,返回一个boolean,判断集合中是否存在至少一个满足条件的记录
boolean anyMatch = users.stream().anyMatch(user -> user.getAge() > 18);
System.out.println(allMatch); // true
// noneMatch,返回一个boolean,判断集合中是否全部不满足条件
boolean noneMatch = users.stream().noneMatch(user -> user.getAge() < 10);
System.out.println(allMatch);
// findFirst,查找第一个符合条件的记录
Optional<User> first = users.parallelStream().filter(user -> user.getAge() < 20).findFirst();
System.out.println(first); // Optional[User{id=1002, name='马云', age=12, salary=9876.12}]
// findAny,查找任何符合条件的记录
Optional<User> any = users.parallelStream().filter(user -> user.getAge() > 20).findAny();
System.out.println(any);
统计
方法 | 返回值类型 | 描述 |
---|---|---|
count() | long | 统计集合中的元素总数 |
max(Comparator p) | T | 返回最大值 |
min(Comparator p) | T | 返回最小值 |
forEach(Consumer c) | 无 | 循环遍历集合元素,与peek() 的区别在与foreach() 是终止操作;forEach() 是内部迭代(使用Collection接口需要用户去做迭代,称之为外部迭代),相反,Stream API使用内部迭代,即内部封装了迭代的细节,不需要用户来写迭代 |
long count = users.parallelStream().filter(user -> user.getAge() > 20).count();
System.out.println(count); // 7
Optional<User> max = users.parallelStream().filter(user -> user.getAge() > 20).max((u1, u2) -> u1.getId() - u2.getId());
System.out.println(max); // Optional[User{id=1001, name='马化腾', age=34, salary=6000.38}]
Optional<User> max = users.parallelStream().filter(user -> user.getAge() > 20).min((u1, u2) -> u1.getId() - u2.getId());
System.out.println(max); // Optional[User{id=1001, name='马化腾', age=34, salary=6000.38}]
规约
方法 | 描述 |
---|---|
reduce(T iden, B) | 可以将流中元素反复结合起来,得到一个值。返回T |
reduce(Comparator com) | 可以将流中元素反复结合起来,得到一个值。返回Optional<T> |
备注
map和reduce的连接通常被称为map-reduce模式,因Google用它来进行网络搜索而出名
// 统计所有员工的薪水,reduce的第一个参数类似于初始值,一般默认为零值
Double sum = users.stream().map(User::getSalary).reduce(0D, Double::sum);
System.out.println(sum); // 48424.08
Optional<Double> reduce = users.stream().map(User::getSalary).reduce((num1, num2) -> num1 + num2);
System.out.println(reduce); // Optional[48424.08]
收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式,接收一个Collecotr接口的实现,用于给Stream中元素做汇总的方法 |
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List,Set,Map)
此外,Collectors提供了很多静态方法,可以方便地创建常用收集器实例
// 收集薪水大于6000的员工信息
List<User> userList = users.stream().filter(item -> item.getSalary() > 6000).collect(Collectors.toList());
System.out.println(userList);
// 将一个list转换成map
Map<String, User> userMap = users.stream().filter(item -> item.getSalary() > 6000).collect(Collectors.toMap(User::getName, user -> user));
System.out.println(userMap);
Optional
Optional<T>类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在,或者仅仅保存null,表示这个值不存在。原来使用null表示一个值不存在,现在Optional可以更好地表达这个概念,并且可以避免空指针异常
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent方法会返回true,调用get()方法会返回该对象
Optional提供了很多有用的方法
创建Optional类对象的方法
Optional.of(T t)
:创建一个Optional实例,t必须非空Optional.empty()
:创建一个空的Optional实列Optional.ofNullable(T t)
:t可以为null
判断Optional容器中是否包含对象
boolbean isPresent()
:判断是否包含对象void idPresent(Consumer<? super T> consumer)
:如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它
获取Optional容器的对象
T get()
:如果调用对象包含值,返回该值,否则抛异常T orElse(T other)
:如果有值则将其返回,否则返回指定的other对象T orElseGet(Supplier<? extends T> other)
:如果有值则将其返回,否则返回由Supplier接口是实现提供的对象T orElseThrow(Suppier<? extends X> exceptionSupplier)
:如果有值则将其返回,否则抛出由Supplier接口实现提供的异常
User user = new User();
Optional<User> userOptional = Optional.ofNullable(user);
user = userOptional.orElse(new User(1009, "无魂雨", 23, 3000));
System.out.println(user);