Lambda表达式(一)入门认识篇

Lambda表达式(一)入门认识篇

Lambda简介

  • Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构

  • JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。

  • Lambda表达式是Java SE 8中一个重要的新特性。Lambda表达式允许你通过表达式来代替功能接口。Lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。

  • Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说Lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。

Lambda定义

Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:
它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多

函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。

传递——Lambda表达式可以作为参数传递给方法或存储在变量中。

简洁——无需像匿名类那样写很多模板代码。

对接口的要求

虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法

jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

函数式接口

什么是函数式接口

(1)只包含一个抽象方法的接口,称为函数式接口。

(2)你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)。

(3)我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包 含一条声明,说明这个接口是一个函数式接口。

@FunctionalInterface

修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。

Lambda表达式语法

基本语法

  • Java8中引入了一个新的操作符,"->",该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分;

    左侧: Lambda表达式的参数列表,对应的是接口中抽象方法的参数列表;
    右侧: Lambda表达式中所需要执行的功能(Lambda体),对应的是对抽象方法的实现;(函数式接口(只能有一个抽象方法))

  • Lambda表达式的实质是 对接口的实现;
	(parameters) ->{ statements; }
	//示例代码
	Comparator<Apple> byWeight2  = (Apple o1, Apple o2)-> o1.getWeight().compareTo(o2.getWeight());

解析:

  • 参数列表——这里它采用了 Comparatorcompare 方法的参数,两个 Apple
  • 箭头——箭头 -> 把参数列表与Lambda主体分隔开
  • Lambda主体——比较两个 Apple 的重量。表达式就是Lambda的返回值了

Lambda语法示例

下面是Java 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五大语法格式

语法格式一:接口中的抽象方法 : 无参数,无返回值;

 @Test
    public void test1(){
        /**
         *语法格式一、
         *  接口中的抽象方法 : 无参数,无返回值;
         */

        /*final */int num = 2; //jdk1.7之前必须定义为final的下面的匿名内部类中才能访问

        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world!" + num); //本质还是不能对num操作(只是jdk自己为我们设置成了final的)
            }
        };
        r.run();

        System.out.println("----------使用Lambda输出-----------");

        Runnable r1 = () -> System.out.println("Hello world!" + num);//省去乐:大括号,分号
        r1.run();
    }

语法格式二:接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)

    @Test
    public void test2(){
        /**
         *语法格式二、
         *  接口中的抽象方法 : 一个参数且无返回值; (若只有一个参数,那么小括号可以省略不写)
         */

        Consumer<String> con = x -> System.out.println(x);
        con.accept("Lambda牛逼!");
    }

语法格式三:两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return

    @Test
    public void test3(){
        /**
         *语法格式三、
         *  两个参数,有返回值,并且有多条语句 : 要用大括号括起来,而且要写上return
         */
        Comparator<Integer> com = (x,y) -> {
            System.out.println("函数式接口,");
            return Integer.compare(y,x); //降序
        };

        Integer[] nums = {4,2,8,1,5};
        Arrays.sort(nums,com);
        System.out.println(Arrays.toString(nums));
    }

语法格式四:两个参数,有返回值,但是只有一条语句: 大括号省略,return省略

    @Test
    public void test4(){
        /**
         *语法格式四、
         *  两个参数,有返回值,但是只有一条语句: 大括号省略,return省略
         */
        Comparator<Integer> com = (x,y) -> Integer.compare(x,y);//升序
        Integer[] nums = {4,2,8,1,5};
        Arrays.sort(nums,com);
        System.out.println(Arrays.toString(nums));
    }

语法格式五:表达式的参数列表的数据类型 可以省略不写,因为JVM编译器通过上下文推断出数据类型,即"类型推断",

 (Integer x,Integer y ) -> Integer.compare(x,y)
 //可以简写成
 (x,y) -> Integer.compare(x,y);

Lambda 表达式常用示例


Lambda创建线程


  • 创建 线程对象,然后通过匿名内部类重写 run() 方法,提到匿名内部类我们可以通过使用 lambda 表达式来简化线程的创建过程。

通过Thread创建线程示例代码:

        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(2 + ":" + i);
            }
        });
        t.start();

通过Runnable创建线程示例代码:

 	/*
     * Lambda实现Runnable接口
     * Runnable 的 lambda表达式,使用块格式,将五行代码转换成单行语句
     */
    @Test
    public  void test6() {
        // 1.1原来方式使用匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world !使用匿名内部类,开线程");
            }
        }).start();

        // 1.2使用 lambda expression
        new Thread(() -> System.out.println("使用 lambda expression,开线程")).start();

        // 2.1使用匿名内部类
        Runnable race1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("使用匿名内部类,不开线程");
            }
        };

        // 2.2使用 lambda expression
        Runnable race2 = () -> System.out.println("使用 lambda expression,不开线程");

        // 直接调用 run 方法(没开新线程哦!)
        race1.run();
        race2.run();
    }

Lambda遍历集合


