Java8的Lambda表达式(箭头函数)

函数式编程思想

在数学中 函数就是有输入量和输出量的一套计算方案 也就是“拿什么东西做什么事情”
相对而言 面向对象过分强调必须通过对象的形式来做事情
而函数式思想则尽量忽略面向对象的复杂语法——强调做什么 而不是以什么形式做

  • 面向对象的思想:
    做一件事情 找一个能解决这个事情的对象 调用对象的方法 完成事情
  • 函数式编程思想:
    只要能获取到结果 谁去做的 怎么做的都不重要,重视的是结果 不重视过程

可能不太好理解 我们举个简单的栗子:

代码冗余

public static void main(String[] args) {
        RunnableImpl runnable=new RunnableImpl();
        Thread thread=new Thread(runnable);
        thread.start();

        // 使用匿名内部类简化
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " - new");
            }
        };
        new Thread(runnable1).start();

        // 继续简化
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " - new");
            }
        }).start();
    }

在这段多线程代码中:
1、Thread 类需要 Runnable 接口作为参数 因为其中的抽象 run 方法是用来指定线程任务内容的核心
2、为了指定 run 的方法体 不得不需要存在一个Runnable接口的实现类
3、为了省去定义一个RunnableImpl实现类的麻烦 不得不使用匿名内部类
4、因为必须覆盖重写抽象的run方法 所以方法名称 方法参数和方法返回值都不得不再写一遍 且不能写错
而实际上 只有方法体才是关键所在

因此 我们需要转换我们的编程思想
更加关注做什么 而不是怎么做

我们为了做这件事情而不得不创建一个匿名内部类对象
而我们真正希望做的事情是将 run 方法体内的代码传递给 Thread 类

传递一段代码——这才是真正的目的


更加简便:

在2014年3月Oracle所发布的Java 8(JDK 1.8)中 加入了Lambda表达式的重量级新特性 打开了新世界的大门
如下案例:

public static void main(String[] args) {
    // 使用匿名内部类的方式实现多线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " - new");
        }
    }).start();

    //使用Lambda表达式实现多线程
    new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " - new");
        }
    ).start();
}

我们来详细看一下:
Runnable 接口只有一个 run 方法的定义:public abstract void run()
即制定了一种做事情的方案(其实就是一个函数):

  • 无参数:不需要任何条件即可执行该方案
  • 无返回值:该方案不产生任何结果
  • 代码块(方法体):该方案的具体执行步骤

在Lambda表达式中更为简单:() ‐> System.out.println("方法执行")

前面的一对小括号即 run 方法的参数(无) 代表不需要任何条件
中间的一个箭头代表将前面的参数传递给后面的代码
后面的输出语句即业务逻辑代码


一、Lambda表达式的标准格式

Lambda省去了面向对象语法的条条框框
格式由3个部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

标准格式:

(参数类型 参数名称)> { 代码语句 }

小括号内的语法与传统方法参数列表一致:无参数则留空 多个参数则用逗号分隔
-> 是新引入的语法格式 代表指向动作
大括号内的语法与传统方法体要求基本一致

二、案例:

1、无参数无返回值

接口类:

public interface Cook {
    public abstract void makeFood();
}

测试类:

public class CookTest {
    public static void main(String[] args) {
        // 调用invokeCook()方法 传递Cook接口的匿名内部类对象
        invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("开饭了");
            }
        });

        // 使用Lambda表达式简化匿名内部类的书写
        invokeCook(() -> {
            System.out.println("开饭了");
        });
    }

    // 定义一个方法 参数传递Cook接口 方法内部调用Cook接口中的makeFood()方法
    public static void invokeCook(Cook cook)
    {
        cook.makeFood();
    }
}

2、有参数有返回值

例一、

实体类:

public class Person {
    private String name;
    private int age;

    ...
}

测试类:

public class ArraysTest {
    public static void main(String[] args) {
        // 使用数组存储多个Person对象
        Person[] arr={new Person("小A",10),
                	new Person("小B",13),
                	new Person("小C",12)};

        // 升序操作
        Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        // 使用Lambda表达式简化匿名内部类的书写
        Arrays.sort(arr,(Person o1, Person o2) -> {
            return o1.getAge()-o2.getAge();
        });

        // 遍历输出结果
        for (Person p:arr)
        {
            System.out.println(p);
        }
    }
}

例二、

计算类:

public interface Calculate {
    public abstract int calc(int a,int b);
}

测试类:

public class CalculateTest {
    public static void main(String[] args) {
        // 调用方法 参数是一个接口 可以使用匿名内部类
        invokeCalc(1, 2, new Calculate() {
            @Override
            public int calc(int a, int b) {
                return a+b;
            }
        });

        // 使用Lambda表达式简化匿名内部类的书写
        invokeCalc(1,2,(int a,int b) -> {
            return a+b;
        });
    }

    // 参数传递两个int类型的整数和Calculate接口 内部调用方法计算两个整数的和
    public static void invokeCalc(int a,int b,Calculate calculate)
    {
        System.out.println(calculate.calc(a, b));
    }
}

三、Lambda省略格式

Lambda表达式是可推导可省略的
凡是根据上下文推导出来的内容 都可以省略书写

可省略的内容:
1、参数列表:括号中参数列表的数据类型可以省略不写 因为在接口里已经定义了类型
2、参数列表:括号中的参数如果只有一个 那么类型和()都可以省略
3、代码:如果{}中的代码只有一行 那么无论是否有返回值 都可以省略{}和return和分号 但要省略必须一起省略

public static void main(String[] args) {
   // 在JDK1.7版本之前 必须把前后的泛型都写上
    ArrayList<String> arrayList1=new ArrayList<String>();

    // 在JDK1.7版本之后 等号后的泛型可省略 因为可以根据前面的泛型推导出来
    ArrayList<String> arrayList2=new ArrayList<>();
}

案例

无参数无返回值:

// 调用invokeCook()方法 传递Cook接口的匿名内部类对象
invokeCook(new Cook() {
    @Override
    public void makeFood() {
        System.out.println("开饭了");
    }
});

// 使用Lambda表达式简化匿名内部类的书写
invokeCook(() -> {
    System.out.println("吃饭了");
});

// 优化省略Lambda ★
invokeCook(() -> System.out.println("吃饭了"));

有参数有返回值:

// 调用方法 参数是一个接口 可以使用匿名内部类
invokeCalc(1, 2, new Calculate() {
     @Override
     public int calc(int a, int b) {
         return a+b;
     }
 });

 // 使用Lambda表达式简化匿名内部类的书写
 invokeCalc(1,2,(int a,int b) -> {
     return a+b;
 });

 // 优化省略Lambda ★
 invokeCalc(1,2,(a,b) -> a+b);

总结:
1、使用Lambda必须具有接口 且要求接口中有且仅有一个抽象方法
因此 无论是JDK内置的 Runnable 接口或 Comparator 接口还是自定义的接口 只有当接口中的抽象方法存在且唯一时 才可使用Lambda
因为如果有多个抽象方法的话 就不知道调用哪个抽象方法了
2、使用Lambda必须具有上下文推断
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型 才能使用Lambda作为该接口的实例

注:有且仅有一个抽象方法的接口称为函数式接口


发布了56 篇原创文章 · 获赞 0 · 访问量 1173

猜你喜欢

转载自blog.csdn.net/Piconjo/article/details/104656938