Java8的新特性
函数式接口
接口里面只有一个抽象方法这样的接口就是函数式接口
以Runnable接口为例,进入接口不难发现,是函数式接口的意思
加这个注解更多的是为了验证以及标记这是一个函数式的接口,也是为了检验一下,类似子类重写父类时有@Override注解标记是一个道理
加入我自己的接口加入了函数式接口的注解,但是我出现了两个抽象方法,那就肯定不行的。
Lambd表达式使用的前提条件就是当前接口是函数式接口
Lambda表达式
是一个匿名函数,为什么会出现,就是别的语言有这个了,挺好用的,Java8把这个借鉴过来了,仅此而已。
不想说简介一大堆了,直接上例子吧。
对比
传统的线程实现方式
//传统线程实现
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Lambda测试1");
}
};
runnable1.run();
Lambda表达式实现多线程
->
的意思是操作符
//Lambda表达式实现多线程
Runnable runnable2 = () -> System.out.println("Lambda测试2");
runnable2.run();
结果都可以输出
基础格式
(o1,o2) -> {方法体}
(左边)->{右边}
-> : Lambda的操作符或箭头操作符
左边 : Lambda的形式参数列表(本质上就是接口中抽象方法的形参列表)
右边 :Lambda方法体 (本质上就是重写抽象方法的方法体)
Lambda 表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object 类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:
Lambda的本质:作为接口的实例
无参无返回值
Runnable runnable2 = () -> System.out.println("Lambda测试2");
一个参数无返回值
一个参数数据类型可以省略
因为是类型推断,什么是类型推断
比如:定义集合的时候,最开始的时候定义泛型,末尾不定义泛型,自动推断出后面的泛型值,这里就是类型推断
ArrayList<String> arraylist=new ArrayList<>();
还比如说int [] array={1,2,3,4,5};
不需要new数组对象也可以完成,这种的也是类型推断
参数小括号省略
Lambda若只有一个参数的时候,小括号也是可以省略的
多个参数且需要有返回值
单条语句省略括号
当Lambda体只有一条语句的时候,return与大括号若有,都可以省略
总结
->左边
:Lambda形参列表的参数类型可以忽略(类型推断):如果Lambda形参列表只有一个参数,其一对( )也可以省略
->右边
:Lambda体应该使用一段大括号,但是特殊情况可以变化,如果Lambda体内只有一条语句(包括只有一条return),那么可以省略这一对{ }和return关键字 (注意,如果省略return,那么{ }也要一起省略掉)
何时使用Lambda表达式?
当需要对一个函数式接口实例化的时候,可以使用Lambda表达式。
何时使用给定的函数式接口?
如果我们开发中需要定义一个函数式接口,首先看看在已有的jdk提供的函数式接口是否提供了能够满足需求的函数式接口,如果有,就直接调用。如果没有,就需要自己去实现具体的内容了。
Java内置函数式接口
这里仅列举主要的几个接口
以上接口当实例化的时候,就可以用Lambda表达式来做接口的实例化,不再需要去自己再实现接口重写方法的流程,比较麻烦。
消费者Conusmer接口
只有一个accept(T t)方法,接收任意的类型参数,消费嘛
供给接口Supplier
里面有一个T get();方法
函数型接口Function<T, R>
里面有一个R apply(T t);接口
断定型接口Predicate
传入参数,返回布尔类型
主要是一个判断方法 根据给定的规则,来对具体是否符合条件做一个判断
方法引用与构造器引用
方法引用
方法引用的使用要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同
使用格式:类(或者对象):: 方法名
三种情况: 对象::非静态方法
类::静态方法
类::非静态方法(lambda里面是可以这么用的)
使用情景:当传递给Lambda体的操作,已经有实现方法了,可以使用方法引用。
方法引用实际上就是Lambda表达式,而Lambda表达式是作为函数式接口的实例,所以方法引用也是函数式接口的实例
使用建议:如果给函数式接口提供实例,恰好满足方法引用的使用情景,大家就可以考虑使用方法引用给函数式接口提供实例。如果大家不熟悉方法引用,那么还可以使用Lambda表达式。
情景一:对象::实例方法
传统的Consumer方法进行实例化
用方法引用的方式进行调用
再比如我们熟悉的MP里面的调用,都是这个
情景二:类::静态方法
Lambda方法下比较两个数的大小
改造用方法引用,因为Integer的方法体和本身抽象方法的形参是一样的,所以连参数列表都省了,可以用方法引用
先用方法引用对函数式接口的抽象方法进行重写,再后续用其他方法进行调用
情景三 :类::实例方法
这个有难度,不是很好理解
再或者
构造器引用
Lambda的方法进行创建
举例:我希望获得一个Employee对象,通过供给型函数Supplier来获取
通过构造器引用进行简写
举例:我希望获得一个Employee对象,通过函数接口Function来获取
调用构造器引用创建
后面类似bifunction的函数都是类似的效果
数组引用
实际上和上面的构造器引用差不多,只不过是把数组看成类
语法:数组类型[] :: new
用Function函数以Lambda表达式的创建方式来创建数组
改进一下用数组引用,就是把类换成数组了,都差不多
Stream API
集合讲的是数据,而Stream讲的是计算
基础
说白了,Stream就是操作集合的工具
Stream关注的是数据的运算,是与CPU打交道。
而集合是关注数据的存储,是与内存打交道的。
注意:
- Steam自己不会去存储元素
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
- Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
Stream执行流程:
- Stream实例化
- 一系列中间操作(过滤、映射),中间操作是一个链式的操作
- 终止操作(一旦终止产生结果后就不会再使用了,就像把IO流关闭了一样)
创建Stream方式
通过集合创建(有现成的集合用这个)
通过数组创建(有现成的数组用这个)
通过Stream的of()实现(啥也没有现造一个0)
把多个内容放在of里,相当于把Stream当容器
而这里的数字都是看作为包装类的(因为操作的是集合),并不是基础数据类型
创建无限流(用的比较少)
上面的创建元素个数都是有限的,这种的创建个数是无限个创建,不停生成不停创建
迭代:Stream.iterate(种子,函数式接口),类似一个apply方法,放入的是t,返回的是t+2,这样循环往复不停迭代,怎么停下来呢?停不下来,这是无限流,只能借用limit终止操作来限制条数。
生成:Stream.generate(Lambda表达式),必须传Lambda表达式。
Stream中间操作
筛选与切片
filter过滤元素
//过滤 filter(Predicate p)过滤,接收Lambda,从流中过滤元素
List employeeList = EmployeeData.getEmployee();
//注意,这里一定要用泛型约束,要不然后续的方法调用调不出来
Stream<Employee> stream=employeeList.stream();
//遍历元素,把工资大于6000的返回
stream.filter(e -> e.getSalary() > 6000).forEach(System.out::println);
最后输出的是所有工资大于6k的对象
注意:如果执行了终止操作foreach或者其他的终止操作,那么后续也就无法再对这个流进行任何其他操作了
limit截断流限制元素个数
skip跳过几个数据
skip(3)跳过前三个数据
distinct 去重
看名字就知道是去重的
映射
map
map(Function f)接收一个函数作为参数,将元素转换为其他形式或者提取信息,该函数会被应用到每个元素上,并且将其映射成一个新的元素,但是这种映射是源值与处理之后结果的映射
练习一下:获取名字长度大于2的员工的姓名
//获取员工集合
List<Employee> employeeList = EmployeeData.getEmployee();
//员工集合转换为Stream流
Stream<String> employeeStream = employeeList.stream().map(Employee::getName);
//把流中名字长度大于2的都过滤出来
employeeStream.filter(e -> e.length() > 2).forEach(System.out::println);
这里的映射关系
flatmap
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);
list1.add(list2);
//此时,list1中元素的个数是4个[1, 2, 3, [4, 5, 6]]
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);
list1.addAll(list2);
//此时,list1中元素的个数是4个[1, 2, 3, 4, 5, 6]
不难发现,无非是把添加的元素根据API的区别,看作一个完整的集合或者一个一个分散的元素
上面的map就类似于add方法,把map流的内容看作一个完整的对象进行添加到流中。
flatmap就类似于addAll方法,把map流的内容拆开之后挨个添加到流中。
演示一下,流里面套一个流,对内部的流对象遍历
//映射之flatmap
@Test
public void test7(){
List<String> strList = Arrays.asList("aa", "bb", "cc");
//不难看出,这行代码的意思是把strList转成Stream之后,调用方法把每个字符串转为Stream再放进这个Stream中
Stream<Stream<Character>> streamStream = strList.stream().map(StreamApiTest1::fromStringToStream);
//把流中的流遍历出来
streamStream.forEach(s->{
s.forEach(
System.out::println
);
});
}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str) {
ArrayList<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
输出,每个字符都单独输出出来。
排序
sorted产生一个新流,其中按自然顺序排序
List<Integer> list = Arrays.asList(20, 6, 7, 32, 11, 30, 54);
//将List转Stream流,排序,输出
list.stream().sorted().forEach(System.out::println);
输出的结果是按照升序的顺序排列的
当然,对象的集合在这里是无法排序的,因为需要Comparator进行排序,Stream的sorted操作仅仅适用于普通的数据类型。
假如我硬排一下,就会报错。
原因是Employee没有实现Comparator接口
sorted(Comparator c)定制排序
适用于上面这种,对象没有实现Comparator接口还要进行排序的情况。
注意,传入参数的时候要用Lambda表达式传入参数,同时Comparator接口中,无方法体的方法仅此一个,所以只能用这个,是两个参数
按照年龄进行排序
List<Employee> employeeList = EmployeeData.getEmployee();
//排序,传入比较的参数,并且实现比较的逻辑
employeeList.stream().sorted((e1,e2)->{
return Integer.compare(e1.getAge(),e2.getAge());
}).forEach(System.out::println);
Stream终止操作
- 非短路操作: 指必须处理所有元素才能得到最终结果;
- 短路操作: 指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
匹配与查找
匹配
- allMatch(Predicate p) 检查是否匹配所有元素
List<Employee> employeeList = EmployeeData.getEmployee();
//判断这个流内部所有的age元素是否都大于21
boolean result=employeeList.stream().allMatch(e -> e.getAge() > 21);
System.out.println(result);
//结果为false,证明这个流中并不是所有的元素都大于21
- anyMatch(Predicate p) 检查是否至少匹配一个元素
List<Employee> employeeList = EmployeeData.getEmployee();
//判断这个流内部所有的age元素是否有21的
boolean result=employeeList.stream().anyMatch(e -> e.getAge() == 21);
System.out.println(result);
//结果为true,证明有年龄为21的
- noneMatch(Predicate p) 检查是否没有匹配的元素
List<Employee> employeeList = EmployeeData.getEmployee();
//判断这个流内部所有的age元素是否没有21的
boolean result=employeeList.stream().noneMatch(e -> e.getAge() == 21);
System.out.println(result);
//输出为false,也就是代表匹配到了
查找
- findFirst() 返回第一个元素
List<Employee> employeeList = EmployeeData.getEmployee();
//用Optional接收返回的第一个对象,相当于一个容器
Optional<Employee> employee=employeeList.stream().findFirst();
- findAny() 返回当前流中的任意元素
List<Employee> employeeList = EmployeeData.getEmployee();
//用Optional接收返回的第一个对象,相当于一个容器
Optional<Employee> employee=employeeList.stream().findAny();
//随机返回一个对象
统计
- count() 返回流中元素总数
List<Employee> employeeList = EmployeeData.getEmployee();
//这里用long接收是因为e.getSalary()的类型是Long
long num=employeeList.stream().filter(e -> e.getSalary() > 7000).count();
System.out.println(num);
//输出:符合薪资大于7k的一共3个
- max(Comparator c) 返回流中最大值
List<Employee> employeeList = EmployeeData.getEmployee();
//这里只能用Optional接收参数,根据年龄获取最大值
Optional employee=employeeList.stream().max((e1,e2)->{
return Integer.compare(e1.getAge() , e2.getAge());
});
System.out.println(employee);
//输出年龄最大的员工
- min(Comparator c) 返回流中最小值
List<Employee> employeeList = EmployeeData.getEmployee();
//这里只能用Optional接收参数,根据id获取最小值
Optional employee=employeeList.stream().min((e1,e2)->{
return Integer.compare(e1.getId() , e2.getId());
});
System.out.println(employee);
//输出id最小的员工
- foreach(Consumer c) 内部迭代
就是常见的遍历操作
//这是Stream遍历
EmployeeData.getEmployee().stream().forEach(System.out::println);
//这是对象foreach遍历,遍历的主体不一样
EmployeeData.getEmployee().forEach(System.out::println);
归约
规约 reduce,相当于大数据map-reduce模型中的reduce,将之前map之后各个元素结果结合到一起,求和之类的操作,归约成一个
- reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 T。iden为初始元素,b为二元运算,结合各个元素,由于有初始元素iden,所以即使流为空,也可以返回iden
List<Integer> intList=Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
//初始值0,相当于把集合全部加和再加个初始值,自动累加,归约成为一个,相当于0+0+1+2+3+.....+9
Integer sum = intList.stream().reduce(0, Integer::sum);
System.out.println(sum);
- reduce(BinaryOperator b) 可以将流中元素反复结合起来, 得到一个值。返回 Optional ,这个是没有初值的版本,直接累加,结果是45
List<Integer> intList=Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional sum = intList.stream().reduce(Integer::sum);
System.out.println(sum);
收集
收集,多个数据被容器承装
- collect(Collector c)将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
List<Employee> employeeList = EmployeeData.getEmployee();
//收集工资大于6k的,并且接收
List resultList = employeeList.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
System.out.println(resultList);
- Collectors实用类提供了很多静态方法, 可以方便地创建常见Collector接口的收集器实例
这个里面可以有很多可选项
- toList(),将流收集到List,返回类型为List
- toSet(),把流中元素收集到Set,返回类型为Set
- toCollection(Supplier sup),把流中元素收集到创建的集合,可用于任何其他的集合类型
Optional类
在开发的时候,经常会遇到空指针异常的问题,为避免该问题,java8引入了Optional 类,提前发现数据是否为空,从而根据业务需求决定是否为其提前创建对象。
概念
Optional 类(java.util.Optional) 是一个容器类, 代表一个值存在或不存在,
原来用 null 表示一个值不存在, 现在 Optional 可以更好的表达这个概念。 并且可以避免空指针异常。
相信很多人会好奇Optional 是怎么避免空指针异常的吧,这在“举例”中会详细分析,大体解决思路就是在空指针异常前,在获取对象的时候就会判断是否为空,没有创建默认选项的话,会提前报错,从而在空指针发生前提示,方便定位空指针发生的位置,从而让开发者不必再debug去找问题。
常用方法:
- Optional.of(T t) : 创建一个 Optional 实例
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
- isPresent() : 判断是否包含值
- orElse(T t) : 如果调用对象包含值, 返回该值, 否则返回t
- orElseGet(Supplier s) :如果调用对象包含值, 返回该值, 否则返回 s 获取- 的值
- map(Function f): 如果有值对其处理, 并返回处理后的Optional, 否则返回- Optional.empty()
- flatMap(Function mapper):与 map 类似, 要求返回值必须是Optional
创建一个Book类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private String name;
private int page;
private Type type;
}
获取时异常
由于在获取Book对象是,是null的,所以直接报错,方便开发者定位到空值的位置
Optional<Book> empty = Optional.empty();
empty.get();
java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at com.atguigu.spring5.test.TestOptional.test1(TestOptional.java:11)
一般使用默认值填充
Optional<Book> empty = Optional.empty();
Book book = empty.orElseGet(() -> new Book("java",100,Type.STUDY));
System.out.println(book);
//输出Book{name='java', page=100, type=STUDY}
三种创建方式
//生成一个空empty实例
Optional<Book> option1 = Optional.empty();
//用book对象创建Optional,但是of方法不能为空
Optional<Book> option2 = Optional.of(new Book());
//创建一个可以为null的Optional
Optional<Object> option3 = Optional.ofNullable(null);
Optional<Book> book = Optional.ofNullable(new Book());
map方法
Optional<Book> option1 = Optional.empty();
Optional<Book> option2 = Optional.of(new Book("java",100,Type.STUDY));
Optional<String> s1 = option1.map(Book::getName);
Optional<String> s2 = option2.map(Book::getName);
System.out.println(s1);
System.out.println(s2.get());
Optional.empty
java
复习动态代理
代理模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理
举例:
实现Runnable接口的方法创建多线程。
Class MyThread implements Runnable{} //相当于被代理类
Class Thread implements Runnable{} //相当于代理类
main(){
MyThread t=new MyThread();//实例化被代理类
Thread thread=new Thread(t);//实例化代理类并且传入被代理对象
thread.start();//启动线程,调用线程的run()
}
静态代理的缺点:比较固定,不利于扩展。每一个代理类只能为一个接口服务,会在程序中产生过多的代理。
动态代理(反射应用)
为了解决静态代理的缺点而产生的动态代理。
程序运行时根据需要动态生成代理类对象。
主要解决了两个问题
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。(通过Proxy.newProxyInstance()实现)
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。(通过InvocationHandler接口的实现类及其方法invoke())