jdk8新特性之函数式编程&## Lambda 表达式

简介

什么特性

面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。

现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。

  • 编写出更容易阅读的代码。

  • 处理批量数据的并行类库。

  • 在写回调函数和事件处理程序时,程序员不必再纠缠于匿名内部类的冗繁和可读性。

  • Java 8 还让集合类可以拥有一些额外的方法: default 方法。

什么是函数式编程

每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函
数,函数对一个值进行处理,映射成另一个值。

Lambda 表达式

一种紧凑的、传递行为的方式

lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码

我们可以通过Lambda表达式实现接口的抽象方法,比之前简直简洁太多。之前的方法是写一个实现该接口的类,并写出实现体。具体的不写了,我们看看通过Lambda表达式的代码有多简洁。

Lambda表达式与匿名类对比

设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部
类还是不够简便。为了调用一行重要的逻辑代码,不得不加上 4 行冗繁的样板代码。若把
样板代码用其他颜色区分开来,就可一目了然:

button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("button clicked");
}
});

在 Java 8 中,上述代码可以写成一个Lambda 表达式

button.addActionListener(event -> System.out.println("button clicked"));

和传入一个实现某接口的对象不同,我们传入了一段代码块——一个没有名字的函数。
event 是参数名,和上面匿名内部类示例中的是同一个参数。 -> 将参数和 Lambda 表达式
的主体分开,而主体是用户点击按钮时会运行的一些代码。

和使用匿名内部类的另一处不同在于声明 event 参数的方式。使用匿名内部类时需要显式
地声明参数类型 ActionEvent event ,而在 Lambda 表达式中无需指定类型,程序依然可以
编译。这是因为 javac 根据程序的上下文( addActionListener 方法的签名)在后台推断出
了参数 event 的类型。这意味着如果参数类型不言而明,则无需显式指定。

注意:

尽管与之前相比,Lambda 表达式中的参数需要的样板代码很少,但是 Java 8
仍然是一种静态类型语言。为了增加可读性并迁就我们的习惯,声明参数时
也可以包括类型信息,而且有时编译器不一定能根据上下文推断出参数的
类型!

Lambda表达式基本形式

lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • **可选类型声明:**不需要声明参数类型,编译器可以统一识别参数值。
  • **可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • **可选的大括号:**如果主体包含了一个语句,就不需要使用大括号。
  • **可选的返回关键字:**如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

口诀:左右遇一省括号,左侧推断类型省

注:当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念

变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

int num = 1;  
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

既成事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,
而不是变量。在例 2-6 中, name 就是一个既成事实上的 final 变量。

String name = getUserName();
button.addActionListener(event -> System.out.println("hi " + name));

若匿名类的方法中使用外部变量,该变量必须是final。Java8中提供的lambda表达式虽然可以引用非final变量,但该变量在既成事实上必须是final,即必须是终态变量,否则,编译器会报错。Lambda表达式引用的是值,而不是变量。这也解释了为什么Lambda表达式被称为闭包。

String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi " + name));

函数接口

Lambda表达式本身的类型为函数接口。使用只有一个方法的接口来表示某特定方法并反复使用。

函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型。接口中唯一方法的命名并不重要,只要方法签名和Lambda表达式的类型匹配即可,参数命名可以更有意义。

Lambda表达式和匿名内部类在JVM上的区别:

匿名内部类是一个不需要显示指定类名的类,编译器会为该类取名,匿名类生成.class文件。

Lambda表达式不会产生新的类,被封装成主类的一个私有方法,并通过invokedynamic指令进行调用。

为了引入Lambda表达式,java8新增了java.util.function包来包含常用的函数接口。

类型推断

Java 7 中程序员可省略构造函数的泛型类型,Java 8 更进一步,程序员可省略 Lambda 表达
式中的所有参数类型。

Javac只是根据Lambda表达式上下文来推断参数的正确类型,程序依然要经过类型检查来保证运行的安全性,只是不显式声明类型。

Predicate是用来判断真假的函数接口。

public interface Predicate<T>{
  boolean test(T t);
}

下面是使用Predicate的例子。

Predicate<Integer> atLeast5 = x -> x>5;

x>5是表达式的主体,返回值就是表达式主体的值。

Lambda表达式是一个匿名方法,将行为像数据一样传递。

函数式接口

函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 lambda 表达式。

如定义了一个函数式接口如下:

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):

GreetingService greetService1 = message -> System.out.println("Hello " + message);

函数式接口可以对现有的函数友好地支持 lambda。

java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:

Predicate接口

输入一个参数,并返回一个Boolean值,其中内置许多用于逻辑判断的默认方法:

    @Test
    public void predicateTest() {
        Predicate<Integer> predicate = (s) -> s > 0;
        boolean test = predicate.test(2);
        System.out.println("该数字大于0:" + test);

        test = predicate.test(0);
        System.out.println("该数字大于0:" + test);

        test = predicate.negate().test(0);
        System.out.println("该数字小于等于0:" + test);
    }

Function接口

接收一个参数,返回单一的结果,默认的方法(andThen)可将多个函数串在一起,形成复合Funtion(有输入,有输出)结果:

    @Test
    public  void functionTest() {

        Function<Integer, Integer> add = (i) -> {
            System.out.println("初始值:" + i);
            return i+1;
        };
        Function<Integer, Integer> power = add.andThen((i) -> {
            System.out.println("第一次运算:" + i);
            return i * i;
        });

        Integer res = power.apply(2);
        System.out.println("第二次运算:" + res);
    }

Supplier接口

返回一个给定类型的结果,与Function不同的是,Supplier不需要接受参数(供应者,有输出无输入):

    @Test
    public void supplierTest() {
        Supplier<String> supplier = () -> "有输出,无输入。";
        String s = supplier.get();
        System.out.println(s);
    }

Consumer接口

代表了在单一的输入参数上需要进行的操作。和Function不同的是,Consumer没有返回值(消费者,有输入,无输出):

    @Test
    public void consumerTest() {
        Consumer<Integer> add = (p) -> {
            System.out.println("old value:" + p);
            p = p + 1;
            System.out.println("new value:" + p);
        };
        add.accept(1);
    }

以上四个接口的用法代表了java.util.function包中四种类型,理解这四个函数式接口之后,其他的接口也就容易理解了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕布辕门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值