简介 java函数式编程可以简单概括:
基本函数 + lambda表达式 + 方法引用 + stream API = java函数式编程
本文介绍java.util.function包下常用的函数式接口及其实战
基本函数 完整文档地址
接口
描述
Predicate
表示一个参数的谓词(布尔值函数)。
Consumer
表示接受单个输入参数并且不返回结果的操作。
Function
表示接受一个参数并产生结果的函数。
Supplier
代表结果供应商。
UnaryOperator
表示对单个操作数产生与其操作数相同类型的结果的操作。
BiFunction
表示接受两个参数并产生结果的函数。
BinaryOperator
表示对同一类型的两个操作数的操作,产生与操作数相同类型的结果。
IntFunction
表示一个接受int值参数并产生结果的函数。
DoubleConsumer
表示接受单个 double值参数的操作,不返回任何结果。
DoubleFunction
表示接受双值参数并产生结果的函数。
BiConsumer
表示接受两个输入参数并且不返回结果的操作。
以上是在函数式编程中的基本函数模型,我们大可以将其与数学函数做关联:y = x +1,我们仅仅需要关注这个函数的输入输出即可。 以predicte函数举例:该函数输入一个表达式,输出一个布尔类型 一元函数function:输入一个类型参数输出为另一个类型,当然这两个类型可以是相同的,当相同时也可以使用unaryOperator来代替。具体下面有给出实际场景的代码断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 public class FunctionDemo { public static void main (String[] args) { binaryOperator(); } public static void predicate () { Predicate<Integer> predicate = i -> i > 0 ; IntPredicate intPredicate = i -> i > 0 ; System.out.print(predicate.test(6 )); System.out.print(intPredicate.test(-1 )); } public static void consumer () { Consumer<String> consumer = s -> System.out.println(s); consumer.accept("我是一个消费者" ); } public static void function () { Function<Integer,String> function = x -> "数字是:" + x; System.out.println(function.apply(88 )); } public static void supplier () { Supplier<String> supplier = () -> "我是一个提供者" ; System.out.println(supplier.get()); } public static void unaryOperator () { UnaryOperator<Integer> unaryOperator = x -> ++x; System.out.println(unaryOperator.apply(1 )); } public static void biFunction () { BiFunction<Integer,Double,Double> biFunction = (x,y) -> { ++x; ++y; return x+y; }; System.out.println(biFunction.apply(1 ,2.3 )); } public static void binaryOperator () { IntBinaryOperator intBinaryOperator = (x,y) -> x + y; System.out.println(intBinaryOperator.applyAsInt(2 ,3 )); } Map<Function<Model, String>, BiConsumer<Model, String>> getFunctionMap() { HashMap<Function<Model, String>, BiConsumer<Model, String>> functionMap = Maps.newHashMap(); functionMap.put(Model::getJul, Model::setJul); functionMap.put(Model::getAug, Model::setAug); functionMap.put(Model::getSep, Model::setSep); functionMap.put(Model::getOct, Model::setOct); functionMap.put(Model::getNov, Model::setNov); functionMap.put(Model::getDec, Model::setDec); functionMap.put(Model::getJan, Model::setJan); functionMap.put(Model::getFeb, Model::setFeb); functionMap.put(Model::getMar, Model::setMar); functionMap.put(Model::getApr, Model::setApr); functionMap.put(Model::getMay, Model::setMay); functionMap.put(Model::getJun, Model::setJun); return functionMap; } public void tranfer () { Map<Function<OverheadCostUnionCategoryVO, String>, BiConsumer<OverheadCostUnionCategoryVO, String>> functionMap = getFunctionMap(); functionMap.forEach((k, v) -> doSomething(k, v)); } void doSomething (Function<OverheadCostUnionCategoryVO, String> k, BiConsumer<OverheadCostUnionCategoryVO, String>> setMethod) { setMethod.accept(child, stripTrailingZeros(total)); } }
lambda表达式 lambda表达式组成 形如以下:
(o1,o2) -> Integer.compare(o1,o2)
-> 被称为lambda操作符或箭头操作符
左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)
右边:lambda体 (其实就是重写的抽象方法的方法体)
lambda表达式使用 1.无参无返回值 1 2 3 4 5 6 7 8 9 public void test01 () { Runnable ri = new Runnable() { @Override public void run () { System.out.println("好好学习,天天向上" ); } }; ri.run(); }
1 2 3 4 public void test02 () { Runnable r2 = ()-> {System.out.println("学会lambda,快乐每一天" );}; r2.run(); }
2. 需要一个参数但无返回值 1 2 3 4 5 6 7 8 9 public void test05 () { Consumer<String> consumer = new Consumer<String>() { @Override public void accept (String s) { System.out.println(s); } }; consumer.accept("这一路上走走停停" ); }
1 2 3 4 public void test06 () { Consumer<String> consumer = (String s)->{System.out.println(s);}; consumer.accept("留下少年漂流的痕迹" ); }
3.数据类型可以省略,由编译器去推断出,称为“类型推断” 1 2 3 4 5 6 7 8 9 public void test05 () { Consumer<String> consumer = new Consumer<String>() { @Override public void accept (String s) { System.out.println(s); } }; consumer.accept("这一路上走走停停" ); }
1 2 3 4 public void test06 () { Consumer<String> consumer = (s)->{System.out.println(s);}; consumer.accept("留下少年漂流的痕迹" ); }
4.lambda若只需要一个参数时,参数的小括号可以省略 1 2 3 4 5 6 7 8 9 public void test05 () { Consumer<String> consumer = new Consumer<String>() { @Override public void accept (String s) { System.out.println(s); } }; consumer.accept("这一路上走走停停" ); }
1 2 3 4 public void test06 () { Consumer<String> consumer = s->{System.out.println(s);}; consumer.accept("留下少年漂流的痕迹" ); }
5.lambda需要两个或以上的参数,多条执行语句,并且可以有返回值 1 2 3 4 5 6 7 8 9 10 11 12 public void test03 () { Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare (Integer o1, Integer o2) { System.out.println(o1); System.out.println(02 ); return Integer.compare(o1,o2); } }; int compare = comparator.compare(12 , 21 ); System.out.println(compare); }
1 2 3 4 5 6 7 8 public void test04 () { Comparator<Integer> comparator = (o1,o2)-> { System.out.println(o1); System.out.println(02 ); return Integer.compare(o1,o2);}; int compare = comparator.compare(32 ,23 ); System.out.println(compare); }
6.当lambda体只有一条语句时,return与大括号若有,都可以省略 1 2 3 4 5 6 7 8 9 10 public void test03 () { Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare (Integer o1, Integer o2) { return Integer.compare(o1,o2); } }; int compare = comparator.compare(12 , 21 ); System.out.println(compare); }
1 2 3 4 5 public void test04 () { Comparator<Integer> comparator = (o1,o2)-> Integer.compare(o1,o2); int compare = comparator.compare(32 ,23 ); System.out.println(compare); }
方法引用 我们可以直接使用两个冒号::来调用方法
静态方法引用
非静态 实例方法引用
非静态 类方法引用
构造函数方法引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class MethodReferenceDemo { public static void main (String[] args) { callMethod2(); } public static void consumer () { Consumer<String> consumer = System.out::println; consumer.accept("我是一个消费者" ); } private static void callStaticMethod () { Consumer<Dog> consumer = Dog::bark; consumer.accept(new Dog()); } private static void callMethod () { Dog dog = new Dog(); Function<Integer,Integer> function = dog::eat; System.out.println("还剩[" + function.apply(3 ) + "]斤狗粮" ); } private static void callMethodByClass () { BiFunction<Dog,Integer,Integer> biFunction = Dog::eat; System.out.println("还剩[" + biFunction.apply(new Dog(),4 ) + "]斤狗粮" ); } private static void callConstructorMethod () { Supplier<Dog> supplier = Dog::new ; System.out.println("new 了一个对象" + supplier.get()); } private static void callMethod2 () { Dog dog = new Dog(); Function<Integer,Integer> function = dog::eat; dog = null ; System.out.println("还剩[" + function.apply(3 ) + "]斤狗粮" ); } }
Stream流API Stream API是Java 8中加入的一套新的API,主要用于处理集合操作。Stream流API是函数式编程的核心所在,它以一种流式编程来对数据进行各种加工运算。形象的来说你可以把它看作工业中的流水线,将原料放入流中经过操作1、操作2…操作N输出一个产品。Stream也是如此它分为创建操作、中间操作、终止操作。业务逻辑清晰简单、代码看上去优雅不少。
流通常是由三个部分组成:
数据源:流的获取,比如list.stream()方法;
中间处理:中间处理是对流元素的一系列处理。比如过滤filter,排序sorted,映射map;
终端处理:终端处理会生成结果,结果可以是任何不是流值。
创建操作
在jdk8中集合数组加入了不少流的方法其中就有直接通过实例或是工具类创建流。如:list.stream(),而数据没有自身API需要借助工具类Arrays来创建。这里通过parallelStream()并行流的模式来创建就可以透明的使用到多线程了。
注:通过阅读源码可以知Stream类与IntStream、LongStream并没有继承关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class CreateStream { public static void main (String[] args) { selfCreate(); } public static void collectionCreate () { List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); Stream<String> parallelStream = list.parallelStream(); } public static void arrayCreate () { Integer[] array = new Integer[5 ]; Stream<Integer> stream = Arrays.stream(array); } public static void numCreate () { IntStream.of(1 ,2 ,3 ); IntStream.rangeClosed(1 ,10 ); new Random().ints().limit(10 ); } public static void selfCreate () { Random random = new Random(); Stream.generate(random::nextInt).limit(20 ); Stream.iterate(2 , (x) -> x*2 ).limit(10 ).forEach(System.out::println); } }
中间操作 中间操作分为有状态操作、无状态操作。无状态操作即该中间操作不依赖与另外的空间来存放临时结果。有状态即需要。这么说还是比较抽象,我们不妨来举个栗子0.0。
比如说:你的排序操作传统我们要进行排序是否需要依赖额外空间来进行大小的比较。去重操作需要额外空间来存放未重复的值。而像是filter只是单纯返回过滤后的结果无需额外空间。
这是一种说法,另一种说法该操作与其他操作,没有依赖关系即为无状态,反正则为有状态。这么说也没错,你看像是order操作不是就要等前面操作都执行完才可以执行吗。后面会提到一点就是Stream的操作模式实际上是每一条数据通过A操作B操作C操作来进行的,而到了中间有有状态操作是,必须停下等所有数据都操作到这一步时一起进行,否则你让他如何进行排序呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class MiddleStream { public static void main (String[] args) { limitSkip(); } public static void mapOrMapToXXX () { String s = "my name is 007" ; Stream.of(s.split(" " )).map(String::length).forEach(System.out::println); System.out.println("-------------" ); Stream.of(s.split(" " )).filter(x -> x.length() > 2 ).mapToDouble(x -> x.length()).forEach(System.out::println); } public static void flatMap () { String s = "my name is 007" ; Stream.of(s.split(" " )).flatMap(x -> x.chars().boxed()).forEach(x -> System.out.println((char )x.intValue())); } public static void peek () { IntStream.of(new int []{ 1 ,2 ,3 ,4 ,5 }).peek(System.out::println).forEach(x->{ }); } public static void distinct () { IntStream.of(new int []{ 1 ,3 ,3 ,4 ,5 }).distinct().forEach(System.out::println); } public static void sort () { IntStream.of(new int []{ 5 ,4 ,3 ,1 ,2 }).sorted().forEach(System.out::println); } public static void limitSkip () { IntStream.of(new int []{ 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 }).skip(2 ).limit(2 ).forEach(System.out::println); } }
这里提到Stream有个特性叫做:惰性求值。什么意思呢?就是当stream没有调用到终止操作时,实际上是不会执行之前的所有过程的。这一点可以在demo工程中有相应的证明方法。 有接触过spark的同学可以将这一特性类比为Transformation和Action。
终止操作 终止操作即流水线的最后一个操作,往往就是返回你所要的产品。 这里分为短路操作和非短路操作:
非短路操作:从流中获取所有数据进行运算返回,有可能返回一个或多个值,但必定运用到了所有数据 短路操作:从流中截取部分数据返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class FinalStream { public static void main (String[] args) { findFirst(); } public static void forEachOrdered () { IntStream.of(new int []{ 1 ,2 ,3 ,4 ,5 ,6 ,7 }).parallel().forEach(System.out::println); } public static void collect () { String s = "hello world!" ; List<String> collect = Stream.of(s.split(" " )).collect(Collectors.toList()); System.out.println(collect); } public static void reduce () { Integer[] intArr = new Integer[]{ 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 }; Optional<Integer> optional = Stream.of(intArr).reduce((x, y) -> x + y); System.out.println(optional.get()); } public static void minMixCount () { Integer[] intArr = new Integer[]{ 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 }; Optional<Integer> optional = Stream.of(intArr).max(Comparator.comparingInt(x -> x)); System.out.println(optional.get()); } public static void findFirst () { Optional<Integer> first = Stream.generate(() -> new Random().nextInt()).findFirst(); System.out.println(first.get()); } }
并行流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class ParallelStream { public static void main (String[] args) { feature4(); } public static void createParallelStream () { IntStream.range(1 , 100 ).parallel().forEach(ParallelStream::printDebug); } private static void feature2 () { IntStream.range(1 , 100 ).parallel().peek(ParallelStream::printDebug).sequential().peek(ParallelStream::printDebug2).count(); } private static void feature3 () { System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism" ,"3" ); IntStream.range(1 , 100 ).parallel().forEach(ParallelStream::printDebug); } private static void feature4 () { ForkJoinPool forkJoinPool = new ForkJoinPool(); forkJoinPool.submit(() -> IntStream.range(1 , 100 ).parallel().forEach(ParallelStream::printDebug)); forkJoinPool.shutdown(); try { Thread.sleep(10000 ); } catch (InterruptedException e) { e.printStackTrace(); } } private static void printDebug (int i) { System.out.println(Thread.currentThread().getName() + "debug:" + i); try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } } private static void printDebug2 (int i) { System.err.println(i); try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
级联表达式与柯里化 简单来说就是将一个复杂表达式拆解为多个简单表达式,比如数学中的: y=5! 可以等价为 y = 1 2 3 4 5
注意:这里涉及一个基础概念数据不变性,说白了就是匿名类中运用到外部变量时,外部变量需要是常量。细心的你会发现在级联表达式中外部变量均为常量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class CurryDemo { public static void main (String[] args) { Function<Integer,Function<Integer,Integer>> fun = x -> y -> x + y; System.out.println(fun.apply(2 ).apply(3 )); Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x -> y -> z -> x + y + z; } }
收集器(终止操作因为内容较多提出来说明) 终止操作中将数据以集合方式回收,可以对数据进行分类统计等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class CollectorsStream { public static void main (String[] args) { List<Student> students = new ArrayList<>(); students.add(new Student("一号" ,7 ,true ,"一年级" )); students.add(new Student("二号" ,8 ,true ,"二年级" )); students.add(new Student("三号" ,8 ,false ,"二年级" )); students.add(new Student("四号" ,9 ,true ,"三年级" )); students.add(new Student("五号" ,7 ,false ,"一年级" )); students.add(new Student("六号" ,8 ,true ,"二年级" )); students.add(new Student("七号" ,10 ,true ,"四年级" )); group(students); } public static void dataToList (List<Student> students) { List<Integer> list = students.stream().map(Student::getAge).collect(Collectors.toList()); System.out.println(list); } public static void summary (List<Student> students) { IntSummaryStatistics collect = students.stream().collect(Collectors.summarizingInt(Student::getAge)); System.out.println(collect); } public static void partitioning (List<Student> students) { Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(x -> x.isGender())); System.out.println(collect); } public static void group (List<Student> students) { Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting())); System.out.println(collect); } }
Stream特性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class FeatureStream { public static void main (String[] args) { feature46(); } public static void feature123 () { Random random = new Random(); Stream<Integer> integerStream = Stream.generate(random::nextInt) .limit(500 ) .peek(x -> System.out.println("peek -> " + x)) .filter(x -> { System.out.println("filter -> " + x);return x > 100000 ;}); integerStream.count(); } public static void feature46 () { Random random = new Random(); Stream<Integer> integerStream = Stream.generate(random::nextInt) .limit(500 ) .peek(x -> System.out.println("peek -> " + x)) .filter(x -> { System.out.println("filter -> " + x);return x > 100000 ;}) .sorted((x,y) -> { System.out.println("sorted -> " + x);return x - y;}) .filter(x -> { System.out.println("filter -> " + x);return x > 100000 ;}) ; integerStream.count(); } public static void feature5 () { Random random = new Random(); Stream<Integer> integerStream = Stream.generate(random::nextInt) .limit(500 ) .peek(x -> print("peek -> " + x)) .filter(x -> { print("filter -> " + x);return x > 100000 ;}) .sorted((x,y) -> { print("sorted -> " + x);return x - y;}) .filter(x -> { print("filter -> " + x);return x > 100000 ;}) .parallel(); integerStream.count(); } private static void print (String x) { System.out.println(Thread.currentThread().getName() + " " + x); } }
This is copyright.