Java8新增的Lambda表达式

Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简单的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。

5.8.1 Lambda 表达式入门

Command.java

package code;
public interface Command
{
    // 接口里定义的process()方法用于封装“处理行为”
    void process(int[] target);
}

ProcessArray.java

package code;
public class ProcessArray
{
    public void process(int[] target , Command cmd)
    {
        cmd.process(target);
    }
}

CommandTest.java

package code;
public class CommandTest{
    public static void main(String [] args){
        ProcessArray pa = new ProcessArray();
        int [] target = {3,-4,6,4};
        pa.process(target,new Command(){
            public void process(int [] target){
                int sum = 0;
                for(int tmp : target){
                    sum += tmp;
                }
                System.out.println("数组元素的总和是"+ sum);
            }
        });
    }
}

数组元素的总和是9

CommandTest2.java

package code;
public class CommandTest2{
    public static void main(String[] args){
        ProcessArray pa = new ProcessArray();
        int [] array = {3,-4,6,7};
        pa.process(array, (int[] target)->{
            int sum = 0;
            for(int tmp : target){
                sum += tmp;
            }
            System.out.println("数组元素的总和是" + sum);
        });
    }
}

Lambda表达式可以简化匿名内部类对象,不需要new Xxx(){}这种繁琐的代码,不需要指出重写的方法名字,也不需要给出重写的方法的返回值类型———只要给出重写的方法括号以及括号里的形参列表即可。

Lambda表达式的代码块会代替实现抽象方法的方法体,Lambda表达式就相当一个匿名方法。

Lambda表达式由三个部分组成:

  • 形参列表。允许省略形参类型,如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
  • 箭头(->)
  • 代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。Lambda代码块只有一条return语句,甚至可以省略return关键字,Lambda表达式需要返回值,而他的代码块中仅有一条省略了return的语句,Lambda表达式会自动返回这条语句的值。

    示例:LambdaQs.JAVA

package code;
interface Eatable{
    void taste();
}
interface Flyable{
    void fly(String weather);

}
interface Addable{
    int add(int a ,int b);
}
public class LambdaQs{
    public void eat(Eatable e){
        System.out.println(e);
        e.taste();
    }
    public void drive(Flyable f){
        System.out.println("我正在驾驶" + f);
        f.fly("碧空如洗的晴日");
    }
    public void test(Addable add){
        System.out.println("5+3的和为" + add.add(5,3));
    }
    public static void main(String[]args){
        LambdaQs lq = new LambdaQs();
        //Lambda表达式的代码块只有一条语句,可以省略花括号
        lq.eat(()->System.out.println("苹果的味道不错"));
        //Lambda表达式的形参列表只有一个形参,可以省略圆括号
        lq.drive(weather->{
            System.out.println("今天天气是"+ weather);
            System.out.println("直升机飞行平稳");
        });
        //Lambda表达式的代码块只有一条语句,可以省略花括号
        //代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字
        lq.test((a,b)->a+b);
    }
}

code.LambdaQsLambda$1/[email protected]

苹果的味道不错

我正在驾驶code.LambdaQs$$Lambda$2/[email protected]

今天天气是碧空如洗的晴日

直升机飞行平稳

5+3的和为8

5.8.2 Lambda 表达式与函数式接口

Lambda表达式的类型,也被称为“目标类型(target type)”,Lambda表达式的目标类型必须是“函数式接口(functional interface)”。函数式接口代表只包含一个抽象方法的接口,函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。

如果采用 匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用Lambda表达式来创建对象。

java8专门为函数式接口提供了@FunctionalInterface注解,该注解通常放在接口定义前面,该注解对程序功能没有任何作用,它用于告诉编译器执行更严格的检查—检查该接口必须是函数式接口,否则编译器就会报错。

由于Lambda表达式的结果就是别当成对象,因此程序中完全可以使用Lambda表达式进行赋值。

Runnable r =()->{
    for(int i = 0;i<100;i++)
        System.out.println();
};

Lambda表达式有两个限制。

  • Lambda表达式的目标类型必须是明确的函数式接口
  • Lambda表达式只能为函数式接口创建对象,Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口创建对象。
Obj obj = ()->{
    for(int i=0;i<100;i++)
        System.out.println();
};

上面程序直接赋值给Object变量,编译上面程序会报错。

