文章目录
函数式编程思想
函数式编程思想类似于我们数学中的函数,它主要关注的是对数据进行了什么操作。
优点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于“并发编程”
[Lambda表达式]
Lambda是JDK8中一个语法,它可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是关注我们对数据进行了什么操作。
基本规则
-
基本格式:
(参数列表) -> {代码} -
省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时,小括号可以省略
- 以上规则都可以省略不记,使用idea的快捷键 alt+enter
总结:
使用Lambda表达式替换匿名内部类,只需要保留匿名内部类的方法参数和方法体,然后在中间加一个箭头即可。
方法调用
如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
基本格式
类名或者对象名::方法名
语法详解(了解)
1. 引用类的静态方法
格式:类名::方法名
使用前提:
- 方法体中只有一行代码
- 这行代码是调用了某个类的静态方法
- 要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中
2. 引用对象的实例方法
格式:对象名::方法名
使用前提:
- 方法体中只有一行代码
- 这行代码是调用了某个对象的成员方法
- 要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中
3. 引用类的实例方法
格式:类名::方法名
使用前提
- 方法体中只有一行代码
- 这行代码是调用了第一个参数的成员方法
- 要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中
4. 构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
格式:类名::new
使用前提:
- 方法体中只有一行代码
- 这行代码是调用了某个类的构造方法
- 要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中
[Stream流]
JDK8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作,可以更方便的让我们对集合或数组操作。
常用操作
创建流
- 单列集合:
集合对象.stream()
- 数组:
Arrays.stream(数组)
或者使用Stream.of
来创建 - 双列集合:转换成单列集合后再创建
中间操作
filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
map
可以把对流中的元素进行计算或转换。
distinct
可以去除流中的重复元素。
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。
sorted
可以对流中的元素进行排序。
注意:
如果调用空参的sorted()方法,需要流中的元素是实现了Comparable。
如果是有参的sorted()方法,可以直接实现Comparable接口,匿名内部类或lambda表达式。
limit
可以设置流的最大长度,超出的部分将被抛弃。
skip
跳过流中的前n个元素,返回剩下的元素
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
终结操作
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作(无返回值)。
count
可以用来获取当前流中元素的个数(返回值为 long)。
max&min
可以用来或者流中的最值(返回值为 Optional)。
collect
把当前流转换成一个集合。
-
List集合
.collect(Collectors.toList());
-
Set集合
.collect(Collectors.toSet());
-
Map集合
.collect(Collectors.toMap(两个参数,分别为key和val)
查找与匹配
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。
noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素(返回值为 Optional)。
findFirst
获取流中的第一个元素(返回值为 Optional)。
reduce归并
对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
作用:把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。
-
两个参数的重载形式内部的计算(返回值为 T ):
T result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;
- 其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
-
一个参数的重载形式内部的计算(返回值为 Optional ):
boolean foundAny = false; T result = null; for (T element : this stream) { if (!foundAny) { foundAny = true; result = element; } else result = accumulator.apply(result, element); } return foundAny ? Optional.of(result) : Optional.empty();
注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的)
高级用法
基本数据类型优化
Stream还提供了很多专门针对基本数据类型的方法。不容易造成自动装箱和自动拆箱的时间损耗
例如:
mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。
并行流
并行流就是把任务分配给多个线程去完成,提高操作的效率。
-
parallel 方法:将串行流转换成并行流
t.stream .parallel()
-
parallelStream 方法:直接获取并行流对象
t.parallelStream()
[Optional]
在JDK8中引入了Optional,使用Optional可以避免空指针异常。
Mybatis从3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
创建对象
- ofNullable 静态方法(常用):无论传入的参数是否为null都不会出现问题。
Optional<T> authorOptional = Optional.ofNullable(t);
-
of 静态方法:对象不为空返回对象,否则报错。
Optional<T> authorOptional = Optional.of(t);
-
empty 静态方法:如果一个方法的返回值类型是Optional类型。而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。
Optional.empty();
获取值(不建议)
get方法:当Optional内部的数据为空的时候会出现异常。
* 安全获取值
-
orElseGet 方法:获取数据并且设置数据为空时的默认值。
- 如果数据不为空就能获取到该数据。
- 如果为空则根据你传入的参数来创建对象作为默认值返回。
Optional<T> authorOptional = Optional.ofNullable(t); T t = authorOptional.orElseGet(() -> new T());
-
orElseThrow 方法:获取数据。
- 如果数据不为空就能获取到该数据。
- 如果为空则根据你传入的参数来创建异常抛出。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); try { Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author为空")); System.out.println(author.getName()); } catch (Throwable throwable) { throwable.printStackTrace(); }
过滤
filter 方法:对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
Optional<T> authorOptional = Optional.ofNullable(t);
authorOptional.filter(过滤条件,可使用匿名内部类 或 lambda表达式)
.ifPresent(author -> System.out.println(t));
判断
-
isPresent 方法:判断数据的是否存在。
- 如果为空,返回值为false。
- 如果不为空,返回值为true。
Optional<T> authorOptional = Optional.ofNullable(t); if (authorOptional.isPresent()) { System.out.println(authorOptional.get(); }
-
-
ifPresent 方法(推荐):判断数据的是否存在。
-
如果为空,返回值为空。
-
如果不为空,直接返回数据。
Optional<T> authorOptional = Optional.ofNullable(t); authorOptional.ifPresent(author -> System.out.println(t));
-
数据转换
- map方法:参考Stream的map中间操作
[函数式接口]
只有一个抽象方法的接口我们称之为函数接口。JDK的函数式接口都加上了 @FunctionalInterface 注解进行标识。
常见函数式接口
-
Consumer 消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * 对给定参数执行此操作。 * * @param t the input argument * T - 输入参数 */ void accept(T t); }
-
Function 计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
public interface Function<T, R> { /** * Applies this function to the given argument. * 将此函数应用于给定参数。 * * @param t the function argument * T - 函数参数 * @return the function result * 返回值:函数结果 */ R apply(T t); }
-
Predicate 判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果
@FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * 根据给定参数计算此谓词。 * * @param t the input argument * T - 输入参数 * @return {@code true} if the input argument matches the predicate,otherwise {@code false} * 如果输入参数与谓词匹配,则为True,否则为false */ boolean test(T t); }
-
Supplier 生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * 获取一个结果。 * * @return a result * 返回值:结果 */ T get(); }
常用的默认方法
-
and
Predicate接口中的方法,进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件
-
or
Predicate接口中的方法,进行判断条件的拼接。而or方法相当于是使用||来拼接两个判断条件。
-
negate
Predicate接口中的方法,在判断添加前面加了个! 表示取反。