15. Java8的型特性
15.1 Lambda表达式
-
Lambda的本质:一个接口的实例,且该接口只能是一个函数式接口(只有一个抽象方法:实例化时,不需要写方法名,唯一没有歧义)
-
从匿名类到 Lambda 的转换举例:
//匿名类 Comparator<Integer> comparator1 = new Comparator<>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1,o2); } }; int compare1 = comparator1.compare(12, 31); System.out.println(compare1); //Lambda表达式写法 Comparator<Integer> comparator2 =(o1,o2) -> Integer.compare(o1,o2); int compare2 = comparator2.compare(51, 31); System.out.println(compare2); //方法引用 Comparator<Integer> comparator3 = Integer :: compare; int compare3 = comparator3.compare(12, 31); System.out.println(compare3);
-
Lambda 表达式: 在Java 8 语言中引入的一种新的语法元素和操作符
-
Lambda 操作符 “->” ,它将 Lambda 分为两个部分:
- 左侧: 指定了 Lambda,表达式需要的参数列表
- 右侧: 指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能
-
Lambda表达式的使用(6种情况):
-
无参,无返回值
Runnable r1 = new Runnable() { @Override public void run() { System.out.println("hh"); } }; r1.run(); Runnable r2 = () -> System.out.println("hh"); r1.run();
-
Lambda 需要一个参数,但是没有返回值
Consumer<String> consumer1 = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; consumer1.accept("hh"); Consumer<String> consumer2 = (String s) -> System.out.println(s); consumer2.accept("hh");
-
数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> consumer2 = (s) -> System.out.println(s);
-
Lambda 若只需要一个参数时, 参数的小括号可以省略
Consumer<String> consumer2 = s -> System.out.println(s);
-
Lambda 需要两个或以上的参数,多条执行语句,并且有返回值
Comparator<Integer> comparator2 =(o1, o2) ->{ System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; int compare2 = comparator2.compare(51, 31); System.out.println(compare2);
-
当 Lambda 体只有一条语句时, return与大括号若有,都可以省略
-
15.2 函数式(Functional)接口
-
如果一个接口中只声明了一个抽象方法,则就是函数式接口
-
可以通过 Lambda 表达式来创建该接口的对象
-
可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口
-
Java 内置四大核心函数式接口
-
其他接口
15.3 方法引用与构造器引用
-
**使用情形:**当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
-
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法
-
**要求:**实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
-
格式: 使用操作符 “: :” 将类(或对象) 与 方法名分隔开来,左侧调用者,右侧被调方法
-
如下三种主要使用情况:
-
对象 :: 实例方法名
Comsumer<String> con = (x) -> System.out.println(x); //等同于 Comsumer<String> con = System.out :: println;
-
类::静态方法名
-
类::实例方法名
-
15.4 强大的Stream API
-
Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中
-
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之, Stream API 提供了一种高效且易于使用的处理数据的方式
-
实际开发中,项目中多数数据源都来自于Mysql, Oracle等。但现在数据源可以更多了,有MongDB, Radis等,而这些NoSQL的数据就需要Java层面去处理
-
Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。 前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算
-
集合讲的是数据, Stream讲的是计算
- Stream 自己不会存储元素
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
-
Stream 的操作三个步骤
-
创建 Stream:一个数据源(如:集合、数组),获取一个流
-
中间操作:一个中间操作链,对数据源的数据进行处理
-
终止操作(终端操作):一旦执行终止操作, 就执行中间操作链,并产生结果。之后,不会再被使用
-
-
Stream的实例化:
-
方式一:通过集合实例化:
class getData { public static List<Person> getArrayList(){ ArrayList<Person> peoples = new ArrayList<>(); peoples.add(new Person("Tom",23)); peoples.add(new Person("Jack",22)); peoples.add(new Person("Mike",21)); peoples.add(new Person("Ted",20)); return peoples; } } public class test{ public static void main(String[] args) { List<Person> peoples = getData.getArrayList(); // default Steam<E> stream() : 返回一个顺序流 Stream<Person> stream = peoples.stream(); // default Steam<E> parallelStream() : 返回一个并行流 Stream<Person> personStream = peoples.parallelStream(); } }
-
方式二:通过数组实例化:
//调用Array类的static <T> stream(T[] array): 返回一个流 int[] ints = new int[]{1,2,3,4,5}; IntStream stream1 = Arrays.stream(ints); Person p1 = new Person("Tom", 22); Person p2 = new Person("Jack", 22); Person[] peoples = {p1, p2}; Stream<Person> stream2 = Arrays.stream(peoples);
-
方式三:通过Stream的of( )
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
-
方式四:创建无限流
//迭代 :public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println); //生成 :public static<T> Stream<T> generate(Supplier<T> s) Stream.generate(Math::random).limit(10).forEach(System.out::println);
-
-
Stream的中间操作:
-
筛选与切片:
-
filter(Predicate p)
:接收 Lambda , 从流中排除某些元素 -
distinct(n)
:筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 -
limit(long maxSize)
:截断流,使其元素不超过给定数量 -
skip(long n)
:跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补List<Person> peoples = getData.getArrayList(); Stream<Person> stream = peoples.stream(); //1.filter(Predicate p):过滤 stream.filter(e -> e.getAge() > 20).forEach(System.out::println); //只有当终止操作forEach()执行时,才会产生结果 //2.limit(n) //stream.limit(3).forEach(System.out::println);//IllegalStateException peoples.stream().limit(3).forEach(System.out::println); //3.skip(n) peoples.stream().skip(1).forEach(System.out::println); //4.distinct():去重(元素类一定要实现hashCode()和equals()) peoples.add(new Person("Tom",23)); peoples.add(new Person("Tom",23)); peoples.stream().distinct().forEach(System.out::println);
-
-
映射:
-
map(Function f)
:接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素List<String> list = Arrays.asList("aa", "bb", "cc", "dd"); //map(Function f) list.stream().map(e -> e.toUpperCase()).forEach(System.out::println); //AA BB CC DD //map练习:找出名字长度大于3的人的名字 List<Person> peoples = getData.getArrayList(); Stream<String> namesstream = peoples.stream().map(e -> e.getName()); namesstream.filter(name -> name.length() > 3).forEach(System.out::println); //Jack Mike
-
flatMap(Function f)
:接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流//类比理解: ArrayList list1 = new ArrayList(); list1.add(1); list1.add(2); list1.add(3); ArrayList list2 = new ArrayList(); list2.add(4); list2.add(5); list2.add(6); //1. //list1.add(list2); //System.out.println(list1); //[1, 2, 3, [4, 5, 6]],集合里面套集合 //2. list1.addAll(list2); System.out.println(list1); //[1, 2, 3, 4, 5, 6],将list2拆开放入list1
public class test{ //此方法返回一个stream流 public static Stream<Character> fromStringToStream(String str){ ArrayList<Character> list = new ArrayList<>(); for (Character c : str.toCharArray() ) { list.add(c); } return list.stream(); } public static void main(String[] args) { List<String> list = Arrays.asList("aa", "bb", "cc", "dd"); //flatMap(Function f) //每次将list中的每个字符串传入方法后,就得到一个流 //如果使用map会得到流中包含流 Stream<Stream<Character>> stream1 = list.stream().map(s -> test.fromStringToStream(s)); stream1.forEach(System.out::println); //结果:流中的流 //java.util.stream.ReferencePipeline$Head@568db2f2 //java.util.stream.ReferencePipeline$Head@378bf509 //java.util.stream.ReferencePipeline$Head@5fd0d5ae //java.util.stream.ReferencePipeline$Head@2d98a335 //如果使用flatMap会将每个流的内容拿出来,合到一个流中 Stream<Character> stream = list.stream().flatMap(s -> test.fromStringToStream(s)); stream.forEach(System.out::print); //a a b b c c d d } }
-
mapToDouble(ToDoubleFunction f)
:接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream -
mapToInt(ToIntFunction f)
:接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream -
mapToLong(ToLongFunction f)
:接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream
-
-
排序:
-
sorted():产生一个新流,其中按自然顺序排序
List<String> list = Arrays.asList("aa", "bb", "dd", "cc"); list.stream().sorted().forEach(System.out::println);
-
sorted(Comparator com):产生一个新流,其中按比较器顺序排序
//List<Person> peoples = getData.getArrayList(); //peoples.stream().sorted().forEach(System.out::println); //抛出ClassCastException,Person没有实现Comparable接口 //定制排序,按年龄排 List<Person> peoples = getData.getArrayList(); peoples.stream().sorted((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge())).forEach(System.out::println);
-
-
-
Stream的终止操作:
-
匹配与查找
-
allMatch(Predicate p)
:检查是否匹配所有元素 -
anyMatch(Predicate p)
:检查是否至少匹配一个元素 -
noneMatch(Predicate p)
:检查是否没有匹配所有元素 -
findFirst()
:返回第一个元素 -
findAny()
:返回当前流中的任意元素 -
count()
:返回流中元素总数 -
max(Comparator c)
:返回流中最大值 -
min(Comparator c)
:返回流中最小值 -
forEach(Consumer c)
:内部迭代使用 Collection 接口需要用户去做迭代, 称为外部迭代,
Stream API 使用内部迭代——它帮你把迭代做了
-
-
归约
-
reduce(T iden, BinaryOperator b)
:可以将流中元素反复结合起来,得到一 个值。返回 TList<Integer> list = Arrays.asList(1,2,3,4,5,6); Integer sum = list.stream().reduce(0, (x, y) -> Integer.sum(x, y)); System.out.println(sum);//21
-
reduce(BinaryOperator b)
:可以将流中元素反复结合起来,得到一 个值。返回 Optional<T>List<Person> peoples = getData.getArrayList(); Stream<Integer> ageStream = peoples.stream().map(p ->p.getAge()); Optional<Integer> ageSum = ageStream.reduce((x, y) -> Integer.sum(x, y)); System.out.println(ageSum);
-
-
收集
collect(Collector c)
:将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法List<Person> peoples = getData.getArrayList(); //收集到List中 List<Person> personList = peoples.stream().filter(p -> p.getAge() > 20).collect(Collectors.toList()); personList.forEach(System.out::println); //收集到Set中 Set<Person> personSet = peoples.stream().filter(p -> p.getAge() > 20).collect(Collectors.toSet()); personSet.forEach(System.out::println);
-
15.5 Optional类(减少空指针异常)
-
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在
-
原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常
-
Optional提供的方法
-
创建Optional类对象的方法:
-
Optional.of(T t)
: 创建一个 Optional 实例, t必须非空Person person = new Person(); //person = null;//NullPointerException不可为空 Optional<Person> person1 = Optional.of(person); System.out.println(person1);
-
Optional.empty()
: 创建一个空的 Optional 实例 -
Optional.ofNullable(T t)
: t可以为nullPerson person = new Person(); person = null;//可以为空 Optional<Person> person2 = Optional.ofNullable(person); System.out.println(person2);//Optional.empty
-
-
判断Optional容器中是否包含对象:
boolean isPresent()
: 判断是否包含对象- void ifPresent(Consumer<? super T> consumer) : 如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它
-
获取Optional容器的对象:
- T get(): 如果调用对象包含值,返回该值,否则抛异常
T orElse(T other)
: 如果有值则将其返回,否则返回指定的other对象- T orElseGet(Supplier<? extends T> other) : 如果有值则将其返回,否则返回由Supplier接口实现提供的对象
- T orElseThrow(Supplier<? extends X> exceptionSupplier) : 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常
-
-
示例:
//避免空指针异常,不再使用之前的多重判断套嵌 if(xxx != null){ if(xxx.yy != null){ ... } }
@Test public void test1() { Boy b = new Boy("张三"); //b.getGrilFriend()可能为null //故用ofNullable()将b.getGrilFriend()包装成Optional Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend()); // 如果女朋友存在就打印女朋友的信息 opt.ifPresent(System.out::println); } @Test public void test2() { Boy b = new Boy("张三"); Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend()); // 如果有女朋友就返回他的女朋友,否则只能欣赏“嫦娥”了 Girl girl = opt.orElse(new Girl("嫦娥")); System.out.println("他的女朋友是: " + girl.getName()); }