首页 > 技术文章 > Java8新特性lambda表达式快速入门

chaogu94 2021-08-08 20:13 原文

随着Java语言的不断发展,Java8提供的新特性lambda表达式也成为越来越多开发者喜欢的写法,为了顺应时代的变化,需要好好地学习这种写法,并应用在平时的开发中。我们先从简单的例子着手,一步步的深入到lambda的复杂写法,首先我们的任务是创建一个用来比较两个int值大小的接口,并给它添加具体的实现方法,然后进行调用。接口定义如下:

interface Comparator{
    int compare(int a,int b);
}

常规写法

写法一:新建类并实现该接口

创建MyComparator.java并实现Comparator接口:

class MyComparator implements Comparator{
    @Override
    public int compare(int a, int b) {
        return a > b ? a : b; //输出最大的值
    }
}

然后进行调用:

public static void main(String[] args) {
    Comparator comparator = new MyComparator();
    
    int res = comparator.compare(1, 10);
    System.out.println(res);
}

写法二:使用匿名函数

直接new接口

public static void main(String[] args) {
    //使用匿名方法实现
    Comparator comparator = new Comparator() {
        @Override
        public int compare(int a, int b) {
            return a > b ? a : b;
        }
    };
    
    int res = comparator.compare(1, 10);
    System.out.println(res);
}

lambda写法

跟上面的常规写法不同的是,lambda只关注方法的入参方法体中具体实现的逻辑,其余能省的代码全部都可以省略,我们先看一下第一种写法:

写法一:lambda常规写法

自己比较一下跟匿名函数写法的差别,是不是非常的简洁,lambda只声明了入参方法体内的逻辑,关键地方在于入参和方法体之间使用了->箭头符号进行连接,箭头符号读作gose to

public static void main(String[] args) {
    //简单的lambda写法
    Comparator comparator = (int a,int b) -> {
        return a > b ? a : b;
    };
    
    int res = comparator.compare(1, 10);
    System.out.println(res);
}

到这里你应该有一个基本的印象了,这一步转变非常重要,停下来好好思考一下。

写法二:lambda简写

仔细观察可以发现写法一中仍然有多余的代码,比如说我们在之前的接口中已经声明过一次入参的类型,在使用的时候又声明了一次,这显然是多余的写法。其实上面的写法依然可以更简洁,只要满足下面这几点,都可以省略。

  1. 当入参类型和初始定义类型一致时,可以省略入参类型。
  2. 当方法体中只有一句代码时,可以省略大括号。
  3. 当方法体中只有一句代码,并且需要返回值时,可以省略return关键字。

更简单的写法如下:

public static void main(String[] args) {
    //省略了入参类型、大括号、return关键字
    Comparator comparator = (a,b) -> a > b ? a : b;
    
    int res = comparator.compare(1, 10);
    System.out.println(res);
}

中场疑问

上面我们定义的接口只有一个抽象方法compare(),有的同学就会有疑问,那如果有多个抽象方法怎么写呢?问的很好!由于lambda的写法非常的简洁,但这是在一定的条件限制下才能这么写,其中非常重要的一点就是,只有一个抽象方法的接口才支持lambda的写法,这种接口被称为函数式接口,可以通过@FunctionalInterface注解进行修饰。

@FunctionalInterface只修饰函数式接口,即只有一个抽象方法的接口。

lambda的方法引用

写法一

lambda表达式中方法体只有一行代码才能有最简单的写法,那如果我想写一些复杂的业务就不能简写了吗?其实并不是的,这时候我们就可以泳道lambda的方法引用,将较复杂的业务封装成方法,然后在lambda中进行调用,假如还是上面的例子,我们可以将方法体中的那一行代码封装成一个方法:

public static int max(int a,int b){
    //这里可以写一些较为复杂的业务
    return a > b ? a : b;
}

这时候lambda表达式就可以这么写:

public static void main(String[] args) {
    //方法体中引用max函数
    Comparator comparator = (a,b) -> max(a,b);
    int res = comparator.compare(1, 10);
    System.out.println(res);
}

