1_函数式编程和lambda表达式

1⃣️ 概念

  1. 函数式编程是种编程方式,它将电脑运算视为函数的计算。将业务逻辑细化,抽象,封装成一个个功能函数,并借助语言自带的高阶函数api,将整个业务流程转化为函数之间的相互调用,这就是函数式编程。

2⃣️ 为什么要使用函数式编程

  1. 与命令式编程相比,函数式编程更加强调做什么,在函数式编程中我们只需要关注做什么样的事情,不需要关注具体的实现细节;这么说可能有一点抽象,接下来我们根据例子说明一下:查找一个数组中的最小值;
两种编程方式实现对比
package lambda;

import java.util.stream.IntStream;

/**
 * Created by wb-yxk397023 on 2018/7/18.
 */
public class MinDemo {
    public static void main(String[] args){
        int[] nums = {33,55,-55,90,-666,90};

        // 命令式编程
        int min = Integer.MAX_VALUE;
        for (int i : nums){
            if (min > i){
                min = I;
            }
        }
        System.out.println("命令式编程实现:" + min);

        // 函数式编程实现
        int asInt = IntStream.of(nums).min().getAsInt();
        System.out.println("函数式编程实现:" + asInt);
    }
}
对比结果

在这里例子中我们使用命令式编程,需要用程序告诉计算机我们每一步需要做什么最终才能实现我们想要的结果;如果我们使用函数式编程我们只需要关注我们最终要实现的结果即可,而且从代码量的角度来看,也简洁了不少;

3⃣️ 初识Lambda

  1. Lambda简介
    Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
    Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
    使用 Lambda 表达式可以使代码变的更加简洁紧凑。
  1. 语法
    (parameters) -> expression

    (parameters) ->{ statements; }
  1. 特征
    可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
  1. Lambda 表达式实例
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}
执行以上代码输出的结果
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
  1. 需要注意的点
    Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
    Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

4⃣️ JDK8的接口新特性

  1. @FunctionalInterface
    标注为@FunctionalInterface的接口被称为函数式接口,该接口只能有一个自定义方法,但是可以包括从object类继承而来的方法。如果一个接口只有一个方法,则编译器会认为这就是一个函数式接口。
  1. 需要注意的点
    1⃣️ 该注解只能标记在”有且仅有一个抽象方法”的接口上。
    2⃣️ JDK8接口中的静态方法和默认方法,都不算是抽象方法。
    3⃣️ 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
    4⃣️ 该注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
    5⃣️ 在一个接口中定义两个自定义的方法,就会产生Invalid ‘@FunctionalInterface’ annotation; FunctionalInterfaceTest is not a functional interface错误.
  1. 使用时的一些建议
    1⃣️ 一个接口中只定义一个方法,也叫做单一责任制;
    2⃣️ 通过接口的多继承来实现相对比较复杂的功能;

5⃣️JDK8中新增的函数接口

Predicate接口
Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):

Predicate接口代码演示
Function 接口
Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):
Function接口代码演示
Supplier 接口
Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数
Supplier接口代码演示
Consumer 接口
Consumer 接口表示执行在单个参数上的操作。
Consumer接口代码演示
Comparator 接口
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
Comparator接口代码演示
Optional 接口
Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:
Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
Optional 接口代码演示
Stream 接口
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
首先看看Stream是怎么用,首先创建实例代码的用到的数据List:
Stream接口代码演示
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。
Filter 过滤
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。
Filter过滤接口代码演示
Sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
Sort排序接口代码演示1
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
Sort排序接口代码演示2
⚠️ 其实这也是函数式编程的一个好处:不会改变对象状态,每次都会创建一个新对象。
Map 映射
中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
Map映射接口代码演示
Match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
Match匹配接口代码演示
Count 计数
计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。
Count计数接口代码演示
Reduce 规约
这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:
Reduce规约接口代码演示
并行Streams
前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。下面的例子展示了是如何通过并行Stream来提升性能:
首先我们创建一个没有重复元素的大表:
并行Streams接口代码演示1
然后我们计算一下排序这个Stream要耗时多久,
串行排序:
并行Streams接口代码演示2
// 串行耗时: 899 ms
并行排序:
并行Streams接口代码演示3
// 并行排序耗时: 472 ms
上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。
Map
前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。
Map接口代码演示1
以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。
下面的例子展示了map上的其他有用的函数:
Map接口代码演示2
接下来展示如何在Map里删除一个键值全都匹配的项:
Map接口代码演示3
另外一个有用的方法:
Map接口代码演示4
对Map的元素做合并也变得很容易了:
Map接口代码演示5
Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

6⃣️ 方法引用

  1. 好处
    使用方法引用可以让我们的代码更加的简洁;

2.方法引用的种类
1⃣️静态方法引用:ClassName::methodName;对于静态方法引用,我们需要在类名和方法名之间加入::分隔符,例如:Integer::sum;
2⃣️实例上的实例方法引用:instanceReference::methodName;对于具体对象上的实例方法引用,我们则需要在对象名和方法名之间加入分隔符;
3⃣️超类上的实例方法引用:super::methodName;
4⃣️类型上的实例方法引用:ClassName::methodName;
5⃣️构造方法引用:Class::new;
6⃣️数组构造方法引用:TypeName[]::new;

7⃣️ 类型推断

package lambda;

@FunctionalInterface
interface IMath {
    int add(int x, int y);
}

@FunctionalInterface
interface IMath2 {
    int sub(int x, int y);
}

public class TypeDemo {

    public static void main(String[] args) {
        // 变量类型定义
        IMath lambda = (x, y) -> x + y;

        // 数组里
        IMath[] lambdas = { (x, y) -> x + y };

        // 强转
        Object lambda2 = (IMath) (x, y) -> x + y;
        
        // 通过返回类型
        IMath createLambda = createLambda();
        
        TypeDemo demo = new TypeDemo();
        // 当有二义性的时候,使用强转对应的接口解决
        demo.test( (IMath2)(x, y) -> x + y);
    }
    
    public void test(IMath math) {}
    
    public void test(IMath2 math) {}
    
    public static IMath createLambda() {
        return  (x, y) -> x + y;
    }
}

8⃣️ 变量引用

package lambda;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * 变量引用
 */
public class VarDemo {

    public static void main(String[] args) {
        final List<String> list = new ArrayList<>();
        Consumer<String> consumer = s -> System.out.println(s + list);
        consumer.accept("1211");
    }
}

这里需要说明一下,为什么变量要定义为final的,在1.8中如果不定义为final的也是可以用的,只要后边不改变变量的值即可;因为我们在穿变量的时候实际上传的是值,为了避免结果的二义性变量需要定义为final的;

9⃣️ 级联表达式和柯里化

  1. 什么是级联表达式?

    有多个箭头的Lambd表达式就是级联表达式,例如
    级联表达式
  1. 什么是柯里化?

    把多个参数的函数转换成只有一个参数的函数,例如
    柯里化
  1. 柯里化的意义。

    1⃣️ 把函数标准化;把函数标准话以后,每个函数都只有一个参数,调用起来就非常的灵活,还可以执行其他的特性比如把函数组合起来;例如js中就有这样的操作:var a = add(2)(3)(4);
    柯里化的意义1

    2⃣️ 高阶函数:返回函数的函数
    柯里化的意义2
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容