java 函数作为元素_Java笔记18 - 函数式编程

本文介绍了Java中的函数式编程概念,强调函数作为基本运算单位,可以接受和返回函数。讲解了Lambda表达式,包括参数类型和返回值类型的自动推断,以及FunctionalInterface的使用,如Callable和Comparator接口。此外,还展示了如何通过方法引用和构造方法引用传递函数,并探讨了Stream API的特性,如惰性计算、转换和聚合操作。
摘要由CSDN通过智能技术生成

函数式编程

一个大型程序调用若干底层函数, 这些函数又可以调用其他函数

大任务被一层层拆解并执行

函数是面向过程的程序设计的基本单元

Java不支持单独定义函数, 静态方法视为独立的函数

函数式编程归结为面向过程的程序设计

计算机: CPU执行计算代码, 条件判断还有调整等指令代码, 汇编是最贴近计算机的语言

计算: 数学意义上的计算, 越是抽象的计算, 离计算机硬件越近

编程语言约低级, 越解决计算机, 抽象程度低, 执行效率高. C语言

编程语言越高级, 越贴近计算, 抽象程度高, 执行效率低. Python

函数式编程是抽象程度很高的编程范式, 纯粹的函数式编程语言编写的函数没有任何的变量.

因此, 任一函数, 只要输入确定, 输出就是一定确定的. 称为没有副作用

允许函数本身作为参数传入另一个函数, 还可以返回一个函数

lambda基础

java的实例方法和静态方法, 本质上都相当于过程式语言的函数. 例如C函数:

char* strcpy(char* dest, char* src)

只不过java的实例方法隐含地传入了一个this变量

函数式编程把函数作为基本运算单位, 可以接受函数, 可以返回函数.

支持函数式编程的编码风格称为Lambda表达式

Lambda表达式

参数类型和返回值类型都是由编译器自动推断

FunctionalInterface

单方法接口称之为FunctionalInterface, 用注解@FunctionalInterface标记

@FunctionalInterface

public interface Callable {

V call() throws Exception;

}

@FunctionalInterface

public interface Compare {

int compare(T o1, T o2); // 唯一一个抽象方法, 其他都是`default`和`static`方法

boolean equals(Object obj); // 这是`Object`定义的方法

default Comparator reversed() {

return Collections.reverseOrder();

}

default Comparator thenComparing(Comparator super T> other) {

// ...

}

// ...

}

方法引入

除了Lambda表达式, 可以直接传入方法使用

public static void main(String[] args) {

String[] array = new String[] {"Apple", "Orange", "Banana", "Lemon"};

Arrays.sort(array, Main::cmp); // 直接传入静态方法

/**

* String compareTo(String o), 在实际方法调用的时候为

* `public static int compareTo(this, String o)`

*/

Arrays.sort(array, String::compareTo); // 直接传入静态方法

System.out.println(String.join(", ", array));

}

static int cmp(String s1, String s2) {

return s1.compareTo(s2);

}

方法引用: 某个方法签名和接口恰好一致, 就可以直接传入方法引用

签名一致: 方法参数一致, 返回类型相同, 两个方法签名一致

构造方法引用

public class Main {

public static void main(String[] args) {

List names = List.of("Bob", "Alice", "Tim");

// List persons = new ArrayList<>();

// for (String name: names) {

// persons.add(new Person(name));

// }

List persons = names.stream().map(Person::new).collect(Collectors.toList());

System.out.println(persons);

}

static int cmp(String s1, String s2) {

return s1.compareTo(s2);

}

}

class Person {

String name;

public Person(String name) {

this.name = name;

}

public String toString() {

return "Person" + this.name;

}

}

构造方法的引用写法: 类名::new

构造方法隐式的返回this

总结

FunctionalInterface允许传入

接口实现类

Lambda表达式(只需要列出参数名, 其他由编译器推断类型)

符合方法签名的静态实例

符合方法签名的实例方法 (实例类型被看作第一个参数类型)

符合方法签名的构造方法 (实例方法被看作返回类型)

FunctionalInterface不强制继承关系, 不需要方法名称相同, 只要求方法参数(类型和数量)与方法返回类型相同, 即认为方法签名相同

使用Stream

这个Stream代表任意Java对象序列

区别: java.io / java.util.stream

存储: 顺序读写的byte或char / 顺序输出的任意Java对象实例

用途: 序列化至文件或网络 / 内存计算或者业务逻辑

List用来操作一组已存在的Java对象, Stream输出的元素可能没有预先存储再内存中, 而是实时计算出来的