LambdaTest.java:33: 错误: 不兼容的类型: Object 不是函数接口

所以Lambda表达式的目标类型必须是明确的函数式接口,而Lambda表达式的类型为Object,Object并不是函数式接口,因此上面代码报错。

可以用上面三种常见的方式来保证代码正确:

  1. 将Lambda表达式赋值给函数式接口类型的参数传给某个方法
  2. 将Lambda表达式作为函数式接口类型的参数传给某个方法
  3. 使用函数式接口对Lambda表达式进行强制类型转换
Object obj = (Runnable)->{
    for(int i = 0;i<100;i++)
        System.out.println();
}

同样的Lambda表达式的目标类型完全可能是变化的—–唯一的要求是要与目标类型中唯一的抽象方法有相同的形参列表。

@FunctionalInterface
interface FKTest{
    void run();
}
Object obj2 = (FKTest)()->{
    for(int i= 0;i<100;i++)
        System.out.println();
};

Java8在java.util.function包下预定义了大量函数式接口。

  • XxxFunction

    • 包含一个apply()抽象方法,返回一个新值
  • XxxConsumer
    • 包含一个accept()抽象方法,不返回处理结果
  • XxxPredicate
    • 包含一个test()抽象方法,返回一个boolean值
  • XxxSupplier
  • 包含一个getAsXxx()抽象方法,返回一个数据

5.8.3 方法引用与构造器引用

方法引用和构造器引用可以让Lambda表达式的代码更加简洁。

种类 示例 对应的Lambda表达式
引用类方法 类名::类方法 (a,b…)->类名.类方法(a,b…)
引用特定对象的实例方法 特定对象::实例方法 (a,b…)->特定对象.实例方法(a,b…)
引用某类对象的实例方法 类名::实例方法 (a,b…)->a.实例方法(b…)
引用构造器 类名::new (a,b…)->类名(a,b…)

详细:

  1. 引用类方法
@functionalInterface
interface Converter{
    Integer convert(String from);
}
public class MethodRefer{
    public static void main(String []args){
        //Converter con1 = from->Integer.valueOf(from);
        Converter con1 = Integer::valueOf;
        Integer val = con1.convert("22");
        System.out.println(val);
    }
}

对于上面的类方法引用,也就是调用Integer类的valueOf()类方法来实现Converter函数式接口中唯一的抽象方法,当调用Converter接口中的唯一的抽象方法时,调用参数将会传给Integer类的valueOf()类方法。

2. 引用特定对象的实例方法

先用Lambda表达式来创建一个Converter对象

        Converter converter2 = from->"fkit.org".indexOf(from);
        Integer value = converter2.convert("it");
        System.out.println(value);

调用converter1对象的convert()方法将字符串转换为整数,

    Converter converter2 = "fkit.org"::indexOf;
        Integer value = converter2.convert("it");

对于上面的实例方法,也就是调用“fkit.org”对象的indexOf()实例方法,来实现,Converter函数式接口中唯一的抽象方法,调用参数将会传给”fkit.org”对象的indexOf()实例方法。

3. 引用某类对象的实例方法

 @FunctionalInterface
interface MyTest{
    String test(String a,int b,int c);
}

该函数式接口中包含一个test()抽象方法,该方法负责根据Stirng,int,int三个参数生成一个String返回值。

    //MyTest mt = (a,b,c)->a.substring(b,c);
        MyTest mt = String::substring;
        String str = mt.test("Java I Love You",2,9);
        System.out.println(str);

创建Lambda表达式,

接着可以调用mt对象的test()—–由于mt对象是Lambda表达式创建的,test()方法执行体就是Lambda表达式的代码块部分,因此上面程序输出

va I Lo

对于上面的实例方法引用,也就是调用某个String对象的substring()实例方法来实现MyTest函数式接口中唯一的抽象方法,当调用MyTest接口中的唯一的抽象方法时,第一个调用参数将作为substring()方法的调用者,剩下的调用参数会作为substring()实例方法的调用参数。

4. 引用构造器

import javax.swing.*;
@FunctionalInterface
interface YourTest{
//JFrame需要导入javax.swing.*;
    JFrame win(String title);
}

