左边:对应接口中的抽象方法的参数
右边,对应接口中的抽象方法的实现,即所需要执行的功能,也即 lambda 体
注:在 jdk1.7 及以前,在 lambda 表达式内部要调用类的成员变量的时候,这个成员变量必须用 final 显示声明,而 jdk1.8 之后,不需要显示声明,系统会默认我们调用的这个成员变量是 final 的,但是你对这个变量进行修改的时候仍然是会报错的
lambda 的语法形式:
① 无参数,无返回值:()-> 具体实现
② 有一个参数,无返回值:(x)-> 具体实现
③ 若只有一个参数,小括号可以省略不写:x -> 具体实现
④ 若有多个参数,多条语句,加个大括号就行了:(x,y)-> { 具体实现 }
⑤ 若 lambda 体重只有一条语句,return 和大括号都可以省略不写,如Comparator
。原本应该是
Comparator<Integer>com = (x,y) -> {
return Integer.compare(x,y);
}
⑥ lambda 表达式的参数列表的数据类型可以省略不写,因为 JVM 编译器通过上下文可以推断出数据类型,即“类型推断”
注:lambda 需要“函数式接口”的支持
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用 @FunctionalInterface 修饰
类型 | 语法 |
---|---|
Consumer:消费型接口 | void accept(T t) |
Supplier :供给型接口 | T get() |
Function |
R apply(T t) |
Predicate: 断言型接口 | boolean test(T t) |
1. Consumer 消费型接口:
public void happy(double m, Consumer<Double>con){
con.accept(m);
}
@Test
public void test01(){
happy(1000,(m) -> System.out.println("消费了"+m+"元"));
}
2. Supplier 供给型接口:
@Test
public void test02(){
String s=supplier(()->"生产一辆车");
System.out.println(s);
}
public String supplier(Supplier<String>sup){
return sup.get();
}
3.函数型接口
@Test
public void test03(){
System.out.println(strHandler("abcd",(s)->s.toUpperCase()));
}
public String strHandler(String s, Function<String,String>fun){
return fun.apply(s);
}
4.断言型接口
@Test
public void test04(){
Integer arr[]={1,2,3,4,5};
for (Integer a : arr) {
Pre(a,(b)->{
if(b<=3) return true;
return false;
});
}
}
public void Pre(int a,Predicate<Integer>pre){
System.out.println(a+" "+pre.test(a));
}
方法引用:若 lambda体 中的内容有方法已经实现了,我们可以使用 “方法引用” (可以理解为方法引用是 lambda 表达式的另外一种表现形式)
主要有三种语法格式:
① 对象 :: 实例方法名
② 类 :: 静态方法名
③ 类 :: 实例方法名
案例演示:
① 对象 :: 实例方法名
@Test
public void test05(){
// 正常的用 lambda 表达式表示:传入一个数并打印应该是这样写的
Consumer<String>con=(x)-> System.out.println(x);
con.accept("小猪猪1号");
/*
由于这里的 System.out.println() 函数和Consumer接口的
参数列表和返回值类型都一致,所以我们可以把上面的lambda表达式
改写成方法引用的形式,如下:
*/
Consumer<String>con1= System.out :: println;// 对象 :: 实例方法名
con1.accept("小猪猪2号");
}
注意:
① lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
② 若 lambda 参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 ClassName :: method
语法格式:ClassName :: new
案例演示:
@Test
public void test06(){
Supplier<Employee> sup=() -> new Employee();
//构造器引用
Supplier<Empployee>suo2=Employee:new;
}
语法格式:Type[] :: new
@Test
public void test06(){
Function<Integer,String[]>fun = (x) -> new String[x];
Function<Integer,String[]>fun1 = String[] :: new;
}
Stream流:和普通的流一样,就是对数据进行操作,是数据就会有数据源(集合、数组等),而流对数据的操作其实主要表现在对数据的传输
但是和普通的流不一样的是,在传输的过程中可以对数据源进行一系列流水线式的中间操作,最终产生一个新流(原来的数据源并不改变,怎么理解呢,就是相当于复制文件,原来的文件并不会改变)。
流(Stream)到底是什么? 是数据渠道,用于操作数据源(集合、数组)所生成的元素序列。“集合讲的是数据,流讲的是运算!”
注意:
① Stream 自己不会存储元素
② Stream 不会改变源对象。相反,他们会返回一个持有结果的新的 Stream
③ Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
1.Java8 中的 Collection 接口被扩展,提供了两个获取流的方法,使用这个方法,包括继承Collection的接口,如:Set,List,Map,SortedSet 等等:
default Stream stream();
返回一个顺序流default Stream parallelStream();
返回一个并行流2.Arrays.stream,我们可以通过Arrays的静态方法,传入一个泛型数组,创建一个流
3.Stream.of,我们可以通过Stream的静态方法,传入一个泛型数组,或者多个参数,创建一个流,这个静态方法,也是调用了Arrays的stream静态方法,如下
4.Stream.iterate,是Stream接口下的一个静态方法,从名字也可以看出,这个静态方法,是以迭代器的形式,创建一个数据流
5.Stream.generate,也是stream中的一个静态方法
案例演示:
@Test
public void test(){
//1.通过 Collection 系列集合提供的 stream() 或 parralleStream()
List<String> list=new ArrayList<>();
Stream<String> stream1=list.stream();
//2. 通过 Arrays 中的静态方法 stream 获得数组流
Integer []arr=new Integer[10];
Stream<Integer>stream2= Arrays.stream(arr);
//3. 通过 Stream 中的静态方法 of()
Stream<String>stream3=Stream.of("aa","bb","cc");
//4. 创建无限流
//迭代
Stream<Integer>stream4=Stream.iterate(0,(x)->x+2);
//生成
Stream.generate(()->Math.random()).limit(5);
}
案例演示:
@Test
@Test
public void test(){
Integer []arr={1,2,3,4,4,4,4,5,6,7,8,9};
// 中间操作:不会执行任何操作
Stream<Integer>stream = Arrays.stream(arr).filter((a)->{
System.out.println("Stream 的中间操作");
return a>=3;
}).skip(2).distinct().limit(1);
//终止操作:一次性执行全部内容,即“惰性求值”
stream.forEach(System.out::println);
}
注意:
filter 是一个短路运算,就比如 limit(2) ,已经取到满足条件的两条数据之后, filter 就不会再继续向下执行了
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为 “惰性求值”
stream中的flatmap是stream的一种中间操作,它和stream的map一样,是一种收集类型的stream中间操作,但是与map不同的是,它可以对stream流中单个元素再进行拆分(切片),从另一种角度上说,使用了它,就是使用了双重for循环
案例演示:
// map -- 将list里面的小写字母全部转成大写
@Test
public void test1() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
Stream<String> stream = list.stream();
stream.map((s) -> s.toUpperCase()).forEach(System.out::println);
}
//flatMap -- 将多个 List 合并成一个
@Test
public void test2(){
List<String> list1= new ArrayList(){{add("1");add("2");}};
List<String> list2= new ArrayList(){{add("3");add("4");}};
List<String> list3= new ArrayList(){{add("5");add("6");}};
List<List<String>> list = new ArrayList(){{add(list1);add(list2);add(list3);}};
System.out.println(list.stream().flatMap(List::stream).collect(Collectors.toList()));
}
sorted() – 自然排序(Comparable)
sorted(Comparable com) – 定制排序(Comparator)
@Test
public void test() {
List<Emp>list=Arrays.asList(new Emp("zs",10),new Emp("ls",15),new Emp("zs",16));
list.stream().sorted((x,y)->{
if(x.name.equals(y.name)){
return x.age.compareTo(y.age);
}
else return x.name.compareTo(y.name);
}).forEach(System.out::println);
}
Stream 的终止操作:
allMatch – 检查是否匹配所有元素
anyMatch – 检查是否至少匹配一个元素
noneMatch – 检查是否没有匹配所有元素
findFirst – 返回第一个元素
findAny – 返回当前流中的任意元素
cout – 返回流中元素的总个数
max – 返回流中最大值
min – 返回流中最小值
归约: reduce(T identity,BinaryOperator) / reduce(BinaryOperator) :可以将流中的元素反复结合起来,得到一个值,比如求一个数组的和
identity:起始值
BinaryOperator:二元运算
@Test
public void test(){
List<Integer>list=Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum=list.stream().reduce(0,(x,y)->x+y);
System.out.println(sum);
}
注意:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名
收集: collect(Collector super T, A, R> collector):将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法。例如,把一个公司的所有员工信息单独收集起来存到一个集合中去
collector:我们要按照哪种方式去收集
案例演示:
@Test
public void test(){
List<Emp>list=Arrays.asList(new Emp("zs",10),new Emp("ls",15),new Emp("zs",16));
List<String>l1=list.stream().map(Emp::getName).collect(Collectors.toList());
// Collectors 里面有很多的工具
for (String s : l1) {
System.out.println(s);
}
}
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流
Java8 中将并行进行了优化,我们可以很容易对数据进行并行操作。Stream API 可以生命性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换
Fork/Join 框架: 就是在必要的情况下,将一个大人物,进行拆分成若干个小任务(拆到不可再分时),再将一个个的小任务运算的结果进行 join 汇总
Fork/Join 框架采用了“工作窃取”模式:
当执行新任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,当自己的线程队列执行完了之后,再从其他的一个随机线程队列中偷一个并把它放在自己的队列中
相对于一般的线程池实现,Fork/Join 框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在 Fork/Join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻求其他尚未运行的子问题来执行,这种方式减少了线程等待时间,提高性能
计算 1~ 1000000000
import java.util.concurrent.RecursiveTask;
public class ForkJoinCalculate extends RecursiveTask<Long> {
Long start,end;
Long thread=5000L;
public ForkJoinCalculate(Long start,Long end) {
this.start=start;
this.end=end;
}
@Override
protected Long compute() {
Long len=end-start,ans=0L;
if(len<=thread){
for(Long i=start;i<end;i++) ans+=i;
return ans;
}
else{
Long mid=(start+end)/2;
ForkJoinCalculate left=new ForkJoinCalculate(start,mid);
ForkJoinCalculate right=new ForkJoinCalculate(mid,end);
left.fork(); //拆分子任务,同时压入线程队列中
right.fork();
return left.join()+right.join();
}
}
}
public class ForkJoinTest {
@Test
public void test(){
//要用到 ForkJoinPool 的池子
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long>forkJoinTask=new ForkJoinCalculate(0L,1000000000L);
Long sum=pool.invoke(forkJoinTask);
System.out.println(sum);
}
}
Optional
类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用 null 来表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常
常用方法:
在接口中的方法前加上default关键字就可以在接口中写方法的默认实现。
默认方法,接口的子类不需要实现,可以直接使用
可以定义一个或多个默认方法
Java8为什么要引入默认方法?
默认方法的主要优势是提供一种拓展接口的方法,而不破坏现有代码。加入我们有一个已经投入使用接口需要拓展一个新的方法,在JDK8以前,如果为一个使用的接口增加一个新方法,则我们必须在所有实现类中添加该方法的实现,否则编译会出现异常。如果实现类数量少并且我们有权限修改,可能会工作量相对较少。如果实现类比较多或者我们没有权限修改实现类源代码,这样可能就比较麻烦。而默认方法则解决了这个问题,它提供了一个实现,当没有显示提供其他实现时就采用这个实现。这样新添加的方法将不会破坏现有代码。
默认方法的另一个优势是该方法是可选的,子类可以根据不同的需求Override默认实现。
接口默认方法的 “类优先” 原则
若接口中定义了一个默认方法,而另一个父类或接口中又定义了一个同名的方法时
和原来的时间日期API最大的不同就是以前不是线程安全的,现在的是线程安全的
就是在一个类上可以定义两个类型相同的注解