什么是函数式编程?
百度百科: 函数式编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算为该语言最重要的基础。而且,λ演算的函数可以接受函数作为输入参数和输出返回值。
讲人话: 函数式编程一种编程范式,允许使用一种表达式(lambda表达式)来表示一个函数
Java语言中函数式编程,通过Java8版本提供的函数式接口规范来实现,它指的是有且只有一个未实现的方法的接口,一般通过FunctionalInterface这个注解来表明某个接口是一个函数式接口(非必须,如果标注了该注解,编译器检查接口是否符合函数式接口规范,如出现两个及以上数量的普通方法,编译器会报错)。函数式接口是Java支持函数式编程的基础。java8及以上版本才支持。
使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出。
现在我们要定义一个Consumer对象,传统的方式是这样定义的:
Consumer c = new Consumer() { @Override public void accept(Object o) { System.out.println(o); } };
而在Java8中,针对函数式编程接口,可以这样定义:
Consumer c = (o) -> { System.out.println(o); };
上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。
如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。
因此,=后面的函数体我们就可以看成是accept函数的实现。
Consumer c = (o) -> System.out.println(o);
然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印,因此可以简化成以下写法:
Consumer c = System.out::println;
它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。
到这一步就可以感受到函数式编程的强大能力。
通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。
而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!
下面对Java中的几个预先定义的函数式接口及其经常使用的类进行分析学习。在java.util.function包下提供了很多函数式接口,这里挑几个常用的进行讲解。
Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法;
除accept方法,它还包含有andThen这个方法;
其定义如下:
default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; }
可见这个方法就是指定在调用当前Consumer后是否还要调用其它的Consumer;
使用示例:
public static void consumerTest() { Consumer f = System.out::println; Consumer f2 = n -> System.out.println(n + "-F2"); //执行完F后再执行F2的Accept方法 f.andThen(f2).accept("test"); //连续执行F的Accept方法 f.andThen(f).andThen(f).andThen(f).accept("test1"); }
Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出;
除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;
/** * Function测试 */ public static void functionTest() { Function<Integer, Integer> f = s -> s++; Function<Integer, Integer> g = s -> s * 2; /** * 下面表示在执行F时,先执行G,并且执行F时使用G的输出当作输入。 * 相当于以下代码: * Integer a = g.apply(1); * System.out.println(f.apply(a)); */ System.out.println(f.compose(g).apply(1)); /** * 表示执行F的Apply后使用其返回的值当作输入再执行G的Apply; * 相当于以下代码 * Integer a = f.apply(1); * System.out.println(g.apply(a)); */ System.out.println(f.andThen(g).apply(1)); /** * identity方法会返回一个不进行任何处理的Function,即输出与输入值相等; */ System.out.println(Function.identity().apply("a")); }
Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。
它的使用方法示例如下:
/** * Predicate测试 */ private static void predicateTest() { Predicate<String> p = o -> o.equals("test"); Predicate<String> g = o -> o.startsWith("t"); /** * negate: 用于对原来的Predicate做取反处理; * 如当调用p.test("test")为True时,调用p.negate().test("test")就会是False; */ Assert.assertFalse(p.negate().test("test")); /** * and: 针对同一输入值,多个Predicate均返回True时返回True,否则返回False; */ Assert.assertTrue(p.and(g).test("test")); /** * or: 针对同一输入值,多个Predicate只要有一个返回True则返回True,否则返回False */ Assert.assertTrue(p.or(g).test("ta")); }
Stream可以对多个元素进行一系列的操作,也可以支持对某些操作进行并发处理。Java8中对集合、数组进行了stream的实现,方便开发人员通过stream对集合或数组进行数据操作。
Stream对象的创建途径有以下几种
a. 创建空的Stream对象
Stream stream = Stream.empty();
b. 通过集合类中的stream或者parallelStream方法创建;
Stream<String> stream = Arrays.stream(new String[]{"1", "2", "3"}); List<String> list = Arrays.asList("a", "b", "c", "d"); Stream listStream = list.stream(); //获取串行的Stream对象 Stream parallelListStream = list.parallelStream(); //获取并行的Stream对象
c. 通过Stream中的of方法创建:
Stream s = Stream.of("test"); Stream s1 = Stream.of("a", "b", "c", "d");
d. 通过Stream中的iterate方法创建:
iterate方法有两个不同参数的方法:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
其中第一个方法将会返回一个无限有序值的Stream对象:它的第一个元素是seed,第二个元素是f.apply(seed); 第N个元素是f.apply(n-1个元素的值);生成无限值的方法实际上与Stream的中间方法类似,在遇到中止方法前一般是不真正的执行的。因此无限值的这个方法一般与limit等方法一起使用,来获取前多少个元素。
当然获取前多少个元素也可以使用第二个方法。
第二个方法与第一个方法生成元素的方式类似,不同的是它返回的是一个有限值的Stream;中止条件是由hasNext来断定的。
第二种方法的使用示例如下:
/** * 本示例表示从1开始组装一个序列,第一个是1,第二个是1+1即2,第三个是2+1即3..,直接10时中止; * 也可简化成以下形式: * Stream.iterate(1, * n -> n <= 10, * n -> n+1).forEach(System.out::println); * 写成以下方式是为简化理解 */ Stream.iterate(1, new Predicate<Integer>() { @Override public boolean test(Integer integer) { return integer <= 10; } }, new UnaryOperator<Integer>() { @Override public Integer apply(Integer integer) { return integer+1; } }).forEach(System.out::println);
e. 通过Stream中的generate方法创建
与iterate中创建无限元素的Stream类似,不过它的每个元素与前一元素无关,且生成的是一个无序的队列。也就是说每一个元素都可以随机生成。因此一般用来创建常量的Stream以及随机的Stream等。
示例如下:
/** * 随机生成10个Double元素的Stream并将其打印 */ Stream.generate(new Supplier<Double>() { @Override public Double get() { return Math.random(); } }).limit(10).forEach(System.out::println); //上述写法可以简化成以下写法: Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);
Stream对象提供多个非常有用的方法,这些方法可以分成两类:
中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。
终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。
下面就几个比较常用的方法举例说明其用法:
用于对Stream中的元素进行过滤,返回一个过滤后的Stream
其方法定义如下
Stream<T> filter(Predicate<? super T> predicate);
使用示例:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa"); //查找所有包含t的元素并进行打印 s.filter(n -> n.contains("t")).forEach(System.out::println);
元素一对一转换。
它接收一个Funcation参数,用其对Stream中的所有元素进行处理,返回的Stream对象中的元素为Function对原元素处理后的结果
其方法定义如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
示例,假设我们要将一个String类型的Stream对象中的每个元素添加相同的后缀.txt,如a变成a.txt,其写法如下:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa"); s.map(n -> n.concat(".txt")).forEach(System.out::println);
元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回;
方法定义如下:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
示例,假设要对一个String类型的Stream进行处理,将每一个元素的拆分成单个字母,并打印:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa"); s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);