一、 Lambda表达式
1. Lambda表达式的好处
使用Lambda表达式可以使代码变得更加简介紧凑,并且Lambda表达式可以和StreamAPI等相结合,使代码更加简洁紧凑。Lambda表达式经常用来替代部分匿名内部类。
2. Lanbda表达式的语法
(parameters) -> expression或 (parameters) ->{ statements; }
参数:要重写的方法的形参列表
-> :lambda运算符
表达式/语句体:要重写的方法的方法体
Lambda 表达式是一种匿名函数(不是匿名内部类),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。它实质属于函数式编程的概念。
3. 注意事项
- 函数只有一条语句{ }是可以省略的
- 在定义参数的时候Lambda表达式的参数类型是可以省略的
- 如果只有一个参数这个时候()是可以省略的
- 如果定义返回值并且方法中只有一条执行语句的时候,这个时候return是可以省略的。
4. 用法
引入内部类以及匿名内部类
interface Fu{
void add();
}
class Zi implements Fu{
@Override
public void add() {
}
}
class TestA{
//内部类
class Zi implements Fu {
@Override
public void add() {
}
}
public static void main(String[] args) {
//匿名内部类
Fu f=new Fu() {
@Override
public void add() {
System.out.println("我是addddd方法");
}
};
f.add();
}
}
引入Lambda
package com.bjsxt.demo03;
public class TestC {
public static void main(String[] args) {
System.out.println("------------1、没有参数也没有返回值--------------");
/* F f=()-> System.out.println("我是add方法");
f.add();*/
System.out.println("------------2、有参数没有返回值--------------");
/* F f=num -> System.out.println("我是方法add"+num);
F f1=(int num)->{
System.out.println("我是方法add"+num);
};
f.add(123);
f1.add(234);*/
System.out.println("------------3、有参数也有返回值--------------");
F f=(int num)->{
num++;
return num; //直接使用return进行返回即可
};
F f1=num -> num++; //传入参数num 在方法中进行num++ 再把结果返回
System.out.println(f.add(123));
System.out.println(f1.add(234));
}
}
interface F{
// void add();
// void add(int num);
int add(int num);
}
二、 函数式接口
函数式接口 | 方法名 | 输入参数 | 输出参数 | 作用 |
---|---|---|---|---|
消费型接口 Consumer |
void accept(T t) | T | void | 对类型为T的对象进行操作 |
供给型接口 Supplier |
T get() | void | T | 返回类型为T的对象 |
函数型接口 Function |
R apply(T t) | T | R | 对类型为T的对象进行操作,返回类型为R的对象 |
断言型接口 Predicate |
boolean test(T t) | T | boolean | 对类型为T的对象进行操作,返回布尔类型结果 |
三、 方法引用
有时候,Lambda体可能仅调用一个已存在方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰。方法引用是一个更加紧凑,易读的 Lambda 表达式,注意方法引用是一个 Lambda 表达式,方法引用操作符是双冒号 “::”。
方法引用有下面几种方式:
1) 对象引用::实例方法名
2) 类名::静态方法名
3) 类名::实例方法名
4) 类名::new(也称为构造方法引用)
5) 类型[]::new(也称为数组引用)
这里讲解了第一种方式(System.out::println、stu::getName)和第二种方式(Integer::compare)
第一种A:
public class TestMethodRef1 {
public static void main(String[] args) {
//使用匿名内部类实现
Consumer consumer = new Consumer<Integer>() {
@Override
public void accept(Integer i) {
System.out.println(i);
}
};
consumer.accept(56);
//使用lambda表达式实现
Consumer<Integer> consumer1 = (i)->System.out.println(i);
consumer1.accept(56);
//使用方法引用
//println()的参数类型、返回值类型正好和accept方法的参数类型、返回值类型相同
Consumer<Integer> consumer2 = System.out::println;
consumer2.accept(56);
}
}
第一种B:
public class TestMethodRef2 {
public static void main(String[] args) {
Student stu = new Student(10,"zhangsan",23,100);
//使用匿名内部类实现
Supplier<String> supplier1= new Supplier<String>(){
public String get() {
return stu.getName();
}
};
System.out.println(supplier1.get());
//使用Lambda表达式实现
Supplier<String> supplier2 = ()-> stu.getName();
System.out.println(supplier2.get());
//使用方法引用实现
Supplier<String> supplier3 = stu::getName;
System.out.println(supplier3.get());
}
}
第二种:
public class TestMethodRef3 {
public static void main(String[] args) {
//使用匿名内部类实现
Comparator comparator1 = new Comparator<Integer>() {
public int compare(Integer in1, Integer in2) {
//return in1.intValue()-in2.intValue();
return Integer.compare(in1,in2);
}
};
System.out.println(comparator1.compare(12,34));
//使用Lambda表达式实现
Comparator<Integer> comparator2 = (in1,in2)->{ return Integer.compare(in1,in2);};
System.out.println(comparator2.compare(12,34));
//使用方法引用实现
Comparator<Integer> comparator3 =Integer::compare;
System.out.println(comparator3.compare(12,34));
}
}
Stream作为Java8的一大亮点,它与java.io包里的InputStream和OutputStream是完全不同的概念。它是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作或者大批量数据操作。
Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。所以说,Java8中首次出现的 java.util.stream是一个函数式语言+多核时代综合影响的产物。
Stream有如下三个操作步骤:
一、创建Stream:从一个数据源,如集合、数组中获取流。
二、中间操作:一个操作的中间链,对数据源的数据进行操作。
三、终止操作:一个终止操作,执行中间操作链,并产生结果。
当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。比如map (mapToInt, flatMap 等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终止操作。终止操作将返回一个执行结果,这就是你想要的数据。比如:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理!而在终止操作时一次性全部处理,称作“惰性求值”。
流式编程1:中间操作和中止操作
public class TestStream1 {
public static void main(String[] args) {
List list = new ArrayList<>();
Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
//创建Stream
Stream stream = list.stream();
//进行中间操作
stream = stream.filter((x)->{if(x>=60) return true; return false;})//刷选掉不及格的
.distinct() //去重
.sorted((x1,x2)->{return -Integer.compare(x1,x2);})//降序排列
.limit(4)//只要前四个
.map((x)->x+5)//每个成绩加5分
.skip(2);//跳过前两个,从第三个开始
//进行终止操作:stream has already been operated upon or closed
//stream.forEach(System.out::println);//遍历
//System.out.println(stream.max((x1,x2)->x1-x2));;
//System.out.println(stream.count());
System.out.println(stream.findFirst());
}
}
流式编程2:并行流和串行流
什么是并行流?
简单来说,并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。 Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换 。
Fork/Join 框架
Fork/Join 框架: 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总 。
使用ForkJoin和普通for实现1-1000000000000l求和效率对比
package com.bjsxt.demo;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class SumTask extends RecursiveTask {
private long start;
private long end;
private final int step = 2000000;//最小拆分成几个数相加
public SumTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override //在这个方法中定义我们自己计算的规则
protected Long compute() {
long sum = 0;
if(end - start <= step ){
//System.out.println("小于步长进行求和");
//小于5个数,直接求和
for (long i = start; i <=end; i++) {
sum+=i;
}
}else{
//大于5个数,分解任务
long mid = (end + start)/2;
SumTask leftTask = new SumTask(start,mid);
SumTask rightTask = new SumTask(mid+1,end);
//执行子任务
leftTask.fork();
rightTask.fork();
//子任务,执行完,得到执行结果
long leftSum = leftTask.join();
long rightSum = rightTask.join();
//System.out.println("join结果"+leftSum+"---"+rightSum);
sum = leftSum+rightSum;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
int sum=0;
long l = System.currentTimeMillis();
for (int i = 0; i <=1000000000000l; i++) {
sum+=i;
}
long l2 = System.currentTimeMillis();
System.out.println("forx循环执行时间:"+(l2-l));
//使用ForkJoin框架解决
//创建一个ForkJoin池
ForkJoinPool pool = new ForkJoinPool();
//定义一个任务
SumTask sumTask = new SumTask(1,1000000000000l);
//将任务交给线程池
long l3 = System.currentTimeMillis();
Future<Long> future = pool.submit(sumTask);
//得到结果并输出
Long result = future.get();
long l4 = System.currentTimeMillis();
System.out.println("ForkJoin执行时间:"+(l4-l3));
}
}
执行结果:
//创建Stream方式2:并行流,底层采用ForkJoin框架,结果并不按照集合原有顺序输出
System.out.println(“————————“);
Stream stream2 = list.parallelStream();//
stream2.forEach((x)->System.out.println(x+”—-“+Thread.currentThread().getName()));
流式编程3:创建Stream
public class TestStream2 {
public static void main(String[] args) {
//创建Stream方式1
List list = new ArrayList<>();
Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
Stream stream = list.stream();
stream.forEach(System.out::println);
//创建Stream方式2:并行流,底层采用ForkJoin框架,结果并不按照集合原有顺序输出
System.out.println("----------------");
Stream stream2 = list.parallelStream();//
stream2.forEach((x)->System.out.println(x+"---"+Thread.currentThread().getName()));
//创建Stream方式3:of()
System.out.println("----------------");
Stream stream3 = Stream.of(34,56,89,65,87,80,87,95,100,34,45);
stream3.forEach(System.out::println);
//创建Stream方式4
int [] arr = {34,56,89,65,87,80,87,95,100,34,45};
IntStream stream4 = Arrays.stream(arr);
stream4.forEach(System.out::println);
//创建Stream方式5
Stream stream5 = Stream.generate(()->Math.random());
// stream5.forEach(System.out::println);
//创建Stream方式6
Stream stream6 = Stream.iterate(6,(i)->2+i);
stream6.limit(5).forEach(System.out::println);
}
}