函数
输入相同的情况下,无论多少次调用都会得到相同的输出。换言之,输入绝对决定输出。
为什么要有?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class Example {
interface Lambda {
int calculate (int a, int b);
}
// 对于add方法,位置固定,使用必须通过Example类
int add (int a, int b) {
return a + b;
}
// 对于add对象,位置可变,传递这条规则即可
Lambda add = (a, b) -> a + b;
}
public class TestExample {
public static void main(String[] args) {
System.out.println(new Exmaple().add(3, 4));
}
System.out.println(new Example().add.calculate(1, 2));
}
|
关于位置可变与位置不可变,将设现在有3台JVM虚拟机,分别为Server,Client0和Client1。
假设Server没有存储运算逻辑,并随机生成两个数字进行运算。Client需要传递运算逻辑。Server虚拟机只有运算的接口定义。
此时,方式2就可以只传递运算方式的Lambda运算方式对象。而方式1需要完整的传递定义运算方法的类,并且服务端反序列化该类后实现运算逻辑,这前提是服务端有该类的字节码文件,相当于把实现绑定在了服务器端,失去了意义。
究其原因,Java底层实现了接口定义,因此可以直接传输Lambda对象,而传自定义对象就需要相对应的字节码文件。
函数对象的核心:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
interface Lambda {
boolean test(User user);
}
public List<User> filter(List<User> users, Lambda lambda) {
List<User> res = new ArrayList<>();
for (User user : users) {
if (lambda.test(user)) {
res.add(user);
}
}
return res;
}
System.out.println(users, u -> u.age < 18);
|
函数编程语法
表现形式
- Lambda表达式
(int a, int b) -> a + b;
看作一个函数对象。
- 方法引用,写法更简洁
Math::max
等价于(int a, int b) -> Math.max(a, b);
对象类型
两个维度
两个维度一致便属于同一种类型。
1
2
3
4
5
6
7
8
9
|
public class Example2 {
// 参数个数类型相同,返回值相同,都可以赋值给Type类型变量
Type obj1 = (int a) -> (a & 1) == 0;
Type obj2 = (int a) -> a > 0;
@FunctionalInterface
interface Type {
boolean op(int a);
}
}
|
函数式接口,仅包含一个抽象方法,用@FunctionalInterface
检查。
JDK提供的函数式接口
IntPredicate
断言型 1个int
入参,1个boolean
出参
IntBinaryOperator
2个int
入参,1个int
出参
Supplier<T>
无入参,泛型出参
Function
泛型入参,泛型出参
其他常见函数式接口
Runnable
()->void
Callable
()->T
Comparator
(T,T)->int
Consumer
(T)->void
Function
(T)->R
Predicate
(T)->boolean
Supplier
()->T
BinaryOperator
(T)->T
命名规则:
Consumer
有参,无返回值
Function
有参,有返回值
Predicate
有参,返回布尔类型
Supplier
无参,有返回值
Operator
有参,有返回值,且类型一致
方法引用
将现有方法的调用化为函数对象
1
2
|
(String s) -> Integer.parseInt(s);
Integer::parseInt
|
1
2
|
(stu) -> stu.getName();
Student::getName
|
1
2
|
() -> new Student();
Student::new
|
闭包「Closure」和柯里化「Currying」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Example {
@FunctionalInterface
interface Lambda {
int op(int y);
}
static void cal(Lambda lambda) {
System.out.println(lambda.op(1));
}
public static void main(String[] args) {
/**
抑或是effective final
*/
// 闭包是一种给函数执行提供数据的手段
final int x = 10; // 由于函数要保证不变性,要加关键词final
cal((int y) -> x + y); // 用到外界变量,形成闭包
}
}
|
举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class Example {
public static void main(String[] args) {
List<Runnable> list = new ArrayList<>();
// 创建-10-个任务,并且给每个任务添加编号
// 此处闭包作用:提供了函数对象参数以外数据,此处为线程序号
for (int i = 0; i < 10; i++) {
// 此处即是闭包,i不可变,这样写会报错
// Runnable task = () -> System.out.println("执行任务" + (i + 1));
int k = i + 1;
Runnable task = () -> System.out.println(Thread.currentThread() + "执行任务" + k);
list.add(task);
}
ExecutorService service = Exectors.newVirtualThreadExectuor();
for (Runnable task : list) {
service.submit(task);
}
// 阻塞主线程
System.in.read();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class Example {
@FunctionalInterface
interface func {
int op(int a, int b);
}
@FunctionalInterface
interface funcA {
funcB op(int a);
}
@FunctionalInterface
interface funcB {
int op(int b);
}
public static void main(String[] args) {
func func1 = (a, b) -> a + b;
func1.op(10, 20);
// 需求:只传一个参数达到相同的效果
// funca传入一个参数得到一个函数对象funcb
// 多个参数的函数对象转为一系列只有一个参数的函数对象,这个过程称为柯里化
// 柯里化的作用:函数分步执行
funcA funca = (a) -> (b) -> a + b; // a相当于闭包的外界参数 (a) ->[b -> (a + b)]
funcB funcb = funca.op(10);
funcb.op(20);
}
}
|
高阶函数
所谓高阶,就是它是其他函数对象的使用者。
作用:将通用、复杂的逻辑隐藏在高阶函数内,将易变、未定的逻辑放在外部的函数对象中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Example {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7);
// 需求:逆序遍历集合,只负责处理,不改变集合。
highOrder(list, System.out::println);
}
// highOrder就是一个高阶函数
public static <T> void highOrder(List<T> list, Consumer<Integer> consumer) {
ListIterator<T> iterator = list.listIterator(list.size());
while (iterator.hasPrevios()) {
T val = iterator.previous();
consumer.accept(value);
}
}
}
|
Stream API
手写一个简单流玩耍一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class SimpleStream {
public static void main(String[] args) {
HashSet<Integer> ans = Simplestream.of(1, 2, 3, 4, 5, 6, 7)
.collect(HashSet::new, HashSet::add);
}
public <C> C collect(Supplier<C> supplier, BiConsumer<C, T> consumer) {
C c = new supplier.get(); // 创建容器
for (T t : collection) {
consumer.accept(c, t);
}
return c;
}
public SimpleStream<T> filter(Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T t : collection) {
result.add(t);
}
return new SimpleStream<>(result);
}
private Collection<T> collection;
public static <T> SimpleStream of(Collection<T> collection) {
return new SimpleStream(collection);
}
private SimpleStream(Collection<T> collection) {
this.collection = collection;
}
}
|
构建流的方式
- 集合构建
集合.stream()
- 数组构建
Arrays.stream(数组)
- 对象构建
Stream.of(对象)
合并与截取
concat
合并
skip
limit
takeWhile
dropWhile
截取
查找与判断
1
|
stream.filter(x -> (x & 1) == 0)).findFirst().ifPresent(System.out::println);
|
去重和排序
1
|
stream.sorted(Comparator.comparingInt(Student::age).reversed().thenComparingInt(stu -> stu.name().length()));
|
化简
收集
流的特性
- 一次使用,流中每个元素只能操作一次,不能重复执行
- 两类操作,中间操作lazy、终结操作eager
并行流
parallel
转换为并行流
- 两种方案,都是线程安全的,方案1更加节省内存空间
Characteristics.CONCURRENT
+ Characteristics.UNORDERED
+ 线程安全容器
- 默认 + 线程不安全容器
举个栗子
异步处理:
1
2
3
|
CompletableFuture
.supplyAsync(() -> someAwesomeFunc())
.thenAccept(a -> System.out::println);
|
实现原理
Lambda表达式是一种语法糖,它会被翻译成类、对象、方法。
编译器发现Lambda表示式后,就会在当前类生成private static方法,方法内包含的就是lambda表示的逻辑。
类和对象运行时动态生成。
1
2
3
4
5
6
7
8
9
|
final class MyLambda implements BinaryOperator<Integer> {
@Override
public Integer apply(Integer a, Integer b) {
return lambda$main$0(a, b);
}
}
private static Integer lambda$main$0(Integer a, Integer b) {
return a + b;
}
|