函数式编程快速入门
面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。
核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
1. Lambda表达式
lambda表达式仅能放入如下代码:
- 预定义使用了
@Functional
注释的函数式接口,自带一个抽象函数的方法 - SAM(Single Abstract Method 单个抽象方法)类型。
这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。
示例
若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。
类似的,如果一个方法接受声明于 java.util.function
包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。
1.1 基本格式
{params}->{code}
[示例]
使用匿名内部类创建线程
new Thread(new Runnable() {
pubic void run() {
// code
}
}).start();
使用lambda表达式创建线程
new Thread(()->{
// code
}).start();
[需求1] 现有方法定义如下,其中IntBinaryOperator
是一个接口
public static int calculateNum(IntBinaryOperator operator) {
int a = 1;
int b = 2;
return operator.applyAsInt(a, b);
}
IntBinaryOperator
是一个函数式接口并且自带一个抽象方法,因此可以使用lambda表达式。
匿名内部类实现
int var = calculateNum(new IntBinaryOperator() {
public int applyAsInt(int left, int right) {
return left + right;
}
});
lambda表达式实现
int var = calculateNum((int left, int right)->{
return left + right;
});
继续优化
int var = calculateNum((left, right)->eft + right);
[需求2] 现有方法定义如下,IntPredicate
是一个接口
public static void printNum(IntPredicate predicate) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int i : arr) {
if (predicate.test(i)) {
System.out.println(i);
}
}
}
IntPredicate
是一个函数式接口并且自带一个抽象方法,因此可以使用lambda表达式。
使用匿名内部类
printNum(new IntPredicate() {
public boolean test(int value) {
return value % 2 == 0;
}
});
使用lambda表达式
printNum((int value)->{
return value % 2 == 0;
});
[需求3] 现有方法定义如下
public static <R> R typeConver(Function<String, R> function) {
String str="12345";
R result = function.apply(str);
return result;
}
使用匿名内部类
Integer res = typeConver(new Function<String, Integer> {
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
使用lambda表达式
Integer res = typeConver((String s)->{
return Integer.valueOf(s);
});
[需求4] 现有方法定义如下
public static void foreachArr(IntConsumer consumer) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int i : arr) {
consumer.accept(i);
}
}
使用匿名内部类
foreachArr(new IntConsumer() {
public void accept(int value) {
System.out.println(value);
}
});
使用lambda表达式
foreachArr((int value)->{
System.out.println(value);
});
1.2 省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
2. Stream流
Java8的Stream使用函数式编程模式,可以用来对集合或数组进行链状流式的操作
2.1 快速入门
[需求1] 现在需要打印所有年龄小于18的作家的名字,并且去重
使用Stream+匿名内部类
List<Author> authors = getAuthors();
authors.stream() //将集合转换成流
.distinct()
.filter(new Predicate<Author>() {
public boolean test(Author author) {
return author.getAge()<18;
}
})
.forEach(new Consumer<Author>() {
public void accept(Author author) {
System.out.println(author.getName);
}
});
使用Stream+lambda表达式
List<Author> authors = getAuthors();
authors.stream() // 将集合转换成流
.distinct() // 去重
.filter(author->author.getAge()<18) // 过滤
.forEach(author- >System.out.println(author.getName())); // 遍历
2.2 常用操作
2.2.1 创建流
单列集合:集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author>stream = authors.stream();
数组:Arrays.stream(数组)
或使用Stream.of()
创建
Integer[] arr = {1, 2, 3};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
双列集合:转换成单列集合再创建
Map<String, Integer> map = new HashMap<>();
map.put("Cyan", 100);
map.put("Jack", 200);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
2.2.2 中间操作
filter
作用
对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中
匿名内部类写法
authors.stream().
.filter(new Predicate<Author> {
public boolean test(Author author) {
return author.getName().length() > 1;
}
})
.forEach(new Consumer<Author>() {
public void accept(Author author) {
System.out.println(author.getName());
}
});
lambda表达式写法
authors.stream().filter(author->author.getName().length>1).forEach(author->System.out.println(author.getName()));
map
作用
把流中的元素进行计算或转换
匿名内部类写法
authors.stream().
.map(new Function<Author,String>() {
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
public void accept(String s) {
System.out.println(s);
}
});
lambda表达式写法
authors.stream().map(author->author.getName()).forEach(s->System.out.println(s));
distinct
作用
流中去重[distinct是依赖Object的equals方法判断是否是相同对象的,要重写equals方法,lombok中使用@EqualsAndHashCode注解重写]
lambda表达式写法
authors.stream().distinct().forEach(author->System.out.println(author.getName()));
sorted
作用
对流中的元素进行排序
lambda表达式写法
1.调用无参sorted
方法
重写Comparable
接口
public class Author implements Comparable<Author> {
...
public int compare(Author o) {
return this.getAge() - o.getAge();
}
}
authors.stream().distinct().sort().forEach(author->System.out.println(author.getAge()));
调用空参的sorted()
方法,需要流中元素实现Comparable
接口
2.调用有参sorted
方法
authors.stream().distinct().sort((o1,o2)->o1.getAge()-o2.getAge()).forEach(author->System.out.println(author.getAge()));
limit
设置流的最大长度,超出的部分将被抛弃
authors.stream().distinct().sorted().limit(2).forEach(author->System.out.println(author.getName()));
skip
跳过流中前n个元素,返回剩下的元素
authors.stream().distinct().sorted.skip(1).forEach(author->System.out.println(author.getName()));
flatMap
map
只能只能把一个对象转换成另一个对象来作为流中的元素,而flatMap
可以把一个对象转换成多个对象作为流中的元素。
public class Author implements Comparable<Author> {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
public int compare(Author o) {
return this.getAge() - o.getAge();
}
}
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
public Stream<?> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(new Consumer<Book>() {
public void accept(Book book) {
System.out.println(book.getName());
}
});
authors.stream().flatMap(author->author.getBooks().stream()).distinct().forEach(book->System.out.println(book.getName()));
[需求] 打印所有数据的分类,对分类进行去重
authos.stream().flatMap(author->author.getName().stream()).distinct().flatMap(book->Arrays.stream(book.getCategory().split(","))).distinct().forEach(category->System.out.println(category));
2.2.3 终结操作
forEach
对流中的元素进行遍历操作,通过传入的参数去指定对遍历到的元素进行具体操作
authors.stream().map(author->author.getName()).distinct().forEach(name->System.out.print(name));
count
可以获取当前流中元素的个数
long cnt = authors.stream().flatMap(author->getBooks().stream()).distinct().count();
max&min
统计流中的最值
Optional<Integer> max=authors.stream().flatMap(author->author.Books()).map(book->book.getScore()).max((score1-score2)->score1-score2);
Optional<Integer> min=authors.stream().flatMap(author->author.Books()).map(book->book.getScore()).min((score1-score2)->score1-score2);
collect
将流转换成集合
Set
List<String> list = authors.stream().map(author->author.getName()).collect(Collectors.toList());
List
Set<Book> books = authors.stream().flatMap(author->author.getBooks.stream()),collect(Collectors.toSet());
Map
1.匿名内部类
authors.stream()
.collect(Collectors.toMap(new Function<Author,String>(){
public String apply(Author author) {
return author.getName();
}
}, new Function<Author,List<Book>>(){
public List<Book> apply(Author author) {
return author.getBooks();
}
}));
2.lambda表达式
authors.stream().distinct().collect(Collectors.toMap(author->author.getName(),author.getBooks()));
查找与匹配
AnyMatch
判断是否有任意符合匹配条件的元素,结果为boolean类型
authors.stream().AnyMatch(author->author.getAge()>29);
allMatch
判断是否都符合匹配条件,结果为boolean类型
authors.stream().allMatch(author->author.getAge()>29)
noneMatch
判断留着元素都不符合匹配条件的,结果为boolean类型
authors.stream().noneMatch(author->author.getAge()>29)
findAny
获取流中任意一个元素[保证不了是否为第一个元素]
Optional<Author> author = authors.stream().filter(author->author.getAge()>52).findAny();
author.ifPresent(author->System.out.print(author.getName()));
findFirst
获取流中第一个元素
Optional<Author> first = authors.stream().sorted((o1,o2)->o1.getAge()-o2.getAge()).findFirst();
first.ifPresent(author->System.out.print(author.getName()));
reduce归并
对流中的数据按照你制定的计算方式计算出一个结果,即把stream中的元素组合起来,传入一个初始值,按照计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面元素计算
reduce单个参数的重载形式内部计算逻辑
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();
将流中第一个元素作为变量初始化值
reduce两个参数的重载形式内部计算逻辑
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
其中,identity是通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是通过方法参数确定的
使用reduce对年龄求和
authors.stream()
.map(author->author.getAge())
.reduce(0, new BinaryOperator<Integer>(){
public Integer apply(Integer result, Integer element) {
return result + element;
}
});
Integer sum = authors.stream().map(author->author.getAge()).reduce((result,element) ->result + element);
使用reduce求年龄最大值
authors.stream().map(author->author.getAge())
.reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>(){
public Integer apply(Integer result, Integer element) {
return result < element ? element : result;
}
})
Integer max = authors.stream().map(author->author.getAge()).reduce(Integer.MIN_VALUE,(result,element)->result < element ? element : result);
使用reduce求年龄最小值
两个参数的重载实现
authors.stream().map(author->author.getAge())
.reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() {
public Integer apply(Integer result, Integer element) {
return result > element ? element : result;
}
})
authors.stream().map(author->author.getAge()).reduce(Integer.MAX_VALUE,(result, element)->result > element ? element : result);
单个参数的重载实现
Optional<Integer> min = authors.stream().map(author->author.getAge())
.reduce(new BinaryOperator<Integer>() {
public Integer apply(Integer result, Integer element) {
return result > element ? element : result;
}
});
min.ifPresent(age->System.out.println(age))
2.3 注意事项
- 没有终结操作,中间操作是不会执行的
- 一旦一个流对象经过终结操作后,该流不能再被使用
- 在流中对数据进行操作,但不能影响到原来集合的元素
3 Optional
3.1 使用
3.1.1 创建对象
Optional类似包装类,将具体的数据封装Optional对象内部,然后使用Optional内部封装号的方法操作数据,从而避免空指针异常。
1.一般使用Optional的静态方法ofNullable
把数据封装成Optional对象,无论传入的参数是否为null都不会出现问题
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
2.如果确定对象不是空可以使用Optional的静态方法of
将数据封装成Optional对象
Optional<Author> authorOptional = Optional.of(author);
使用of传入的参数必须不为空
3.如果方法的返回值为Optional类型,经判断发现某次计算得到的返回值为null,需要把null封装成Optional对象返回,可以使用Optional的静态方法empty
进行封装
Optional.empty();
3.1.2 安全消费值
如果获取到Optional对象需要使用数据,使用其ifPresent
方法消费其中的值
该方法判断其内封装的数据是否为空,不空时执行具体的消费代码,具有安全性
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(author->System.out.println(author.getName()));
3.1.2 安全获取值
期望安全获取值,推荐使用Optional提供的以下方法:
-
orElseGet
获取数据并且设置数据为空时的默认值,如果数据不为空就能获取到该数据,如果为空根据传入的参数创建对象作为默认返回值信息
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); Author author = authorOptional.erElseGet(()->new Author());
-
orElseThrow
获取数据,如果数据不为空就能获取到该数据,如果为空则根据传入的参数来创建异常抛出
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); Author author = authorOptional.erElseGet((Supplier<Throwable>) ()-> new RuntimeException("author is null"));
3.1.3 过滤
使用filter
方法进行数据过滤,原本有数据,但不符合要求,会变成无数据的Optional对象
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(author->author.getAge()>100).ifPresent(author->System.out.println(author.getName()));
3.1.4 判断
使用ifPresent
方法进行是否存在数据的判断,如果为空返回值为false,如果不为空,返回值为true
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent()) {
System.out.println(authorOptional.get().getName());
}
3.1.5 数据转换
Option提供map
方法对数据进行转换,并且转换得到的数据还是被Optional包装好的,保证使用安全
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
Optional<List<Book>> books = authorOptional.map(author->author.getBooks());
books.ifPresent(new Consumer<List<Book>>() {
public void accept(List<Book> books) {
books.forEach(book->System.out.println(book.getName()));
}
});
4 函数式接口
4.1 概述
仅含有一个抽象方法的接口称之为函数式接口,JDK中的函数式接口都加上@FunctionalInterface
注解标识
4.2 常见函数式接口
-
Consumer消费接口
根据其中抽象方法的参数列表和返回值类型,在方法中传入参数进行消费
@FunctionalInterface public interface Consumer<T> { void accept(T t); ... }
-
Function计算转换接口
根据其中抽象方法的参数列表和返回值,在方法传入的参数计算或转换,将结果返回
@FunctionalInterface public interface Function<T, R> { R apply(T t); ... }
-
Predicate判断接口
根据其中抽象方法的参数列表和返回值类型,在方法中对传入的参数条件判断,返回判断结果
@FunctionalInterface public interface Predicate<T> { boolean test(T t); ... }
-
Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型,在方法中创建对象,把创建好的对象返回
@FunctionalInterface public interface Function<T> { T get(); ... }
4.3 函数式接口常用默认方法
-
and
在使用Predicate接口可能需要判断条件的拼接,而and方法相当于使用&&来拼接两个判断条件
// 打作家中年龄大于17且姓名的长度大于1的作家 authors.stream().filter(author->author.getAge()>17) .and(new Predicate<Author>() { public void test() { return author.getName().length()>1; } }).forEach(author->System.out.print(author));
-
or
在使用Predicate接口可能需要判断条件的拼接,而or方法相当于使用||拼接两个判断条件
// 打印作家中年龄大于17或者姓名的长度小于2的作家 authors.stream().filter(author->author.getAge()>17).or(author->author.getName().length()<2).forEach(author->author.getName());
-
negate
Predicate接口中的方法,该方法相当于在判断添加!表示取反
5 方法引用
使用lambda表达式时,如果方法中只有一个方法的调用话(包括构造方法),可以使用方法引用进一步简化代码
基本格式
类名或者对象名::
方法名
5.1 引用类的静态方法
基本语法
类名::方法名
使用前提
如果在重写方法时,方法体中只有一行代码,并且改代码调用某个类的静态方法,并且把重写的抽象方法中所有的参数都按照顺序传入静态方法中,此时可以引用类的静态方法
authors.stream().map(author->author.getAge()).map(age->String.valueOf(age));
如果重写的方法没有参数,调用的方法也是没有参数的也符合以上规则
优化代码
authors.stream().map(author->author.getAge()).map(String::valueOf);
5.2 引用对象的实例方法
基本语法
对象名::方法名
使用前提
如果在重写方法时,方法体中只有一行代码,并且该代码是调用某对象的成员方法,并且要把重写的抽象方法中所有的参数按照顺序传入该成员方法中,使用引用对象的实例方法
StringBuilder sb = new StringBuilder();
authors.stream().map(author->author.getName()).forEach(name->sb.append(name));
优化代码
StringBuilder sb = new StringBuilder();
authors.stream().map(author->author.getName()).forEach(sb::append);
5.3 引用类的实例方法
基本语法
类名::方法名
使用前提
如果在重写方法时,方法体中只有一行代码,并且该代码调用第一个参数的成员方法,并且把重写的抽象方法中剩余的所有参数都按照顺序传入到该成员方法中,可以使用类的实例方法。
subAuthorNam("Cyan",new UseString() {
public String use(String str, int start, int len) {
return str.substring(start, len);
}
})
代码优化
subAuthorNam("Cyan", String::substring);
5.4 构造器引用
如果方法中的一行代码是构造器的话可以使用构造器引用
基本语法
类名::new
使用前提
如果在重写方法时,方法体中只有一行代码,并且该代码调用某类的构造器方法,并且把重写的抽象方法中所有的参数按照顺序传入构造器中,可以使用引用构造器
authors.stream().map(author->author.getName()).map(name->new StringBuilder(name)).map(sb->sb.append("Cyan").toString()).forEach(str->System.out.println(str));
优化代码
authors.stream().map(author->author.getName()).map(StringBuilder::new).map(sb->sb.append("Cyan").toString()).forEach(str->System.out.println(str));
6 高级用法
6.1 基本数据类型优化
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。
所以为对大数据量时装箱拆箱的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。
mapToInt
mapToLong
mapToDouble
flatMapToInt
flatMapToDouble
6.2 并行流
使用parallel
方法将串行流转换成并行流,也可以通过parallelStream
直接获取并行流对象
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
Integer sum = stream.parallel()
.peek(new Consumer<Integer>() {
public void accept(Integer num) {
System.out.println(num+Thread.currentThread().getName());
}
})
.filter(num->num>5)
.reduce((result.ele)->result+ele)
.get();