介绍
函数式编程最直接的表现就是将行为作为数据传递。使代码表达能力得到提升!在java8中主要有三个核心接口
- 函数接口 (Function)
- 流(Stream)
- 聚合器(Collector)
函数接口
- 函数接口是行为的抽象
- 函数接口是数据转换器
在java.util.funciton包中定义了定义了四个最基础的函数接口
- Supplier<T> 数据提供器,可以提供 T 类型对象;无参的构造器,提供了 get 方法
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Supplier 类似工厂方法 example
public class SupplierTest {
public static void main(String[] args) {
Supplier<User> userSupplier = () -> new User();
System.out.println(userSupplier.get());
System.out.println(userSupplier.get());
}
}
输出
lamdal.User@27bc2616
lamdal.User@3941a79c
可以看到每一次get都会产生一个新的对象
- Function<T, R> 数据转换器,接收一个 T 类型的对象,返回一个 R类型的对象; 单参数单返回值的行为接口;提供了 apply, compose, andThen, identity 方法
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
Funciton example
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest = new FunctionTest();
System.out.println(functionTest.compute1(1, i -> i * 2, i -> i * i));
System.out.println(functionTest.compute2(1, i -> i * 2, i -> i * i));
}
private int compute1(int i, Function<Integer, Integer> before, Function<Integer, Integer> after) {
return after.compose(before).apply(i);
}
private int compute2(int i, Function<Integer, Integer> before, Function<Integer, Integer> after) {
return before.andThen(after).apply(i);
}
}
输出结果
4
4
- Consumer<T>: 数据消费器,接收一个T类型的对象,无返回值,通常用于设置T对象的值; 单参数无返回值的行为接口;提供了 accept, andThen 方法
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
- Predicate<T>: 条件测试器,接收一个T类型的对象,返回布尔值,通常用于传递条件函数; 单参数布尔值的条件性接口。提供了 test (条件测试) ,and(与),negate(非) and-or-negate(与或非),isEqual(等) 方法
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
其中, compose, andThen, and,or,negate用来组合函数接口而得到更强大的函数接口。
Predicate and Consumer test
public class PredicateAndConsumerTest {
public static void main(String[] args) {
Student a = new Student("a", 90);
Student b = new Student("b", 80);
PredicateAndConsumerTest test = new PredicateAndConsumerTest();
test.updateStudent(a, student -> student.getScore() >= 90, student -> student.setGrade("A"));
test.updateStudent(b, student -> student.getScore() <= 90, student -> student.setGrade("B"));
a.printGrade();
b.printGrade();
}
private void updateStudent(Student student, Predicate<Student> predicate, Consumer<Student> consumer) {
if (predicate.test(student)) {
consumer.accept(student);
}
}
}
输出
A
B
可以看到Predicate.test()方法进行某些逻辑判断并返回一个boolean值,而Consumer.accept()接受并改变某个对象的内部值
其它的函数接口都是通过这四个扩展而来。
流
流(Stream)是Java8对函数式编程的重要支撑。大部分函数式工具都围绕Stream展开。
流主要是四类接口:
- 流到流之间的转换:比如 filter(过滤), map(映射转换), mapTo[Int|Long|Double] (到原子类型流的转换), flatMap(高维结构平铺),flatMapTo[Int|Long|Double], sorted(排序),distinct(不重复值),peek(执行某种操作,流不变,可用于调试),limit(限制到指定元素数量), skip(跳过若干元素) ;
- 流到终值的转换: 比如toArray(转为数组),reduce(推导结果),collect(聚合结果),min(最小值), max(最大值), count (元素个数), anyMatch (任一匹配), allMatch(所有都匹配),noneMatch(一个都不匹配),findFirst(选择首元素),findAny(任选一元素) ;
- 直接遍历: forEach (不保序遍历,比如并行流), forEachOrdered(保序遍历) ;
- 构造流: empty (构造空流),of (单个元素的流及多元素顺序流),iterate (无限长度的有序顺序流),generate (将数据提供器转换成无限非有序的顺序流), concat (流的连接), Builder (用于构造流的Builder对象)
除了 Stream 本身自带的生成Stream 的方法,数组和容器及StreamSupport都有转换为流的方法。比如 Arrays.stream , [List|Set|Collection].[stream|parallelStream] , StreamSupport.[int|long|double|]stream;
流的类型主要有:Reference(对象流), IntStream (int元素流), LongStream (long元素流), Double (double元素流) ,定义在类 StreamShape 中,主要将操作适配于类型系统。
- 流的创建方式
public class StreamTest {
public static void main(String[] args) {
//流的创建方式
//1.
Stream stream1 = Stream.of("hello","world","hello world");
//2.
String[] array = new String[]{"hello","world"};
Stream stream2 = Stream.of(array);
//3.
Stream stream3 = Arrays.stream(array);
//4.通过结合创建,最常见的方式
List<String> list = Arrays.asList(array);
Stream stream4 = list.stream();
}
}
- 常用方法
forEach方法
List<String> listStr = new ArrayList<>();
listStr.add("hello");
listStr.add("world");
Stream streamStr = listStr.stream();
streamStr.forEach(System.out :: println);
输出结果
hello
world
range方法
IntStream.of(new int[]{1,2,3,4,5,6,7,8});
IntStream.range(2,4).forEach(index -> System.out.print(index + " "));
输出结果
2 3
map reduce方法
List<Integer> listInt = Arrays.asList(1,2,3,4,5);
System.out.print(listInt.stream().map(i -> i * 2).reduce(0,Integer :: sum));
输出结果
30
filter方法
List<Integer> listInt = Arrays.asList(1,2,3,4,5);
listInt.stream().filter(item -> item > 2).forEach(System.out :: println);
输出结果
3
4
5
flatMap方法
将三个数组压平再对每个值求平方
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2),Arrays.asList(3,4,5),Arrays.asList(7,8,9));
stream.flatMap(theList -> theList.stream()).map(item -> item * item).forEach(System.out :: println);
输出结果
1
4
9
16
25
49
64
81
找出所有单词并且去重
List<String> listDis = Arrays.asList("hello welcome", "world hello", "hello world hello", "hello welcome");
List<String> reList = listDis.stream().map(item -> item.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
reList.forEach(System.out::println);
输出结果
hello
welcome
world
等等还有很多应用的方法,如短路,分组等。。。
聚合器(Collector)
stream里有一个collect(Collectorc)方法,这个方法里面接收一个Collector的实例。它是一个可变的汇聚操作,将输入元素累计到一个可变的结果容器中;它会在所有元素都处理完毕后,将累积的结果转换为一个最终的表示(这是一个可选操作)
example
public class CollectTest {
public static void main(String[] args) {
Student a = new Student("a", 95);
Student b = new Student("b", 92);
Student c = new Student("c", 92);
Student d = new Student("d", 89);
List<Student> studentList = Arrays.asList(a, b, c ,d);
// 按照分数分组
Map<Integer, List<Student>> studentMap = studentList.stream().collect(Collectors.groupingBy(Student :: getScore));
System.out.println("按照分数分组:" + studentMap);
// 求分组后的平均分
Map<String, Double> avaMap = studentList.stream().collect(Collectors.groupingBy(Student :: getName, Collectors.averagingDouble(Student :: getScore)));
System.out.println("按照姓名分组后的平均值:" + avaMap);
// 分区
Map<Boolean, List<Student>> parMap = studentList.stream().collect(Collectors.partitioningBy(student -> student.getScore() > 90));
System.out.println("按照90分分区:" + parMap);
}
}
输出结果
按照分数分组:{89=[name:d, score:89], 92=[name:b, score:92, name:c, score:92], 95=[name:a, score:95]}
按照姓名分组后的平均值:{a=95.0, b=92.0, c=92.0, d=89.0}
按照90分分区:{false=[name:d, score:89], true=[name:a, score:95, name:b, score:92, name:c, score:92]}
我们也可以自定义Collector
Collector接口
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
其中这里的泛型所表示的含义是:
T:表示流中每个元素的类型。
A:表示中间结果容器的类型。
R:表示最终返回的结果类型。
Collector中还定义了一个枚举类Characteristics,有三个枚举值,
Characteristics.CONCURRENT:表示中间结果只有一个,即使在并行流的情况下。所以只有在并行流且收集器不具备CONCURRENT特性时,combiner方法返回的lambda表达式才会执行(中间结果容器只有一个就无需合并)。
Characteristics.UNORDER:表示流中的元素无序。
Characteristics.IDENTITY_FINISH:表示中间结果容器类型与最终结果类型一致,此时finiser方法不会被调用。
下面实现一个将set转为map的一个Collector
public class MyCollector<T> implements Collector<T, Set<T>, Map<T, T>> {
@Override
public Supplier<Set<T>> supplier() {
return HashSet<T>::new;
}
@Override
public BiConsumer<Set<T>, T> accumulator() {
return Set<T>::add;
}
@Override
public BinaryOperator<Set<T>> combiner() {
return (set1, set2)->{
set1.addAll(set2);
return set1;
};
}
@Override
public Function<Set<T>, Map<T, T>> finisher() {
return (set)->{
HashMap<T, T> map = new HashMap();
set.stream().forEach((item)->map.put(item, item));
return map;
};
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));
}
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "welcome");
HashSet<String> set = new HashSet<>();
set.addAll(list);
Map<String, String> map = set.stream().collect(new MyCollector<>());
System.out.println(map);
}
}
输出结果
{world=world, hello=hello, welcome=welcome}