Lambda就是一个匿名函数,注意!只有函数式接口才可使用Lambda表达式!
Lambda表达式本质就是函数是接口的对象!
@Test
// 展示两个代码中最常见的写法
void contextLoads() {
// Lambda 表达式
Comparator<Integer> comLam = (o1, o2) -> Integer.compare(o1, o2);
// 方法引用
Comparator<Integer> comRef = Integer::compare;
System.out.println(comLam.compare(10, 11) == comRef.compare(10, 11));
}
Lambda格式:
Runnable runSim = new Runnable() {
@Override
public void run() {
System.out.println("xxx");
}
};
Runnable runLam = () -> System.out.println("xxx");
Consumer<String> conSim = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
Consumer<String> conLam = (String s) -> System.out.println(s);
// 沿用 ② 代码
Consumer<String> conLam = (s) -> System.out.println(s);
// 类似的我们还接触过很多,such as:
List<String> sList = new ArrayList<>();
int[] iArr = {0, 1, 2};
// 沿用 ③ 代码
Consumer<String> conLam = s -> System.out.println(s);
Comparator<Integer> compSim = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// ...假装这里还写了一大堆缺心眼逻辑
return o1.compareTo(o2);
}
};
Comparator<Integer> comLam = (o1, o2) -> {
// ...假装这里还写了一大堆缺心眼逻辑
return o1.compareTo(o2);
};
// 沿用 ⑤ 代码
Comparator<Integer> comLam = (o1, o2) -> Integer.compare(o1, o2);
首先官方的接口上挂着@FunctionalInterface注解的就是函数式接口,其次凡是只有一个抽象方法的接口都是函数式接口。
我们可以用Lambad表达式创建函数式接口的对象,我们也可以使用@FunctionalInterface注解标注我们的函数是接口,这样可以检验它到底是不是一个函数式接口(就一个抽象方法,还是很明显的)
在 java.util.function 包下定义了 Java 8 的丰富的函数式接口。
| 函数式接口 | 参数类型 | 返回类型 | 用途 |
|---|---|---|---|
| Consumer 消费型接口 | T | void | 对类型 T的对象应用操作,包含方法:void accept(T t); |
| Supplier 供给型接口 | 无 | T | 返回类型 T的对象,包含方法:T get(); |
| Function 函数型接口 | T | R | 对类型 T的对象操作,返回 R类型结果,包含方法:R apply(T t); |
| Predicate 断定型接口 | T | boolean | 确定类型 T的对象是否满足某约束,并返回布尔值,包含方法:boolean test(T t); |
| 函数式接口 | 参数类型 | 返回类型 | 用途 |
|---|---|---|---|
| BiFunction | T,U | R | 对类型T、U的对象进行操作,返回R类型结果,包含方法:R apply(T t, U u); |
| UnaryOprerator (Function子接口) | T | T | 对类型T进行一元运算,返回T类型结果,包含方法:T apply(T t); |
| BinaryOperator (BiFunction子接口) | T,T | T | 对类型T进行二元运算,返回T类型结果,包含方法:T apply(T t1, T t2); |
| BiConsumer | T,U | void | 对类型T、U的对象进行操作,包含方法:void accept(T t, U u); |
| BiPredicate | T,U | boolean | 根据参数判断断言并返回布尔值,包含方法:boolean test(T t, U u); |
| ToIntFunction ToLongFunction ToDoubleFunction | T | int/long/double | 对T类型对象进行处理,返回对应类型的结果,包含方法依次为:int applyAsInt(T value);long applyAsLong(T value);double applyAsDouble(T value); |
| IntFunction LongFunction DoubleFunction | int/long/double | R | 对对应类型对象进行处理,返回R类型的结果,包含方法依次为:R apply(int value);R apply(long value);R apply(double value); |
当要传给Lambda体的操作,已经有实现方法了,就可以使用方法引用!
方法引用可以看作是Lambda表达式的深层次的表达,换句话说,方法引用就是Lambda表达式,也是函数式接口的一个实例,通过方法名来指向一个方法,学过C/C++的就更好理解了,就是传递指针(如有问题可联系小白,目前小白是这么理解的)。
格式:使用操作符 :: 将类(或对象)与方法名分隔开
如下三种主要使用情况:(前两种必须保证形参列表和返回值一致)
对象::实例方法名
/*---------------------栗1----------------------*/
// Lambda
Consumer<String> conLam = s -> System.out.println(s);
// MR
Consumer<String> conMr = System.out::println;
/*---------------------栗2----------------------*/
// 假设Dog类有个属性是name(包含setter/getter)
// Lambda
Supplier<String> supLam = ()->dog.getName();
// MR
Supplier<String> supMr = dog::getName;
类::静态方法名
/*---------------------栗1----------------------*/
// Lambda
Comparator<Integer> comLam = (o1, o2) -> Integer.compare(o1, o2);
// MR
Comparator<Integer> comMr = Integer::compare;
/*---------------------栗2----------------------*/
// Math:Long round(Double d)
// Lambda
Function<Double, Long> funcLam = d -> Math.round(d);
// MR
Function<Double, Long> funcMr = Math::round;
类::实例方法名
/*---------------------栗1----------------------*/
// Comparator: int compare(T t1, T t2)
// String: int t1.compareTo(t2)
// Lambda
Comparator<String> comLam = (t1, t2) -> t1.compareTo(t2);
// MR
Comparator<String> comMr = String::compareTo;
/*---------------------栗2----------------------*/
// BiPredicate: boolean test(T t, U u)
// String: boolean t1.equals(t2)
// Lambda
BiPredicate<String, String> comLam = (t1, t2) -> t1.equals(t2);
// MR
BiPredicate<String, String> comMr = String::equals;
/*---------------------栗3----------------------*/
// Function: R apply(T t)
// 假设Dog类有个属性是name(包含setter/getter):String getName()
// Lambda
Function<Dog, String> funcLam = d -> d.getName();
// MR
Function<Dog, String> funcMr = Dog::getName;
和方法引用类似:参数列表相同,返回值就是构造器所在类的类型。
// Lambda
Supplier<Dog> suppLam = ()->new Dog();
// CR
Supplier<Dog> suppCr = Dog::new;
和构造器引用一致,把数组看成一个特殊的类即可。
// Lambda
Function<Integer, String[]> funcLam = length->new String[length];
// AR
Function<Integer, String[]> funcAr = String[]::new;
Stream API (java.util.stream)把真正的函数式编程风格引入到 Java 中。这是目前为止对 Java 类库最好的补充!
Stream API 是对集合数据进行操作,类似于SQL的查询。
Collection 和 Stream 区别:Collection是数据的容器,Stream提供数据的计算。
注意!!!
Stream 操作的三步骤:
Java 8 中 Collection 接口被扩展,提供两个获取 Stream 的方法:
// 返回一个顺序流(按顺序把数据取出来)
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
// 返回一个并行流(并行取数据,取出来的数据和源数据不能保证顺序一致。。。)
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
CODE:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dog {
private String name;
private int age;
}
List<Dog> dogs = new ArrayList<>();
// 返回一个顺序流
Stream<Dog> stream = dogs.stream();
// 返回一个并行流
Stream<Dog> dogStream = dogs.parallelStream();
Java 8 中 Arrays 的静态方法 stream(),可以获取 数据流:
public static <T> Stream<T> stream(T[] var0) {
return stream((Object[])var0, 0, var0.length);
}
public static <T> Stream<T> stream(T[] var0, int var1, int var2) {
return StreamSupport.stream(spliterator(var0, var1, var2), false);
}
CODE:
int[] arr = {};
IntStream stream = Arrays.stream(arr);
可以调用 Stream 类静态方法 of() 通过显示值创建一个流,它可以接收任意数量的参数:
// 返回包含单个元素的序列{@code Stream}
public static<T> Stream<T> of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
// 返回元素为指定值的顺序有序流
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
CODE:
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
可以使用静态方法 Stream.iterate() 和 Stream.generate() 创建无限流:
// 返回由函数 f 迭代源数据 seed 生成无限顺序有序流
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
Objects.requireNonNull(f);
final Iterator<T> iterator = new Iterator<T>() {
@SuppressWarnings("unchecked")
T t = (T) Streams.NONE;
@Override
public boolean hasNext() {
return true;
}
@Override
public T next() {
return t = (t == Streams.NONE) ? seed : f.apply(t);
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
// 返回无限顺序无序流,其中每个元素由提供的 s 生成。这适用于生成恒定流、随机元素流等。
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
CODE:
// 遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成10个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
| 方法 | 描述 |
|---|---|
filter(Predicate p) | 接收Lambda,从流中排除元素 |
distinct() | 去重。老二式:通过hashCode()和equals() |
limit(long maxSize) | 截断(分页)。截取前maxSize个 |
skip(long n) | 跳过前n个元素的流。长度不足n返回空流,与limit(n)互补 |
CODE:
Stream.iterate(0, t -> t + 2)
.limit(10).
.filter(t->t<10)
.distinct()
.skip(2)
.forEach(System.out::println);
| 方法 | 描述 |
|---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新元素 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream |
flatMap(Function f) | 接收一个函数作为参数,将流中每个元素都换成 f 返回的流,然后把所有流连接成一个流 |
CODE:
Arrays.asList("aa", "bb", "cc", "dd").stream()
.map(String::toUpperCase)
.flatMap(t->{
List<Character> list = new ArrayList<>();
for (char c: t.toCharArray()) {
list.add(c);
}
return list.stream();
})
.forEach(System.out::println);
| 方法 | 描述 |
|---|---|
sorted() | 按自然顺序排序产生一个新流 |
sorted(Comparator c) | 按比较器顺序排序产生一个新流 |
CODE:
// sorted()
Arrays.asList(22,13,9,0,13,-4,226,37).stream()
.sorted()
.forEach(System.out::println);
List<Dog> dogs = new ArrayList<>();
for (int i = 0; i < 10; i++) {
dogs.add(new Dog("dog-" + i, (int)(Math.random() * 10)));
}
// sorted(Comparator c)
dogs.stream()
.sorted(Comparator.comparing(Dog::getAge).reversed()) // Comparator.reversed()倒序
.forEach(System.out::println);
| 方法 | 描述 |
|---|---|
boolean anyMatch(Predicate p) | 检查是否至少匹配一个元素,返回布尔值 |
boolean allMatch(Predicate p) | 检查是否匹配所有元素,返回布尔值 |
boolean noneMatch(Predicate p) | 检查是否没有匹配所有元素,返回布尔值 |
Optional | 返回第一个元素 |
Optional | 返回当前流中任意元素 |
long count() | 返回流中元素个数 |
Optional | 返回最小元素 |
Optional | 返回最大元素 |
void forEach(Consumer c) | 内部迭代(使用Collection接口需要用户自己迭代, 称为外部迭代。Stream API 使用内部迭代——它帮 你把活干了) |
CODE:
/* 本栗沿用上面的dogs */
// allMatch:狗年龄都大于2吗
System.out.println("狗年龄都大于2吗?__" +
dogs.stream().allMatch(d->d.getAge() > 2));
// anyMatch:狗年龄有大于2的吗
System.out.println("狗年龄有大于2的吗?__" +
dogs.stream().anyMatch(d->d.getAge() > 2));
// noneMatch:狗年龄是否都不大于2
System.out.println("狗年龄是否都不大于2?__" +
dogs.stream().noneMatch(d->d.getAge() > 2));
// findFirst:年龄逆序返回第一条狗
System.out.println("年龄逆序返回第一条狗__" + dogs.stream()
.sorted(Comparator.comparing(Dog::getAge).reversed())
.findFirst());
// findAny:随机返回一条狗
for (int i = 0; i < 10; i++) {
// 这里总会返回第一个元素,就很离谱。。。
System.out.println(i + "__随机返回一条狗__" +
dogs.stream().findAny());
}
// count:返回年龄大于2的狗的条数
System.out.println("龄大于2的狗的条数__" +
dogs.stream().filter(d->d.getAge() > 2).count());
// max:返回这群狗中的最大年龄
System.out.println("这群狗中的最大年龄__" +
dogs.stream().map(d->d.getAge()).max(Integer::compareTo));
// min:返回年龄最小的狗
System.out.println("年龄最大的狗__" +
dogs.stream().min(Comparator.comparingInt(Dog::getAge)));
// forEach:这群狗年龄降序输出(此forEach是Stream内部迭代,非集合的forEach)
dogs.stream()
.sorted(Comparator.comparing(Dog::getAge).reversed())
.forEach(System.out::println);
| 方法 | 描述 |
|---|---|
T reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来得到一个值,返回T |
Optional | 可以将流中元素反复结合起来得到一个值,返回Optional |
CODE:
// reduce:1-10的和
System.out.println("1-10的和__" +
Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream().reduce(0, Integer::sum));
// reduce:狗窝狗年龄总和(继续沿用上面的dogs;另:用map的话更便捷可以省略new)
System.out.println("狗窝狗年龄总和__" +
dogs.stream()
.reduce((d1, d2)-> new Dog("", d1.getAge() + d2.getAge()))
.get()
.getAge());
| 方法 | 描述 |
|---|---|
| 将流转换为其他形式。接收一个Collector接口实现, 用于给Stream中元素做汇总 |
Collector接口中方法的实现决定了如何对流执行汇总操作(如何收集到List、Set、Map)另外,
Collectors实用类提供了很多静态方法,可以方便的创建常见收集器实例,如下表所示
CODE:
// 继续沿用上面的dogs,返回年龄>3的狗列表
dogs.stream()
.filter(d -> d.getAge() > 3)
.collect(Collectors.toList()) // 这里得到的就是List
.forEach(System.out::println);
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型 T 的值,代表这个值存在。或者仅仅保存 null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。起源:
Google公司著名的 Guava项目引入了Optional类,Guava通过使用检査空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava的启发,Optional类已经成为 Java 8 类库的一部分。
Optional 类提供了很多方法,可以不用再现实的进行空值检验。
Optional.of(T t) : 创建一个 Optional 实例,t 必须非空Optional.empty() : 创建一个空的 Optional 实例Optional.ofNullable(T t):t 可以为 nullboolean isPresent():判断是否包含对象void ifPresent(Consumer super T> consumer):如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它T get():如果调用对象包含值,返回该值,否则抛异常T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象T orElseGet(Supplier extends t> other):如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象T orElseThrow(Supplier extends X> exceptionSupplier):如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常CODE:
// 首先沿用上面的Dog
// Person类包含Dog
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
private Dog dog;
}
// 封装一个通过Person获取其狗name的方法
public String getPDName(Person p){
return p.getDog().getName(); // 问题大大滴!很容易碰到空指针异常
}
// 我们做一下常规优化(可能的空指针异常被避免,代码更健壮)
public String getPDNameSafely(Person p){
if(Strings.notEmpty(p.getDog()) && Strings.notEmpty(p.getDog()){
return p.getDog().getName();
}
return null;
}
// 使用 Optional...这特么何其麻烦啊...
public String getPDNameSafelyPlus(Person p){
Optional<Person> pOptional = Optional.ofNullable(p);
Person pCp = pOptional.orElse(new Person(new Dog()));
Dog dog = pCp.getDog();
Optional<Dog> dogOptional = Optional.ofNullable(dog);
Dog dog = dogOptional.orElse(new Dog());
return dog.getName();
}