一、函数式接口和Lambda表达式
简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例**(可以理解为:函数式接口的实现类对象)**。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
1.定义函数式接口
// TODO: 2021/8/18 判断一个接口是否为函数式接口,可以在接口之前添加注解@FunctionalInterface
@FunctionalInterface
public interface MyNumber<T> {
// TODO: 2021/8/18 接口可以定义为一个泛型,在函数式接口实现类对象中,可以通过类型推断来确定数据类型
T getValue(T t);
}
2.函数式接口实现类对象做为参数应用
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收****Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
public class DreamOne {
//作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda
//表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
public String toUpperString(MyNumber<String> myNumber, String str) {
return myNumber.getValue(str);
}
// TODO: 2021/8/18 测试:能过lambda表达式生成一个函数式接口实现类对象,将其做为参数传传递进去
public void toUpperStringTest(){
MyNumber<String> myNumber = (x)->x;
String s = toUpperString(myNumber, "hellojavac!");
System.out.println(s);
}
}
3.JAVA内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get() |
Function<T, R>消费生产型 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t) |
- 其他接口
以上四大接口和其他接口都为函数式接口,采取泛型,可以通过类型推断,确定函数式接口实现类对象的数据类型。
二、函数式接口的方法引用与构造器引用
1.方法引用
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
个人理解:方法用引,其核心思想是函数式接口不需要自已写lambda表达式来创建函数式接口实现类对象,而是直接使用系统或之前已经写好的,这样也可能通过调用函数式接口实现类对象中方式,产生与之前写好代码的效果
具体代码示例如下:
@Test
public void test56() {
// TODO: 2021/8/18 两层意思:一是方法引用得到的也是一个对象,是函数式接口实现类对象,
// 二是:函数式接口的实现类抽象方法的实现也引用的方法的方法体相同,
// 三是:可以使用系统自带的方法去引用,也可以自已写方法去引用,
Consumer<String> consumer = System.out::println;
consumer.accept("hellothis");
Comparator<Integer> comparator = Integer::compareTo;
Comparator<String> comparator1 = String::compareTo;
int compare = comparator.compare(52, 34);
Consumer<Integer> consumer1 = System.out::println;
consumer1.accept(compare);
}
@Test
public void test71() {
//采用方法引用
BiPredicate<String,String> biPredicate = String::equals;
boolean test = biPredicate.test("java", "hello");
System.out.println(test);
//采用lambda表达式
BiPredicate<String,String> biPredicate1 = (x,y)->{return x.equals(y);};
System.out.println(biPredicate1.test("this", "this"));
}
2.构造引用
格式: ClassName::new
-
与函数式接口相结合,自动与函数式接口中方法兼容。
-
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
@Test
public void test82() {
// TODO: 2021/8/18 构造引用
//构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象
DreamConstractor dreamConstractor = DreamOne::new;
DreamOne dreamOne = dreamConstractor.getDreamOne();
dreamOne.setName("admin");
dreamOne.setAge(123);
//方法引用
Consumer<Object> consumer = System.out::println;
consumer.accept(dreamOne);
}
三、Stream API
-
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
-
Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
-
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
1. 为什么要使用Stream API
-
实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
-
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
2. 什么是Stream
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算!”
注意点:
-
① Stream 自己不会存储元素。
-
② Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
-
③ Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
3. Stream的操作三个步骤
- 创建Stream
一个数据源(如:集合、数组),获取一个流
- 中间操作
一个中间操作链,对数据源的数据进行处理
- 终止操作
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
4. 通过集合创建Stream
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
default Stream stream() : 返回一个顺序流
default Stream parallelStream() : 返回一个并行流
5. 通过数组创建Stream
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
static Stream stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
-
public static IntStream stream(int[] array)
-
public static LongStream stream(long[] array)
-
public static DoubleStream stream(double[] array)
6. 通过Stream的of()方法创建Stream
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
- public static Stream of(T… values) : 返回一个流
7. 创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
- 迭代
public static Stream iterate(final T seed, final UnaryOperator f)
- 生成
public static Stream generate(Supplier s)
@Test
public void test92() {
// TODO: 2021/8/18 迭代的方式
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
iterate.limit(10).forEach(System.out::println);
// TODO: 2021/8/18 生成方式
// TODO: 2021/8/18 采用了lambda表达式
Stream<Double> generate1 = Stream.generate(() -> {
return Math.random();
});
// TODO: 2021/8/18 采用了方法引用
// TODO: 2021/8/18 Stream本身不存储数据,而是在使用的时候将数据源中的
// 数据按指定的生成方法产生数据和Stream一起将数据存储到内存当中
Stream<Double> generate = Stream.generate(Math::random);
generate.limit(100).forEach(System.out::println);
}
8. Stream的中间操作:筛选与切片
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
- 筛选与切片
方 法 | 描 述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
@Test
public void test92() {
// TODO: 2021/8/18 迭代的方式产生数据到Stream中
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
iterate.limit(10).forEach(System.out::println);
// TODO: 2021/8/18 生成方式
// TODO: 2021/8/18 采用了lambda表达式
Stream<Double> generate1 = Stream.generate(() -> {
return Math.random();
});
// TODO: 2021/8/18 采用了方法引用
// TODO: 2021/8/18 Stream本身不存储数据,而是在使用的时候将数据源中的
// 数据按指定的生成方法产生数据和Stream一起将数据存储generate到内存当中
// TODO: 2021/8/18 明确产生数据的方式,
Stream<Double> generate = Stream.generate(Math::random);
// TODO: 2021/8/18 只选前100的数据到stream中,Stream存储在内存当中
Stream<Double> limit = generate.limit(100);
// TODO: 2021/8/18 过滤,只选取大gf 0.5的数据在Stream中
Stream<Double> doubleStream = limit.filter(x -> x > 0.5);
// TODO: 2021/8/18 将Stream中的数据去重
Stream<Double> distinct = doubleStream.distinct();
// TODO: 2021/8/18 跳过前10个元素,将后面的元素继续存储在Stream中
Stream<Double> skip = distinct.skip(10);
// TODO: 2021/8/18 终止操作,将stream中的数据打印出来
doubleStream.forEach(System.out::println);
}9
9. Stream的中间操作:映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
@Test
public void test123() {
// TODO: 2021/8/18 通过迭代的方式道理成数据
Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
// TODO: 2021/8/18 截取前100个数据保存到Stream中
Stream<Integer> limit = iterate.limit(100);
// TODO: 2021/8/18 映射:接收一个函数,这个函数可以通过lambda表达式或方法引用确定的,
// 传递的是一个函数式接口实现类对象,再调用这个对象中的方法
Stream<Integer> integerStream = limit.map(x -> x * 2);
// TODO: 2021/8/18 将int型数据转换成double型,由于是数据从小转大,所以会自动类型转换,在lambda方法体中不需要进行任何操作
DoubleStream doubleStream = integerStream.mapToDouble(x -> x*12);
// TODO: 2021/8/18 将double数据转换成int数据,由于是数据从大到小,所以在表达式方法体中需要强转
IntStream intStream = doubleStream.mapToInt(x -> (int) x);
// TODO: 2021/8/18 将int数据转换从long数据,数据从小到大,会自动类型转换
LongStream longStream = intStream.mapToLong(x -> x);
// TODO: 2021/8/18 将Stream中的一个数据映射成为包含这个数据的一个Stream
LongStream stream = longStream.flatMap(x -> LongStream.of(x));
}
10. Stream的中间操作:排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
@Test
public void test143() {
Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
iterate.limit(100).sorted((x, y) -> {
return x.compareTo(y);
}
).forEach(System.out::println);
}
11. Stream终止操作
-
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
-
流进行了终止操作后,不能再次使用。
11.1 匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
@Test
public void test152() {
Stream<Integer> generate = Stream.generate(() -> {
Integer integer = local.get();
if (integer == null) {
integer = 1;
local.set(integer);
} else {
integer += 1;
local.set(integer);
}
return integer;
});
Stream<Integer> limit = generate.limit(100);
// TODO: 2021/8/18 检查是否匹配所有元素
boolean b = limit.allMatch(x -> x > 0);
// TODO: 2021/8/18 检查是否匹配其中的一个元素
System.out.println(limit.anyMatch(x -> x == 900));
// TODO: 2021/8/18 检查是否没有匹配所有元素
System.out.println(limit.noneMatch(x -> x < 0));
// TODO: 2021/8/18 返回第一个元素 ,产生的是一个Optional对象
Optional<Integer> first = limit.findFirst();
// TODO: 2021/8/18 返回流中任意的一个元素
Optional<Integer> any = limit.findAny();
Integer integer = first.get();
System.out.println(integer);
}
@Test
public void test183() {
Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
Stream<Integer> limit = iterate.limit(100);
// TODO: 2021/8/18 返回流中值的总数
long count = limit.count();
// TODO: 2021/8/18 返回流中的最小值
Optional<Integer> min = limit.min((x,y)->x.compareTo(y));
// TODO: 2021/8/18 返回流中的最大值
Optional<Integer> max = limit.max(Integer::compareTo);
// TODO: 2021/8/18 将流中的值逐一进行打印
limit.forEach(x-> System.out.println(x));
System.out.println(max.get());
}
11.2 归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional |
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
@Test
public void test198() {
Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
Stream<Integer> limit = iterate.limit(100);
// TODO: 2021/8/18 采用方法引用
Optional<Integer> reduce = limit.reduce(Integer::sum);
// TODO: 2021/8/18 采用lambda表达式
Optional<Integer> reduce1 = limit.reduce((x, y) -> x + y);
System.out.println(reduce.get());
}
@Test
public void test209() {
Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
Stream<Integer> limit = iterate.limit(100);
// TODO: 2021/8/18 归约,合并流中的值并加止额外的参数值100
Integer reduce = limit.reduce(100, Integer::sum);
System.out.println(reduce);
}
11.2 收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
-
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
-
另外, Collectors 实现类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法 | 返回类型 | 作用 |
---|---|---|
toList | List | 把流中元素收集到List |
toSet | Set | 把流中元素收集到Set |
toCollection | Collection | 把流中元素收集到创建的集合 |
counting | Long | 计算流中元素的个数 |
summingInt | Integer | 对流中元素的整数属性求和 |
averagingInt | Double | 计算流中元素Integer属性的平均值 |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。如:均值 |
joining | String | 连接流中每个字符串 |
maxBy | Optional | 根据比较器选择最大值 |
reducing | 归约产生的类型 |
四、Optional 类
-
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
-
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
-
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
1. 创建Optional类对象的方法:
-
Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
-
Optional.empty() : 创建一个空的 Optional 实例
-
**Optional.ofNullable(T t) 😗*t可以为null
2. 判断Optional容器中是否包含对象:
-
boolean isPresent() : 判断是否包含对象
-
void ifPresent(Consumer<? super T> consumer) **:**如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
3. 获取Optional容器的对象:
-
T get(): 如果调用对象包含值,返回该值,否则抛异常
-
T orElse(T other) **:**如果有值则将其返回,否则返回指定的other对象。
-
T orElseGet(Supplier<? extends T> other) **:**如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
-
T orElseThrow(Supplier<? extends X> exceptionSupplier) **:**如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
- 代码示例一:
@Test
public void test225() {
// TODO: 2021/8/18 创建Optional对象,有三种创建方法,这是其中之一
Optional<Girl> girlFriend = Optional.ofNullable(new Boy().getGirlFriend());
// TODO: 2021/8/18 判断Optional对象中是否有值,如果有值,就值传递给参数对象进行处理 ,参数数像可以是函数值接口实现类对象
girlFriend.ifPresent(System.out::println);
// TODO: 2021/8/18 如果optional 对象中有值,则返回对象中的值,如果没有,就返回参数中定义的对象
Girl aThis = girlFriend.orElse(new Girl("this", 30));
System.out.println(aThis);
// TODO: 2021/8/18 如果Optional对象中有值,则返回值,如果没有,返回在生产型函数式接口实现类对象所产生的值
Girl hello = girlFriend.orElseGet(() -> {
return new Girl("hello", 25);
});
System.out.println(hello);
}
- 代码示例二:
@Test
public void test243() {
Optional<Employee> admin = Optional.of(new Employee("admin", 5000));
// TODO: 2021/8/18 判断 Optional对象中的数据是否符合参数中的条件,如果符合,则重新创建一个包含此数据的Opitonal对象
//如果不符合,则创建一个空的Optional对象
Optional<Employee> employee = admin.filter(e -> e.getSalary() > 7000);
employee.ifPresent(System.out::println);
// TODO: 2021/8/18 由于Optional对象中的数据为空,所以下面会返回参数中创建的对象
Employee thisgood = employee.orElse(new Employee("thisgood", 6000));
System.out.println(thisgood);
// TODO: 2021/8/18 由于Optional对象为空,所以会返回一个由生产型函数式接口实现类对象创建的对象
Employee hellojava = employee.orElseGet(() -> {
return new Employee("hellojava", 20);
});
System.out.println(hellojava);
}