目录
- Lambda表达式
- 引入
- 参数列表
- 表达式
- 变量
- 函数式接口
- consumer、supplier、runnable、function、predicate
- 方法引用
- 接口默认方法
- Stream流
- Stream的本质
- Stream执行流程
- Optional
- Optional API
Lambda表达式
引入
Lambda表达式,Stream流
Java是一门面向对象的语言,从Java8出来后,加入了函数式编程。这个概念就被推翻了。函数式编程对并行开发,基于事件的开发有非常特别的优势。
$(function(){
$('.click').click(function(){
myClickLogic();
})
})
jquery中表示页面加载完之后要做事情。一个方法作为参数传到另外一个方法之中。
在java中要实现类似的效果,要使用匿名内部类
package com.java8.lambda;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Comparator;
public class LambdaTest1 {
class User{
public String name;
public int score;
public User(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
@Test
public void testOldUse(){
User[] us = new User[]{
new User("张三",90),
new User("李四",80),
new User("匿名",76)
};
//对User数组按照score排序
Arrays.sort(us,new Comparator<User>(){
@Override
public int compare(User o1, User o2) {
// return o1.score - o2.score; //如果前面是一个很大的数,后面是一个很小的数。这么一减,就可能溢出
return Integer.compare(o1.score,o2.score);
}
});
System.out.println(Arrays.toString(us));
}
@Test
/**
* 可能里面的子线程还没有执行,Test线程就执行完了
* 简单测试没有问题
*/
public void testOldUse2(){
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("hello,lambda");
}
}).start();
}
}
传一个接口,在接口的实现类里面实现自己的逻辑。没有js那么简洁优雅
思考1:
- Arrays.sort的第二个参数一定是Comparator接口
- Comparator里面一定要重写compare方法
-
compare方法一定要返回int
这些一定要java程序员写出来吗?这些不能够由jvm虚拟机推导吗?
将上面所说的三个东西删除掉,在参数列表和方法体之间加上
->
就可以让代码编译通过。这一段东西就是Lambda表达式
@Test
public void testNewUse(){
User[] us = new User[]{
new User("张三",90),
new User("李四",80),
new User("匿名",76)
};
//对User数组按照score排序
Arrays.sort(us,
(User o1, User o2) -> {
return Integer.compare(o1.score,o2.score);
});
System.out.println(Arrays.toString(us));
}
为什么可以推导?因为有三个一定,将代码简化为lambda,jvm可以将lambda反推回匿名内部类的写法。Lambda只是将以前一定要java程序员要做的事情省略掉。省略掉一定可以推导出来的东西。业务逻辑无法推导
Lambda表达式还可以改进。
- 代码块里面只有一行代码,可以删除掉花括号。
可以不要了。return
返回值一定是intInteger.compare()
Arrays.sort(us, (User o1, User o2) -> Integer.compare(o1.score,o2.score) );
-
参数的类型可以省略
o1,o2是us数组里面的元素
Arrays.sort(us, (o1,o2) -> Integer.compare(o1.score,o2.score) );
jvm可以帮助我们推导什么?
- 不管方法是否有参数,小括号不能够省略。小括号代表方法
new Thread(() -> System.out.println("hello,lambda")).start();
编译器会把我们写的代码转成字节码,转换的过程中可以把lambda语句还原成匿名内部类
@Test
public void testOldUse3(){
Runnable run = new Runnable(){
@Override
public void run() {
System.out.println("hello lambda!");
}
};
new Thread(run).start();
new Thread(run).start();
}
@Test
public void testNewUse3(){
Runnable run = () -> System.out.println("hello lambda!");
new Thread(run).start();
new Thread(run).start();
}
参数列表
-
lambda的历史: 波长单位,带有参数变量的表达式称为lambda表达式
避免匿名内部类使用过多,使代码更加简洁,去掉无意义的代码,只留下核心的逻辑
- lambda表达式的基本语法
- 参数列表 -> 表达式 ;
- 如果没有参数,直接用小括号表示,小括号不能够省略
-
如果只有一个参数,
如果加了参数类型,小括号不能省略;
如果去掉参数类型,小括号能够省略
- 两个或者多个参数, 不管是否写参数类型,小括号不能省略
String[] strs = new String[]{"Alice","Mike","Byte"}; Arrays.sort(strs,(s1,s2)->Integer.compare(s1.length(),s2.length())); System.out.println(Arrays.toString(strs));
- 如果参数要加修饰符或者标签,参数一定要加上完整的类型
表达式
-
如果表达式只有一行代码,可以直接不写花括号{}
接口里面只有一行代码
- 如果表达式有多行,要加上花括号变成代码块
- 如果表达式是代码块,并且方法需要返回值,那么在代码块中必需返回一个返回值
- 如果只有单行的情况,并且方法需要返回值,绝对不能写
,编译器会自动帮助我们推导return
return
变量
public void repeatPrint(String content,int times){
Runnable run = () -> {
for(int i=0;i<times;i++)
System.out.println(content);
};
new Thread(run).start();
}
@Test
public void testVar(){
this.repeatPrint("lambda",5);
}
- 参数(参数列表的变量,接口的方法的参数)
- lambda里面声明的局部变量
- 自由变量(不是参数,也不是局部变量)
结论:Lambda表达式找那个的自由变量会被保存,无论是什么时候执行Lambda表达式,都可以直接使用
自由变量在Lambda在是不能修改的,一旦在Lambda中使用到变量,变量就会变成
final
修饰。
操作自由变量的代码块,称为闭包
参数和局部变量的使用方式和普通的变量使用方式相同
- this关键字注意问题
Lambda表达式中的
this
指向创建Lambd表达式的方法中的
this
匿名内部类的
this
指向匿名内部类的实例
package com.java8.lambda;
import org.junit.jupiter.api.Test;
public class LambdaTest3 {
public void repeatPrint(String content,int times){
Runnable run = () -> {
for(int i=0;i<times;i++)
System.out.println(content);
System.out.println(this);
};
new Thread(run).start();
}
public void repeatPrint2(String content,int times){
Runnable run = new Runnable() {
@Override
public void run() {
for(int i=0;i<times;i++)
System.out.println(content);
System.out.println(this);
}
};
new Thread(run).start();
}
@Test
public void testVar(){
this.repeatPrint("lambda",3);
/**
* lambda
* lambda
* lambda
* com.java8.lambda.LambdaTest3@467738e0
*/
}
@Test
public void testVar2(){
this.repeatPrint2("lambda",3);
/**
* lambda
* lambda
* lambda
* com.java8.lambda.LambdaTest3$1@bd94a08
*/
}
}
函数式接口
-
我们能够写Lambda表达式的地方?一个接口,且接口只有一个抽象方法
函数式接口里面可以写Object类的方法
Comparator接口里面除了compare,equals,还有被default修饰的方法?
- 在Java中,把只有一个抽象方法的接口称为函数式接口,如果一个接口是函数式接口,我们可以在接口上添加
注解标明这是一个函数式接口@FunctionalInterface
@FunctionalInterface
public interface IMyWork {
void doWork();
}
package com.java8.lambda;
import org.junit.jupiter.api.Test;
public class LambdaTest4 {
public void wrapWork(IMyWork work){
System.out.println("do some wrap work...");
work.doWork();
}
@Test
public void test(){
//this.wrapWork(()-> System.out.println("do real work"));
IMyWork work = () -> System.out.println("do real work");
this.wrapWork(work);
//Runnable work = () -> System.out.println("do real work");
}
}
-
无论是否标识@FunctionInterface,只要满足函数式接口的接口,Java都会直接识别为函数式接口
加上注解的好处,如果不满足函数式解口,可以自动提醒
- Java中提供Lambda表达式唯一的作用是简化函数式接口
- 有了上下文环境,Lambda表达式才能够反推相应的接口(才能够知道自己的类型)
- Lambda表达式中的异常处理
- 在代码块中处理
- 在接口的方法中声明
void doWork() throws Exception;
consumer、supplier、runnable、function、predicate
-
consumer
Lambda没有返回值
-
supplier
Lambda有返回值
-
runnable
Lambda既无输出也无输入
-
function
Lambda可以有输入,输出
-
predicate
Lambda返回布尔类型
方法引用
Integer[] is = new Integer[]{2,3,4,6,4,3,5};
/**
* x,y原封不动的作为compare的参数
*/
Arrays.sort(is,(x,y)->Integer.compare(x,y));
/**
* 方法引用(类::静态方法)
*/
Arrays.sort(is, Integer::compare);
System.out.println(Arrays.toString(is));
Integer[] is = new Integer[]{2,3,4,6,4,3,5};
List<Integer> la = Arrays.asList(2, 3, 4, 6, 4, 3, 5);
la.forEach(x-> out.println(x+" "));
//println是System类下out对象的一个方法
la.forEach(System.out::println);
-
类::静态方法
-
对象::方法
Integer[] is = new Integer[]{2,3,4,6,4,3,5}; LambdaTest5 lt = new LambdaTest5(); //自己写的类 //方法引用(对象::方法) Arrays.sort(is, lt::compare); System.out.println(Arrays.toString(is));
-
对象::静态方法
- 构造器引用, 需要有以一个无参构造
构造器引用对应的函数式接口里面的方法格式一定是,返回一个对象,方法没有参数类::new
@FunctionalInterface public interface IMyCreator<T extends List<?>> { T create(); }
package com.java8.lambda; import org.junit.jupiter.api.Test; import java.util.LinkedList; import java.util.List; public class LambdaTest6 { public <T> List<T> asList(IMyCreator<List<T>> creator,T... a){ List<T> list = creator.create(); for(T t:a) list.add(t); return list; } @Test public void test(){ List<Integer> list = this.asList(LinkedList::new,2,3,4,5,1,10,8,6); //LinkedList::new 相当于 ()->{reurn new ArrayList();} list.forEach(System.out::println); System.out.println(list.getClass()); } }
注:java泛型方法带有返回值
接口默认方法
允许在接口里面写默认的实现。写实在的方法
-
用接口的方式来实现以前无法达到的多重继承
接口1:
接口2:public interface ILandAnimal { void run(); default void breathInAir(){ System.out.println("breath in air..."); } default void breath(){ System.out.println("LandAnimal breath"); } }
父类:public interface IWaterAnimal { void swim(); default void breathInWater(){ System.out.println("breath in water..."); } default void breath(){ System.out.println("waterAnimal breath"); } }
Forgpublic class BigMouth { public void openMouth(){ System.out.println("大嘴巴..."); } }
package com.java8.interfaceMethod; public class Forg extends BigMouth implements IWaterAnimal,ILandAnimal { @Override public void run() { System.out.println("对不起,我只会跳"); } @Override public void swim() { System.out.println("对不起,我没有尾巴,只会蛙泳"); } @Override public void breath() { ILandAnimal.super.breath(); } public static void main(String[] args) { Forg forg = new Forg(); forg.swim(); forg.breathInWater(); forg.run(); forg.breathInAir(); forg.openMouth(); forg.breath(); /** * 对不起,我没有尾巴,只会蛙泳 * breath in water... * 对不起,我只会跳 * breath in air... * 大嘴巴... * LandAnimal breath */ } }
- 如果两个接口出现了相同名字的默认方法,那么java要求实现类重写方法,即实现类必需实现冲突的方法,指定使用哪个接口中的默认实现
@Override public void breath() { ILandAnimal.super.breath(); }
-
父类的方法和接口的默认方法冲突
父类有一个方法和接口的一个默认方法有相同的方法签名,那么使用的是父类里面的方法。
所有的类隐式继承了Object类,不要期望通过使用接口的默认方法来改变Object对象里面的方法
- 接口里面可以写接口的静态方法
public interface IWaterAnimal { void swim(); static void count(){ System.out.println("现在的动物种类正在减少"); } }
IWaterAnimal.count();
-
常见的一些模式被消灭了
工具类:DefaultHandler.java
适配器模式:WindowAdapter.java
写接口的方法做成default方法就可以了,使用的时候覆盖即可
Comparator即作为函数式接口,又作为工具类存在
Stream流
案例1:
@Getter
@Setter
@AllArgsConstructor
@ToString
public class User {
private String name;
private int score;
}
package com.java8.stream;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.stream.Collectors;
public class StreamTest1 {
private List<User> us = new ArrayList<>();
public void prepared(){
Random random = new Random();
for(int i=0;i<100;i++){
us.add(new User("user"+i,random.nextInt(50)+50));
}
}
//1. 列出班上超出85分的学生姓名,并且按照降序的方式输出用户的名字
@Test
public void test1(){
this.prepared();
System.out.println(us);
List<String> ret = new ArrayList<>();
List<User> temp = new ArrayList<>();
for(User u:us){
if(u.getScore()>85){
temp.add(u);
}
}
temp.sort(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return Integer.compare(o2.getScore(),o1.getScore());
}
});
for (User u:temp){
ret.add(u.getName());
}
System.out.println(ret);
/**
* 业务逻辑只有三个地方,其他地方都是在遍历
*/
/*
* 使用Stream
* 1. 得到集合的流对象
* 2. 使用filter方法进行过滤
* 3. 使用sorted方法完成了排序
* 4. 使用map方法把User的流变成了String的流
* 5. 使用collect把String的流变成了一个List<String>
*/
ret = us.stream()
.filter(u->u.getScore()>85)
//.sorted((u1,u2)->Integer.compare(u2.getScore(),u1.getScore()))
.sorted(Comparator.comparing(User::getScore).reversed())//比较完Score排序后再逆序
.map(u->u.getName())
.collect(Collectors.toList());
System.out.println(ret);
}
}
Stream流的写法更加贴近自然语言
案例2:
//统计出平均分数
@Test
public void test2(){
this.prepared();
double totalScore = 0;
for (User u:us){
totalScore+=u.getScore();
}
if(us.size()>0){
double avg = totalScore/us.size();
System.out.println(avg);
}
//使用Stream
//Optional可选,可能为空
us.stream().mapToInt(User::getScore)
.average().ifPresent(System.out::println);
}
Java8中的Stream是对集合对象功能的增强,它专注于对集合对象进行各种便利,高效的聚合操作或者大批量的数据操作。
Stream处理和集合相关的东西,Stream提供了很多直接使用Lambda的方法,Stream的重点在于数据的操作(里面隐含了遍历,只要传入逻辑即可)
Stream的本质
外部迭代带来的问题
Integer[] ints = {1, 2, 3, 4, 5, 6, 7, 8};
List<Integer> ret = new ArrayList<>();
for(Integer i:ints)
if(i>5)
ret.add(i);
System.out.println(ret);
业务的代码和遍历的代码揉在一块儿,需要准备另外一个列表来保存结果。循环的代码和业务处理的代码混淆
Stream内部循环,代码里面不需要写循环的操作
Integer[] ints = {1, 2, 3, 4, 5, 6, 7, 8};
Stream.of(ints)
.filter(x->x>5)
.collect(Collectors.toList())
.forEach(System.out::println);
- Stream是不会存储数据的
- Stream是不会修改源数据的
- Stream是单向,不可重复使用的
- Stream的部分操作是延迟的
- 只要Stream的方法返回的是Stream,那么这些就是延迟执行的方法
调用了一个方法,马上执行,我们叫做迫切执行方法;调用了一个方法,并不会立刻执行,叫做延迟执行方法。
在Stream里面,返回的不是一个Stream的基本都是迫切执行的方法
- 延迟执行的方法一定要等到一个迫切执行的方法执行的时候,才会执行
public boolean compare(int x){ System.out.println("调用了compare方法"); return true; } @Test public void test3() { Integer[] ints = {1, 2, 3, 4, 5, 6, 7, 8}; Stream<Integer> stream = Stream.of(ints).filter(this::compare); System.out.println("================"); stream.collect(Collectors.toList()).forEach(System.out::println);//collect是迫切执行的方法 /** * ================ * 调用了compare方法 * 调用了compare方法 * 调用了compare方法 * 2 * 4 * 1 */ }
可以简单理解为Stream是一种高级的Iterator,这种Iterator迭代器提供了更多关于其包含的数据的操作功能。
创建水流,搭建管道,得到干净的水
- 使用Stream
- 创建一个Stream
- 对Stream进行操作和变换(延迟操作)
- 通过Stream得到一个j结果(迫切操作/执行延迟操作)
Stream执行流程
stream.filter(x->x>5).filter(x->x<8).collect(toList());
collect就像是一个开关。
不是第一个filter过滤完了,第二个filter才打开,6可以直接通过管道。容易执行并行操作
Optional
Optional出现是为了解决空指针问题。
Optional可以优化,使得我们的代码更加简单
并不是所用了Optional就不会产生空指针了,它强制我们去思考空指针
NullPointer是最糟糕的隐藏错误。函数调用栈变深,溯源变得困难
Optional是一个泛型类,泛型就是我们使用的类。Optional为我们提供了一些增强的方法
Optional API
of
获取Optional包裹的类型
创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException
Optional<String> name = Optional.of("Alice");
String s = name.get();
System.out.println(s); //Alice
Optional<String> someNull = Optional.of(null); //java.lang.NullPointerException
ofNullable
可以接受参数为null的情况。可能会导致空指针传递,蔓延
Optional<String> name = Optional.ofNullable(null);//精髓所在。防止空指针。这一步还不会报错
String s = name.get();//直接取就会报错
empty
返回一个空的Optional对象
Optional<String> name = Optional.of("Alice");
Optional<Object> empty = name.empty();
System.out.println(empty); //Optional.empty
get
如果Optional有值则将其返回,否则抛出NoSuchElementException。
isPresent
如果Optional实例有值则为其调用consumer,否则不做处理
//name.ifPresent(t-> System.out.println(name.get()));
// 省去lambda 传递参数的过程
Optional<String> name = Optional.of("Alice");
name.ifPresent(System.out::println);
orElse
如果Optional实例有值则将其返回,否则返回orElse方法传入的参数。
Optional<String> t1 = Optional.of("Alice");
Optional<String> t2 = Optional.ofNullable(null);
String s1 = t1.orElse("默认值");
String s2 = t2.orElse("默认值");
System.out.println(s1);
System.out.println(s2);
/**
* Alice
* 默认值
*/
orElseThrow
判空,如果存在,则返回相应的值; 如果不存在,则抛出一个异常
public String testOrElseThrow() {
Optional<String> name = Optional.of("Alice");
return name.orElseThrow(()->new RuntimeException("exception"));
}
public String testOrElseThrow2(){
Optional<Object> name2 = Optional.ofNullable(null);
return (String) name2.orElseThrow(()->new RuntimeException("exception"));
}
@Test
public void test2(){
String s = this.testOrElseThrow();
String s2 = this.testOrElseThrow2(); //java.lang.RuntimeException: exception
}
filter
接受参数为Predicate对象,用于对Optional对象进行过滤,如果符合Predicate的条件,返回Optional对象本身,否则返回一个空的Optional对象。
Optional<String> t1 = Optional.ofNullable("a");
String s1 =t1.filter(t->t.equals("a")).orElse("wrong");
System.out.println(s1); //a
map
不是Stream里的map
map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)
Optional<String> t1 = Optional.ofNullable("a");
String s1 = t1.map(t -> t + "b").orElse("c");
System.out.println(s1); //ab
Optional类结合Lambda表达式的使用能够让开发的代码更简洁和优雅