上面就是一个普通的lambda方法引用,简单的说就是将方法体中的代码封装成一个一方法,然后在调用这个方法即可,需要注意的是max函数中的返回值必须要跟接口中的抽象方法一致,否则就会报异常。

写法二

上面的写法虽然简单有效,但依然有多余的地方,那就是方法参数重复了,代码(a,b) -> max(a,b);中出现了两次(a,b),在实际开发中,我们一般采用另一种写法来代替->箭头符号,请大家牢记语法:

方法的隶属者::方法名。

隶属者可以理解为,能够调用这个方法的对象,如果该方法是static修饰的,则隶属者位当前类;如果不是static修饰的,则隶属者属于一个对象。如果听不懂也没关系,看一个栗子就清晰明了。

首先看一下完整的代码:

//定义接口
interface Comparator {
    int compare(int a, int b);
}

public class Program {
    public static void main(String[] args) {
        //使用lambda的方法引用调用max方法,隶属者为类
		Comparator comparator = Program::max;
        int res = comparator.compare(1, 10);
        System.out.println(res);
    }
    //用static修饰的方法
    public static int max(int a,int b){
        return a > b ? a : b;
    }
}

从上面代码中我们发现,将之前的(a,b) -> max(a,b);写成了Program::max;,因为方法max被关键字static修饰了,因此隶属者应该是Program这个类。也可以发现省略了参数(a,b),这么写就已经是非常的简洁了,但这么写需要满足两个基本条件:

  1. 只有接口中抽象方法的参数格式跟定义的方法参数格式一样,才能省略参数。
  2. 接口中抽象方法的返回值必须跟定义的方法的返回值一样。

这就是java8引入的新特性,一切为了简洁明了。

方法不是static怎么写?

理解了上面,这个就比较好懂了,贴一下代码,不做解释了

public class Program {
    public static void main(String[] args) {
        //创建一个Program对象
        Program p = new Program();
        //使用lambda的方法引用调用max方法,隶属者为对象
		Comparator comparator = p::max;
        int res = comparator.compare(1, 10);
        System.out.println(res);
    }
    //没有被static修饰的方法
    public int max(int a,int b){
        return a > b ? a : b;
    }
}

练习题

素材如下:

Person.java

class Person{
    String name ;
    int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

准备集合

public class Program {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person("张三",18));
        list.add(new Person("李四",30));
        list.add(new Person("王五",16));
        list.add(new Person("赵六",20));
        list.add(new Person("田七",40));
        list.add(new Person("马八",25));        
        printList(list);
    }
    
    //打印集合
    public static void printList(List<Person> list){
        for (Person person : list) {
            System.out.println(person);
        }
    }
}

1. 将集合按照指定属性进行排序

按照person的年龄进行从低到高排序

普通的排序方式:

//使用正常的方式排序
list.sort(new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getAge() - o2.getAge();
    }
});

问题:你能将普通的排序方式改造成lambda的方式吗?写出来后思考还有其他写法吗?

2. 将list.forEach方法改造成lambda方式

正常的list遍历:

list.forEach(new Consumer<Person>() {
    @Override
    public void accept(Person person) {
		System.out.println(person.toString());
    }
});

问题:如何改造成lambda的方式??改造之后修改练习1中的遍历代码

3.删除集合中指定元素

删除年龄大于35的人

正常的删除:

list.removeIf(new Predicate<Person>() {
    @Override
    public boolean test(Person person) {
        return person.age > 35;
    }
});

问题:如何改造成lambda的方式??

答案

  1. 排序(二选一)
list.sort((p1,p2) -> p1.getAge() - p2.getAge());
Collections.sort(list,(p1,p2) -> p1.getAge() - p2.getAge());
  1. 遍历
list.forEach(person -> System.out.println(person.toString()));
  1. 删除指定元素
list.removeIf(person -> person.age > 35);

推荐阅读