通过调用集合的 public void forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。以下是 Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。

	//jdk提供的函数式接口
    @FunctionalInterface//这个注解是用判断是否是函数式接口
    public interface Consumer<T> {
        void accept(T t);
        //....
    }
      String[] atp = {"Rafael Nadal", "Novak Djokovic",
                "Stanislas Wawrinka",
                "David Ferrer","Roger Federer",
                "Andy Murray","Tomas Berdych",
                "Juan Martin Del Potro"};
        List<String> players =  Arrays.asList(atp);

        // 以前的循环方式
        for (String player : players) {
            System.out.print(player + "; ");
        }

        // 使用 lambda 表达式以及函数操作(functional operation)
        players.forEach((player) -> System.out.print(player + "; "));

        // 在 Java 8 中使用双冒号操作符(double colon operator)
        players.forEach(System.out::println);
    }

删除集合中的某个元素


通过removeIf()方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。

 List<Person> javaProgrammers = new ArrayList<Person>() {
            {
                add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
                add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
                add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
                add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
                add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
                add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
                add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
                add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
                add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
                add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
            }
        };
        //removeIf()删除集合中符合条件的值
        javaProgrammers.removeIf(ele -> ele.getSalary() == 2000);

        //通过 foreach 遍历,查看是否已经删除
        javaProgrammers.forEach(System.out::println);

集合内元素的排序


在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。

    /*
     *Lambdas排序集合
     * 在Java中,Comparator 类被用来排序集合。
     */
		String[] players = {"Rafael Nadal", "Novak Djokovic",
                "Stanislas Wawrinka", "David Ferrer",
                "Roger Federer", "Andy Murray",
                "Tomas Berdych", "Juan Martin Del Potro",
                "Richard Gasquet", "John Isner"};
        // 1.1 使用匿名内部类根据 name 排序 players
        Arrays.sort(players, new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return (s1.compareTo(s2));
            }
        });
        System.out.println("使用静态内部类排序结果:"+Arrays.toString(players));
        System.out.println("-----------------------分割线-------------------------");

        String[] players2 = {"Rafael Nadal", "Novak Djokovic",
                "Stanislas Wawrinka", "David Ferrer",
                "Roger Federer", "Andy Murray",
                "Tomas Berdych", "Juan Martin Del Potro",
                "Richard Gasquet", "John Isner"};

        // 1.2 使用 lambda expression 排序 players
        Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
        Arrays.sort(players2, sortByName);
        System.out.println("使用lambda排序结果(方式一):"+Arrays.toString(players2));
        // 1.3 也可以采用如下形式:
        Arrays.sort(players2, (String s1, String s2) -> (s1.compareTo(s2)));
        System.out.println("使用lambda排序结果(方式二):"+Arrays.toString(players2));


Lambda 表达式引用方法


有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。
语法:
方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象
示例代码:

package com.zy.pagehelper.Interface;

@FunctionalInterface
public interface ReturnOneParam {
    /**
     *函数式接口
     * 一个参数有返回值
     */
    int method(int a);
}

public class LambdaTest {
    public static void main(String[] args) {
    	//这里返回函数式接口ReturnOneParam一个参数
        ReturnOneParam lambda1 = a -> doubleNum(a);
        System.out.println(lambda1.method(3));

        //lambda2 引用了已经实现的 doubleNum 方法
        ReturnOneParam lambda2 = LambdaTest::doubleNum;
        System.out.println(lambda2.method(3));

        LambdaTest exe = new LambdaTest();

        //lambda4 引用了已经实现的 addTwo 方法
        ReturnOneParam lambda4 = exe::addTwo;
        System.out.println(lambda4.method(2));

    }

    /**
     * 要求
     * 1.参数数量和类型要与接口中定义的一致
     * 2.返回值类型要与接口中定义的一致
     */
    public static int doubleNum(int a) {
        return a * 2;
    }
    public int addTwo(int a) {
        return a + 2;
    }

}

构造方法的引用


一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。

示例代码:


public interface PersonCreatorBlankConstruct {
        /**接口作为对象的生成器
         *无参构造器
         */
        Person getPerson();
}
public interface PersonCreatorParamContruct {
    /*
     *有参构造器
     */
    Person getPerson(String firstName, String lastName, String job,
                   String gender, int age, int salary);
}


    /**
     *构造方法的引用
     * 一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。
     * 该接口作为对象的生成器---->创建一个无参构造器,
     * 该接口作为对象的生成器---->创建一个有参构造器,
     */
    @Test
    public  void test9() {
        /**
         *1.lambda表达式创建对象,返回无参函数接口,生参无参对象
         */
        PersonCreatorBlankConstruct creator = () -> new Person();
        Person person = creator.getPerson();

        PersonCreatorBlankConstruct creator2 = Person::new;
        Person person1 = creator2.getPerson();

        PersonCreatorParamContruct creator3 = Person::new;
        Person person2 = creator3.getPerson("名称", "修改名称","职位","男",23,2000);
    }

完毕!,搞定搞定lambda表达式的基本知识点,接下来我们才可以更深入的认识JDK8的新特性


参考/推荐博客:
Lambda表达式详解
Java8新特性入门一(Lambda表达式一)
Java Lambda表达式入门

原文地址:https://www.cnblogs.com/MrYuChen-Blog/p/14000259.html