区别: java.util.List / java.util.stream

元素: 已分配并存储在内存中 / 可能未分配, 实时计算

用途: 操作一组存在的Java对象 / 惰性计算

总结:

可以"存储"有限或者无限个元素, 可能全部存储在内存, 也有根据需要实时计算出来的

一个Stream可以轻易转化为另一个Stream, 不会修改原Stream本身

惰性计算的特定: 一个Stream转另一个Stream时, 只存储了转换规则, 没有任何计算

真正的计算通常发生在最后结果的获取

创建Stream

Stream.of()

创建Stream最简单的方法, 直接使用Stream.of(), 传入可变参数即创建一个能确定输出确定元素的Stream

Stream stream = Stream.of("A", "B", "C", "D");

// forEach()相当于内部循环调用

// 可以传入符合Consumer接口的 void accept(T t)方法引用

stream.forEach(System.out::println);

基于数组或Collection

Stream stream1 = Arrays.stream(new String[] {"A", "B", "C"});

Stream stream2 = List.of("X", "Y", "Z").stream();

stream1.forEach(System.out::println);

stream2.forEach(System.out::println);

数组使用Arrays.stream()方法

Collection直接使用stream()方法即可

上述方法都是现有序列变成stream, 元素是固定的

基于Supplier

使用Stream.generate()方法, 传入一个Supplier对象

Stream会不断调用Supplier.get()方法来不断产生下一个元素

Stream保存的是算法, 可以表示无限队列

public class Main {

public static void main(String[] args) {

Stream natual = Stream.generate(new NatualSupplier());

// 注意: 无限序列必须先编程有限序列再打印

natual.limit(20).forEach(System.out::println);

}

}

class NatualSupplier implements Supplier {

int n = 0;

public Integer get() {

n++;

return n;

}

}

无限循环直接调用forEach()或者count()求值, 会进入死循环

其他方法

创建Stream的第三种方法是通过一些API接口, 直接获得Stream

// 文件读取

try (Stream lines = Files.lines(Paths.get("/path/to/file.txt"))) {

// 每个元素, 表示一行内容

}

// 正则

Pattern p = Pattern.compile("\\s+");

Stream s = p.splitAsStream("The quick brow fox jumps over the lazy dog");

s.forEach(System.out::println);

基本类型

泛型不支持基本类型, 无法进行Stream, 会编译错误.

提供IntStream, LongStream, DoubleStream, 使用基本类型的Stream

使用方法和泛型Stream, 并无大碍.

目的是: 为了避免使用Stream的运行效率低

IntStream is = Arrays.stream(new int[] {1, 2, 4});

LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);

使用map

将一个Stream转换成另一个Stream

map操作, 把一个Stream的每一个元素一一对应到应用了目标函数的结果上

参数接收一个Function对象, 并定义一个apply(), 负责把一个T类型转换成R类型

对任何Java对象都有效

public class Main {

public static void main(String[] args) {

List.of("Apple ", " pear", "ORANGE", "BanaNa ")

.stream()

.map(String::trim) // 去除空格

.map(String::toLowerCase) // 变成小写

.forEach(System.out::println); // 打印

}

}

String[] array = new String[] { " 2019-12-31 ", "2020 - 01-09 ", "2020- 05 - 01 ", "2022 - 02 - 01",

" 2025-01 -01" };

// 请使用map把String[]转换为LocalDate并打印:

Arrays.stream(array)

.map(n -> n.replace(" ", ""))

.map(LocalDate::parse)

.forEach(System.out::println);

使用filter

Stream的转换方法, 对每个元素一一测试, 过滤不满足条件的元素

接受Predicate方法, 定义一个test()方法, 判断元素是否符合条件

可以应用与任何Java对象

public class Main {

public static void main(String[] args) {

Stream.generate(new LocalDateSupplier())

.limit(31)

.filter(ldt -> ldt.getDayOfWeek() == DayOfWeek.SATURDAY || ldt.getDayOfWeek() == DayOfWeek.SUNDAY)

.forEach(System.out::println);

}

}

class LocalDateSupplier implements Supplier {

LocalDate start = LocalDate.of(2020, 1, 1);

int n = -1;

@Override

public LocalDate get() {

n++;

return start.plusDays(n);

}

}

使用reduce

reduce是一个聚合方法, 可以把Stream的所有元素按照函数聚合成一个结果

传入BinaryOperator接口, 定义了一个apply()方法, 负责把上次累加的结果和本地的元素进行运算. 并返回累加的结果.

