函数式编程
函数式编程
NuyoahLambda表达式
基本格式
1 | (参数列表)->{代码} |
一个接口并且只有一个方法要重写才能使用lambda表达式
-
例一
创建线程的时候匿名内部类写法
1
2
3
4
5
6new Thread(new Runable() {
public void run() {
System.out.println("哈哈哈")
}
}).start();Lambda表达式
1
2
3new Thread(()->{
System.out.println("哈哈哈")
}).start(); -
例二
方法
1
2
3
4
5public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a, b)
}调用
1
2
3
4
5
6
7
8public static void main(String[] args){
int i = calculateNum(new IntBinaryOperator() {
public int applyAsInt(int left, int right) {
return left + right;
}
});
}lambda表达式
1
2
3public static void main(String[] args){
int i = calculateNum((int left, int right) -> {return left + right;});
}
省略规则
参数类型可以省略
方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
方法只有一个参数时小括号可以省略
以上这些规则都记不住也可以省略不记
Stream流
Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们
对集合或数组操作。
我们可以调用getAuthors方法获取到作家的集合。现在需要打印所有年龄小作家的名字,并且要注意去重。
1 | public static void main(String[] args) { |
lambda表达式
1 | public static void main(String[] args) { |
常用操作
创建流
-
单列集合:集合对象.stream()
1
2List<Author> atuhor = getAuthors();
Stream<Author> stream = authors.stream(); -
数组 Arrays.stream(数组) 或者使用stream.of来创建
1
2
3Integer[] arr = {1,2,3,4,5}
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr); -
双列集合:转换成单列集合之后在创建
1
2
3
4
5
6Map<String, Integer> map = new HashMap<>();
map.put("姓名1", 19);
map.put("姓名2", 19);
map.put("姓名3", 19);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作
filter
将流中的元素进行过滤
1 | private static void test04() { |
map
对流中的元素进行计算和转换
打印所有作家的名字
1 | List<Author> authors = getAuthors(); |
distinct
将流中的重复元素去除
默认调用的是Object.equals方法,我们需要重写,如果不重写,则默认比较地址值
打印作家,将重名的去掉
1 | List<Author> authors = getAuthors(); |
sorted
对流中的元素进行排序
对作家的年龄进行排序,且没有重复元素
空参sorted()
1 | List<Author> authors = getAuthors(); |
1 | public class Author implements Comparable<Auhtor> { |
传参sorted()
1 | List<Author> authors = getAuthors(); |
limit
设置流的最大长度,超出的部分将被抛弃
对作家年龄进行降序排序并去重,找到年龄最大的两个作家的姓名
1 | List<Author> authors = getAuthors(); |
skip
跳过流中的前n个元素,返回剩下的元素
打印除了年龄最大的作家姓名,并按照年龄降序
1 | List<Author> authors = getAuthors(); |
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
打印所有书籍的名字,要求对重复的元素进行去重
错误方法:
1 | List<Author> authors = getAuthors(); |
正确方法
1 | List<Author> authors = getAuthors(); |
打印所有数据的分类,要求对分类进行去重,不能出现这种格式:哲学,爱情
1 | List<Author> authors = getAuthors(); |
终结操作
使用Stream流的时候一定要有终结操作,否则Stream流不会执行
forEach
打印除了年龄最大的作家姓名,并按照年龄降序
1 | List<Author> authors = getAuthors(); |
count
获取当前流中的元素个数
1 | List<Author> authors = getAuthors(); |
max&min
求流中的最值
分别获取这些华家的所出书糖的最高分和最低分并打印
1 | List<Author> authors = getAuthors(); |
collect
将当前流返回成一个集合
获取一个存放所有作者名字的list集合
1 | List<Author> authors = getAuthors(); |
获取一个存放所有书字的set集合
1 | List<Author> authors = getAuthors(); |
获取一个Map集合key:书名, value:List< Book >
1 | List<Author> authors = getAuthors(); |
查找与匹配
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
1 | List<Author> authors = getAuthors(); |
allMatch
可以用来判断所有符合匹配条件的元素,结果为boolean类型
1 | List<Author> authors = getAuthors(); |
noneMatch
可以用来判断是否都不符合匹配条件的元素,结果为boolean类型
1 | List<Author> authors = getAuthors(); |
findAny-
…List authors = getAuthors();
Optional
.filter(author -> author.getAge()>18)
.findAny();
optionalAuhtor.ifPresent(author -> System.out.println(author.getName()));
findFrist
1 | List<Author> authors = getAuthors(); |
reduce归并
对流中的数据按照你指定的计算方式计算出一个结果。 (缩减操作)
reduce的作用是把stream中的元案给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元亲和在初始化值
的基础上进行计算,计算结果再和后面的元素计算。
两个参数内部计算方式为:
1 | T result = identity; // 先给result附上初始值 |
使用reduce求所有作者的年龄和
1 | List<Author> authors = getAuthors(); |
求年龄的最大值
1 | List<Author> authors = getAuthors(); |
一个参数的重载形式
1 | boolean foundAny = false; |
求年龄的最小值
1 | List<Author> authors = getAuthors(); |
注意事项
惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
Optional
用来检测空指针异常
所以在IDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常.
并且在很多函数式编程相关的API中也都用到了Optional,如果不会使用Optional也会对函数式编程的学习造成影响。
创建对象
一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题。
1 | Author author = getAuthor(); |
1 | Author author = getAuthor(); |
可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下getAuthor方法,让其的返回值就是封装好的Optional的话,我们
在使用时就会方便很多
1 | public static Optional<Author> getAuthorOptional() { |
安全使用值
使用ifPresent方法来使用值
1 | Author author = getAuthor(); |
安全获取值
如果我们想获取值自己进行处理可以使用get方法获取,但是不推荐。因为当optional内部的数据为空的时候会出现异常。
-
orElseGet
设置返回默认值,如果没有获取到值,则返回默认值,如果获取到值,则返回获取到的值
1
2Optional<Author> authorOprtional = Optional.ofNullable(getAuthor());
Author author1 = authorOptional.orElseGet(() -> new Author()); // 如果authorOptional里面没有值,则返回自己创建的对象 -
orElseThrow
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
1
2
3
4
5
6
7Optional<Author> authorOprtional = Optional.ofNullable(getAuthor());
try{
Auhtor author = authorOptional.orElseThrow((Supplier<Throwable>)() -> new RunTimeException("author为空"));
System.out.println(author.getName());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
过滤
我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象
1 | Optional<Author> authorOption = Optional.ofNullable(getAuthor()); |
判断
使用ifPresent()
数据转换
Optional还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全
例如我们想获取作家的书籍集合。
1 | Optional<Author> authorOption = Optional.ofNullable(getAuthor()); |
函数式接口
只有一个抽象方法的接口我们称之为函数接口
JDK的函数式接口都加上了@Functionallnterfae 注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式
接口
常见的函数式接口
-
Consumer接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参教进行消费
1
2
3
4
public interface Consumer<T> {
void accept(T t);
} -
function计算转换成接口
对传入的参数进行计算,并把结果返回
1
2
3
4
public interface Function<T, R> {
R accept(T t);
} -
Supplier生产型接口
可以在其中创建对象,把创建好的对象返回
1
2
3
4
public interface Supplier<T> {
T get();
}
常用的默认方法
-
and
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件
例如
打印作家年龄大于17并且姓名的长度大于1的作家
1
2
3
4
5
6
7
8
9
10
11
12
13List<Author> authros = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
public boolean test(Author author) {
return author.getAge()>17;
}
}).and(new Predicate<Author>() {
public boolean test(Author author) {
return author.getName().length<2;
}
}).forEach(author -> System.out.println(author)); -
or
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用来拼接两个判断条件.例如:
打印作家中年龄大于17或者姓名的长度小于2的作家
1
2
3
4
5
6
7
8
9
10
11
12
13List<Author> authros = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
public boolean test(Author author) {
return author.getAge()>17;
}
}).or(new Predicate<Author>() {
public boolean test(Author author) {
return author.getName().length<2;
}
}).forEach(author -> System.out.println(author)); -
negate
Predicate接口中的方法。negate方法相当于是在判断添加前面加了个!表示取反
例如:
打印作家中年龄不大于17的作家。
1
2
3
4
5
6
7
8List<Author> authros = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
public boolean test(Author author) {
return author.getAge()>17;
}
}.negate()).forEach(author -> System.out.println(author));
方法引用
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
推荐用法
我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现
方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
基本格式
类名或者对象名::方法名
语法详情
引用静态方法
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
例如
1 | authorStream.map(new Function<Author, Integer>() { |
简化后
1 | authorStream.map(author -> author.getAge()).map(String::valueOf) |
引用对象的实例方法
格式
1 | 对象名::方法名 |
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
1 | StringBuilder sb = new StringBuilder(); |
简化后
1 | authorStream.map(author -> author.getAge()).forEach(sb::append) |
引用类的实例方法
高级用法
基本数据类型优化
我们之前用到的很多stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们极作的是整数小数,但是实际用的都是他们的包装类。IDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像
使用基本数据类型一样方便,但界你一定要知道装植和拆箱肯定易要消耗时间的。虽然这个时间消耗很下,但易在大量的数据不断的重复
装箱拆箱的时候,你就不能无视这个时间损耗了。
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法.
例如: mapTolnt,mapToLong,mapToDouble,flatMapTolnt,flatMapToDouble等.
并行流
当流中有大量元秦时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码
实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识,而如果我们使用stream的话,我们只需要修改一个方法的调
用就可以使用并行流来帮我们实现,从而提高效率。
1 | private static void test04() { |