该函数接口包含一个win()抽象方法, 该方法负责根据String参数生成一个JFrame返回值。

    //YourTest yt = (String a) -> new JFrame(a);
        YourTest yt = JFrame::new;
        JFrame jf = yt.win("我的窗口");
        //System.out.println(jf);
        jf.setVisible(true);

使用Lambda表达式创建一个YourTest对象,

接着是调用yt对象的win()方法—-由于yt对象是Lambda表达式创建的,因此win()方法执行体就是Lambda表达式的代码块部分,即执行体就是执行new JFrame(a);语句,并将这条语句的值作为方法的返回值。

调用YourTest对象的win()抽象方法时,实际只传入了一个String类型的参数,这个String类型的参数会被传给JFrame构造器—这就确定了是调用JFrame类的、带一个String参数的构造器。

5.8.4 Lambda表达式与匿名内部类的联系和区别

Lambda表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用,Lambda和匿名内部类有以下的相同点

  • 都可以直接访问“effectively final”的局部变量,以及外部类的成员变量。
  • 两个生成的对象一样,都可以直接调用从接口中继承的默认方法。

    LambdaAndInner.java

package code;
@FunctionalInterface
interface Displayable{
    //定义一个抽象方法和默认方法
    void display();
    default int add(int a,int b){
        return a+b;
    }
}
public class LambdaAndInner{
    private int age = 12;
    private static String name = "疯狂软件中心";
    public void test(){
        String book = "疯狂Java讲义";
        Displayable dis = ()->{
            System.out.println("book局部变量为" + book);
            System.out.println("外部类的age实例变量为" + age);
            System.out.println("外部类的name类变量为" + name);

        };
        dis.display();
        System.out.println(dis.add(3,5));
    }
    public static void main(String [] args){
        LambdaAndInner lambda = new LambdaAndInner();
        lambda.test();
    }
}

book局部变量为疯狂Java讲义

外部类的age实例变量为12

外部类的name类变量为疯狂软件中心

8

当程序使用Lambda表达式创建了Displayable的对象之后,该对象不仅可调用接口中唯一的抽象方法,也可调用接口中的默认方法。

主要存在如下区别:

  • 匿名内部类可以为任何接口创建实例—-不管接口包含多少个抽象方法,但是Lambda表达式只能为函数式接口创建实例。
  • 匿名内部类可以为抽象类甚至普通类创建实例;但Lambda表达式只能为函数式接口创建实例;
  • 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法,但Lambda表达式不允许调用接口中定义的默认方法
//尝试调用接口中的默认方法,编译器会报错
System.out.println(add(3,5));

虽然Lambda比大师的目标类型:Displayable中包含了add()方法,但是Lambda表达式的代码块不允许调用这个方法, 而改用匿名内部类的时候就可以调用这个add()方法。

5.8.5 使用Lambda表达式调用Arrays的类方法

Arrays类的有些方法需要Comparator、XxxOperato、XxxFunction等接口的实例,这些接口都是函数式接口,因此可以使用Lambda表达式来调用Arrays的方法。

package code;
import java.util.Arrays;
public class LambdaArrays{
    public static void main(String [] args){
        String [] arr1 = new String[]{
            "java","fkava","fkit","ios","android"};
        Arrays.parallelSort(arr1,(o1,o2)->o1.length()-o2.length());
        System.out.println(Arrays.toString(arr1));

        int[] arr2 = new int[]{3,-4,25,16,30,18};
        Arrays.parallelPrefix(arr2,(left,right)->left*right);
        System.out.println(Arrays.toString(arr2));

        long[] arr3 = new long[5];
        Arrays.parallelSetAll(arr3,operand->operand*5);
        System.out.println(Arrays.toString(arr3));
    }
}

[ios, java, fkit, fkava, android]

[3, -12, -300, -4800, -144000, -2592000]

[0, 5, 10, 15, 20]

  1. 第一段arr1的Lambda表达式的目标类型是Comparator,该Comparator指定了判断字符串大小的标准,字符串越长,即可认为该字符串越大;
  2. 第二段arr2的Lambda表达式的目标类型是IntBinaryOperator,该对象将会根据前后两个元素来计算当前元素的值;
  3. 第三段arr3的Lambda表达式的目标类型是IntToLongFunction,该对象将会根据元素的索引来计算当前元素的值。
时间: 2024-12-21 14:17:45

Java8新增的Lambda表达式的相关文章

Java8新特性 - Lambda表达式 - 基本知识