如果去掉初始值, 并stream为空, 会出现返回Optional对象的情况, 所以我们需要进一步进行判断

public static void main(String[] args) {

// 按行读取配置文件:

List props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");

Map map = props.stream()

// 把k=v转换为Map[k]=v:

.map(kv -> {

String[] ss = kv.split("\\=", 2);

return Map.of(ss[0], ss[1]);

})

// 把所有聚合到一个Map

.reduce(new HashMap(), (m, kv) -> {

m.putAll(kv);

return m;

});

// 打印结果

map.forEach((k, v) -> {

System.out.println(k + " = " + v);

});

}

输出集合

Stream操作分成两种

转换操作: 并不会触发任何计算

聚合操作: 真正的计算从这里开始, 因为要获取具体元素

public class Main {

public static void main (String[] args) {

Stream s1 = Stream.generate(new NatualSupplier());

Stream s2 = s1.map(n -> n * n);

Stream s3 = s2.map(n -> n - 1);

Stream s4 = s3.limit(10);

Long s5 = s4.reduce((long) 0, (acc, n) -> acc + n);

System.out.println(s5);

}

}

class NatualSupplier implements Supplier {

long n = 0;

public Long get() {

System.out.println("调用get()");

n++;

return n;

}

}

s1 -> s3的过程不会有任何计算, 只保留计算过程

s4的聚合开始真正的获取元素, 一直从s1开始计算

不进行s5操作的话, 不会调用get()方法

输出为List

Stream stream = Stream.of("Apple", "", null, "Pear", " ", "Orange");

List list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());

System.out.println(list);

Stream 的每个元素收集到List方法是调用collect()方法, 并传入Collectors.toList()对象.

使用collect(Collectors.toSet())可以把Stream每个元素收集到Set中

输出为数组

Stream stream = Stream.of("Apple", "", null, "Pear", " ", "Orange");

List list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());

String[] array = list.stream().toArray(String[]::new);

传入的"构造方法"是String[]::new

输出为Map

需要指定两个映射函数, 分别把元素映射为key和value

Stream stream = Stream.of("APPL:Apple", "MSFT:Microsoft");

Map map = stream.collect(Collectors.toMap(

s -> s.substring(0, s.indexOf(':')),

s -> s.substring(s.indexOf(':') + 1)

));

System.out.println(map);

分组输出

List list = List.of("Apple", "Banana", "Blackberry", "Cocount");

Map> groups = list.stream()

.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));

System.out.println(groups);

使用groupingBy()进行分组输出, 第一个参数, 表示key, 第二个参数表示value

其他操作

排序

List list = List.of("Orange", "apple", "Banana")

.stream()

.sorted(String::compareToIgnoreCase)

.collect(Collectors.toList());

如果需要自定义排序, 传入Comparator即可

去重

List list = List.of("A", "A", "b", "B")

.stream()

.distinct()

.collect(Collectors.toList());

截取

List list = List.of("a", "b", "c", "d", "e")

.stream()

.skip(2) // 跳过多少个元素

.limit(3) // 截取3个

.collect(Collectors.toList());

System.out.println(list);

合并

Stream s1 = List.of("a", "b", "c").stream();

Stream s2 = List.of("d", "c").stream();

Stream s = Stream.concat(s1, s2);

System.out.println(s.collect(Collectors.toList()));

flatMap

flatMap把Stream中的每个元素, 都变成Stream, 然后合并成一个Stream

Stream> s = Stream.of(

Arrays.asList(1, 2, 3),

Arrays.asList(4, 5, 6),

Arrays.asList(7, 8, 9)

);

Stream i = s.flatMap(list -> list.stream());

System.out.println(i.collect(Collectors.toList()));

并行

Stream s = List.of("a", "b", "c", "d").stream();

String[] r = s.parallel() // 此处变为并行处理stream, 提高处理效率

.sorted()

.toArray(String[]::new);

for (String i : r) {

System.out.println(i);

}

System.out.println(r);

其他聚合方法

其他的聚合方法:

count(): 返回stream中元素个数

max(Comparator super T> cp): 找出最大元素

针对IntStream, LongStream和DoubleStream:

sum(): 求和操作

average(): 对所有元素求平均数

测试Stream的元素是否满足条件

boolean allMatch(Predicate super T>): 测试所有元素是否满足测试条件

boolean anyMatch(Predicate super T>: 测试至少有一个元素满足条件

forEach()循环处理每一个元素

s.forEach(System.out::println);

困惑

String[]::new到底是个什么

相当于 size -> new String[size]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值