目录大纲
- lambda 表达式
- 函数式接口
- 常用的函数式接口
- 方法引用
- 构造器引用
- 数组引用
- 集合流
- 创建流 集合 、数组 、of、无限流
- 中间操作 filter、map
- 终止操作 foreach collect
- Optional
一、JDK8 新特性概述
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以 来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
二、Lambda 表达式
2.1 为什么要求Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
2.2 举例
我们看如下代码就可用lamdba表达式进行简化
2.3 Lambda 表达式概述
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:左侧:指定了 Lambda 表达式需要的参数列表 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
2.4 Lambda表达式具体语法格式
lamdba表达式只能针对函数式接口进行操作,进行简化。只包含一个抽象方法的接口,称为函数式接口
类型推断
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
2.5具体案例
public class LamdbaDemo1 {
@Test
public void test1() {
//1.语法格式一 函数式接口的抽象方法没有参数列表没有返回值
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("正常写法");
}
};
runnable.run();
System.out.println("------------------");
//2.lambda表达式写法
Runnable runnable2 = () -> {System.out.println("lambda表达式写法");};
runnable2.run();
}
@Test
public void test2() {
//1.语法格式二 函数式接口的抽象方法 有一个参数,但没有返回值
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
consumer.accept("我爱你中国");
System.out.println("------------");
//2.lamdba表达式写法
Consumer<String> consumer2 = (String t) ->{System.out.println(t);};
consumer2.accept("我爱你中国");
}
@Test
public void test3() {
//1.语法格式三 函数式接口的抽象方法 有一个参数,但没有返回值
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
consumer.accept("我爱你中国");
System.out.println("------------");
//2.类型推断 不用指定String类型
Consumer<String> consumer2 = (t) ->{System.out.println(t);};
consumer2.accept("我爱你中国");
}
@Test
public void test4() {
//1.语法格式四 函数式接口的抽象方法 有一个参数,但没有返回值
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
consumer.accept("我爱你中国");
System.out.println("------------");
//2.在三的基础上省略小()
Consumer<String> consumer2 = t ->{System.out.println(t);};
consumer2.accept("我爱你中国");
}
@Test
public void test5() {
//1.语法格式5 函数式接口的抽象方法 有多个参数列表多条执行语句并有返回值
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(1, 2));
System.out.println("----------");
//2.lambda简写
Comparator<Integer> comparator2 = (o1,o2)->{
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(comparator2.compare(1, 2));
}
@Test
public void test6() {
//1.语法格式6:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(1, 2));
System.out.println("------------");
Comparator<Integer> comparator2 = (o1,o2)->o1.compareTo(o2);
System.out.println(comparator2.compare(1, 2));
}
}
三、函数式接口(Functional)
3.1 什么是函数式接口
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该接口的对象。
- 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
- 在java.util.function包下定义了Java 8 的丰富的函数式接口
例如:Comparator就是一个函数式接口
当然我们也可以自定义函数式接口,如下所示:
3.2 常见内置的函数式接口
四、方法引用
4.1 方法引用概述
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
- 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
- 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
- 如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
4.2方法引用举例
4.2.1 对象::实例方法名(常用)
当函数式接口的抽象方法的参数列表以及返回值和实现方法的调用者一致时,可以采用对象::实例方法名
进行 方法引用
@Test//1.情况1:对象实例方法
public void test1() {
//1.函数式接口正常创建匿名对象
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
//2.lambda表达式的方式创建匿名对象
Consumer<String> consumer2 = (t) -> System.out.println(t);
//3.方法引用的方式
/**
* 可以这么调用的原因
* 1.接口中的抽象方法accept的参数列表和println()方法参数列表相同返回值也相同
*
*/
Consumer<String> consumer3 = System.out::println;
}
@Test//1.情况1:对象实例方法
public void test2() {
User user = new User(1, "小黑", "123456");
//1.正常表示
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return user.getName();
}
};
//2.lambda表达式
Supplier<String> supplier2 = ()->user.getName();
/**
* user.getName() 方法返回值String和抽象方法get() 返回值一样,参数列表一样
*/
//3.方法的引用方式
Supplier<String> supplier3 = user::getName;
}
4.2.2 类::静态方法名
@Test//情况2 类名::静态方法名
public void test3() {
//1.正常表示
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
//2.lambda表示方式
Comparator<Integer> comparator2 = (o1,o2)-> Integer.compare(o1, o2);
//3.静态方法引用表示
Comparator<Integer> comparator3 = Integer :: compare;
System.out.println(comparator3.compare(3, 4));
}
@Test//情况2 类名::静态方法名
public void test4() {
//1.正常写法
Function<Double, Long> function = new Function<Double, Long>() {
@Override
public Long apply(Double t) {
return Math.round(t);
}
};
//2.lambda表达式写法
Function<Double, Long> function2 = (t)->Math.round(t);
//3.方法引用表示
/**
* 可以这么表示的原因:Math.round() 和appli() 方法参数列表相同,返回值相同
*/
Function<Double, Long> function3 = Math::round;
}
4.2.3 类::实例方法名
@Test//情况3 类名::实例方法
public void test5() {
//1.正常写法
Comparator<String> comm1 = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
//2.lambda表达式写法
Comparator<String> comm2 = (o1,o2)->o1.compareTo(o2);
//3.方法引用表示
Comparator<String> comm3 = String::compareTo;
System.out.println(comm3.compare("a", "b"));
}
@Test//情况3 类名::实例方法
public void test6() {
//1.正常写法
BiPredicate<String,String> biPredicate = new BiPredicate<String, String>(){
@Override
public boolean test(String t, String u) {
return t.equals(u);
}
};
//2.lambda表达式写法
BiPredicate<String, String> biPredicate2 = (str1,str2)->str1.equals(str2);
//3.方法引用
BiPredicate<String, String> biPredicate3 = String::equals;
System.out.println(biPredicate3.test("abc", "abc"));
}
五、构造器引用
格式: **ClassName::new **
与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象 方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
案例
@Test
public void test1() {
//1.正常写法
Supplier<User> supplier = new Supplier<User>() {
@Override
public User get() {
return new User();
}
};
//2.lambda写法
Supplier<User> supplier2 = ()->new User();
//3.构造方法引用
Supplier<User> supplier3 = User::new;
}
六、数组引用
格式: type[] :: new
案例
@Test
public void test2() {
//1.正常写法
Function<Integer, User[]> function = new Function<Integer, User[]>() {
@Override
public User[] apply(Integer t) {
return new User[t];
}
};
//2.lambda表达式写法
Function<Integer, User[]> function2 = (length)->new User[length];
//3.数组引用
Function<Integer, User[]> function3 = User[]::new;
}
七、强大的StreamAPI
7.1 StreamAPI 说明
- Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
- Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
7.2 为什么要使用StreamAPI
- 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Redis等,而这些NoSQL的数据就需要Java层面去处理。
- Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中, 后者主要是面向 CPU,通过 CPU 实现计算。
7.3 什么是Stream
Stream到底是什么呢
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算!”
注意
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
7.4 Stream 操作的三个步骤
- 创建 Stream :一个数据源(如:集合、数组),获取一个流
- 中间操作 :一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作) :一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
7.5 创建Stream的实例
7.5.1 通过集合方法创建Stream
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
- default Stream stream() : 返回一个顺序流
- default Stream parallelStream() : 返回一个并行流
@Test
public void test1() {
Collection c1 = new ArrayList();
//1.返回一个顺序流
Stream stream = c1.stream();
//2.返回一个并行流
Stream parallelStream = c1.parallelStream();
}
7.5.2 通过数组工具类方法创建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)
@Test//2.通过数组工具类创建stream流
public void tes2() {
//1.创建数组
int[] arr = {1,2,3};
long[] arr2 = {1L,2L,3L};
double[] arr3 = {1.1,2.2,3.3};
//2.通过数组工具类创建stream流
IntStream stream = Arrays.stream(arr);
LongStream stream2 = Arrays.stream(arr2);
DoubleStream stream3 = Arrays.stream(arr3);
User[] users = {new User(1, "瑶", 8000.2),new User(2, "明世隐", 9000.2)};
Stream<User> stream4 = Arrays.stream(users);
}
7.5.3 Stream.of() 方法创建Stream
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
- public static Stream of(T… values) : 返回一个流
@Test
public void test3() {
Stream<Integer> stream = Stream.of(1,2,3);
}
7.5.4 创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
- 迭代
- public static Stream iterate(final T seed, final UnaryOperator f)
- 生成
- public static Stream generate(Supplier s)
@Test//1.创建无限流
public void test4() {
//1.创建无线流 初始值是0 然后流中的数据加1.
Stream<Integer> stream = Stream.iterate(0, t->t+1);
stream.limit(10).forEach(System.out::println);
//2.生成
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
7.6 Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
7.6.1 筛选与切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回空 |
@Test//1.帅选与切片
public void test1() {
//1.接收 Lambda , 从流中排除某些元素
List<User> data = UserList.getData();
//1.判断工资大于9000的用户
data.stream().filter(user->user.getMoney()>9000).forEach(System.out::println);
System.out.println("***************************");
//2.截取前三条数据
data.stream().limit(3).forEach(System.out::println);
//3.跳过前三条记录
data.stream().skip(3).forEach(System.out::println);
//4.去重复数据 // 要重写hashCode 和equals方法
data.stream().distinct().forEach(System.out::println);
}
7.6.2 映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元 ` 素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
@Test//2.映射
public void test2() {
//1.映射 将小写字母映射成大小字母
List<String> list = Arrays.asList("aa","bb","cc","dd");
list.stream().map(t->t.toUpperCase()).forEach(System.out::println);
list.stream().map(String::toUpperCase).forEach(System.out::println);
//2.映射用户姓名等于1的姓名
List<User> data = UserList.getData();
Stream<User> filter = data.stream().filter(user->user.getName().length()==1);
filter.map(User::getName).forEach(System.out::println);
System.out.println("-----------");
//3.flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list2 = Arrays.asList("aaa","bbb","ccc","ddd");
list2.stream().flatMap(str->StreamDemo1.getStream(str)).forEach(System.out::println);
}
public static Stream<Character> getStream(String str) {
ArrayList<Character> list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
7.6.3 排序
方法名 | 描述 |
---|---|
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
@Test//2.排序
public void test3() {
//1.自然排序
List<Integer> list = Arrays.asList(1,2,3,9,-1,0);
list.stream().sorted().forEach(System.out::println);
//2.定制排序
List<User> listUser = UserList.getData();
listUser.stream().sorted((u1,u2)->{
return Double.compare(u1.getMoney(), u2.getMoney());
}).forEach(System.out::println);
}
7.7 Stream的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
**注意:**流进行了终止操作后,不能再次使用
7.7.1 匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
@Test
public void test1() {
//1.查看所有有用户的钱都大于5000
List<User> list = UserList.getData();
boolean tag = list.stream().allMatch(u->u.getMoney()>5000);
System.out.println(tag);
//2.至少一个一个用户的前大于9000的
System.out.println(list.stream().anyMatch(u->u.getMoney()>9000));
//3.查看是否没有叫蔡文姬的用户
System.out.println(list.stream().noneMatch(u->u.getName().equals("蔡文姬")));
//4.返回第一个元素
Optional<User> findFirst = list.stream().findFirst();
System.out.println(findFirst.get());
}
@Test
public void test2() {
List<User> list = UserList.getData();
//1.返回流中总个数
long count = list.stream().count();
System.out.println(count);
//2.返回流中最大值
List<Double> list2 = Arrays.asList(12.2,3.3,99.9);
Optional<Double> max = list2.stream().max((o1,o2)->Double.compare(o1, o2));
System.out.println(max.get());
//3.内部迭代
list2.stream().forEach(System.out::println);
//4.外部集合直接迭代
list2.forEach(System.out::println);
}
7.7.2规约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional |
@Test//规约
public void test3() {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//1.计算元素的累加和初始值是0
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
Optional<Integer> reduce2 = list.stream().reduce(Integer::sum);
System.out.println(reduce2.get());
}
7.7.3 收集
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
@Test//收集
public void test4() {
Set<User> set = UserList.getData().stream().filter(u->u.getMoney()>9000).collect(Collectors.toSet());
set.forEach(System.out::println);
}
八、Optional类
8.1 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()方法会返回该对象。
8.2 Optional常用方法
- Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- 创建Optional类对象的方法:
- Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t):t可以为null
- 判断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接口实现提供的异常。
@Test
public void test1() {
User user = new User(1,"小黑",99.99);
user = null;
Optional<User> optional = Optional.ofNullable(user);
User u = optional.orElse(new User(0,"默认",0.0));
System.out.println(u);
}
九、总结
- lambda 表达式
- 函数式接口
- 常用的函数式接口
- 方法引用
- 构造器引用
- 数组引用
- 集合流
- 创建流 集合 、数组 、of、无限流
- 中间操作 filter、map
- 终止操作 foreach collect
- Optional
er 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接口实现提供的异常。
@Test
public void test1() {
User user = new User(1,"小黑",99.99);
user = null;
Optional<User> optional = Optional.ofNullable(user);
User u = optional.orElse(new User(0,"默认",0.0));
System.out.println(u);
}