A lambda expression is an unnamed block of code (or an unnamed function) with a list of formal parameters and abody. Java8中的lambda表达式不同于C#,使用的是-> eg: // Takes an int parameter and returns the parameter value incremented by 1 (int x) -> x + 1 // Take

JAVA8学习——深入浅出Lambda表达式(学习过程)

JAVA8学习--深入浅出Lambda表达式(学习过程) lambda表达式: 我们为什么要用lambda表达式 在JAVA中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法. 在JavaScript中,函数参数是一个函数,返回值是另一个函数的情况下非常常见的,JavaScript是一门非常典型的函数式编程语言,面向对象的语言 //如,JS中的函数作为参数 a.execute(callback(event){ event... }) Java匿名内部类实例 后面补充一个匿名内

Java核心技术之Java8新特性-Lambda表达式

1 总体说明 Java8新特性概述 函数式接口 Lambda表达式(闭包) 2 Java8新特性概述 Oracle公司于2014年3月发布了Java8正式版,该版本是自JDK5.0以来最具革命性的版本. Java8为Java语言.编译器.类库和JVM带来了大量的新特性.接下来的内容将会详细说明Java8在Java语言方面的新特性以及它们的使用场景. 3 函数式接口 Java8引入的一个核心概念是函数式接口(Functional Interfaces):如果一个接口定义一个唯一的抽象方法,那么这个

java8新特性——Lambda表达式

上文中简单介绍了一下java8得一些新特性,与优点,也是为本次学习java8新特性制定一个学习的方向,后面几篇会根据上文中得新特性一一展开学习.本文就从java8新特性中比较重要的Lambda表达式开始学学习. 一.为什么要使用Lambda表达式 Lambda是一个匿名函数,我们可以baLambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁,更灵活的代码.作为一种更紧凑得代码风格,使得java得语言表达能力得到提升.Lambda表达式需要函数式接口的支持,接口中

java8新特性-Lambda表达式(二)

Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接. Collections中的常用函数接口 Java集合框架的接口继承结构: 上图中蓝色标记和橙色标记的接口类,表示在Java8中加入了新的接口方法,由于继承关系,他们相应的子类也会继承这些方法. 下面用一张表列举这些方法 接口名 Java8新加入的方法 Collection removeIf() spliterator() st

用Java8 Stream和 Lambda表达式来解析文件的一个例子

最近我想从一个日志文件中提取出指定的数据,下面是日志的一部分: 2015-01-06 11:33:03 b.s.d.task [INFO] Emitting: eVentToRequestsBolt __ack_ack [-6722594615019711369 -1335723027906100557] 2 2015-01-06 11:33:03 c.s.p.d.PackagesProvider [INFO] ===---> Loaded package com.foo.bar 3 2015-

Java8一:Lambda表达式教程

1. 什么是λ表达式 λ表达式本质上是一个匿名方法.让我们来看下面这个例子: public int add(int x, int y) {         return x + y;     } 转成λ表达式后是这个样子:         (int x, int y) -> x + y; 参数类型也可以省略,Java编译器会根据上下文推断出来: (x, y) -> x + y; //返回两数之和   或者 (x, y) -> { return x + y; } //显式指明返回值 可见λ

Java8新特性 - Lambda 表达式

 是什么? Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性. Lambda 属于函数式编程思想,允许把函数作为一个方法的参数(函数作为参数传递进方法中). 怎么使用? 使用前提: 必须支持上下文推导,要能够推导出来 Lambda 表达式表示的是哪个接口中的内容. 可以使用接口当做参数,然后传递 Lambda 表达式(常用),也可以将 Lambda 表达式赋值给一个接口类型的变量. Lambda 表达式的省略规则: 小括号中的参数类型可以省略. 如果小括号中只有一个

【Java8实战】Lambda表达式(二)

在上一节中,我们为了使用Lambda表达式不得不创建了各种函数描述符的函数式接口,其实Java 8已经给我们提供了一套能够描述常见函数描述符的函数式接口.比如Predicate<T>.Consumer<T>.Function<T,R>.Supplier<T>等,这些函数式接口位于java.util.function包.这一节主要记录这些函数式接口的应用. Java8中的函数式接口 下表列出了Java8中常见的函数式接口: 函数式接口 函数描述符 原始类型特化