Java8学习--Lambda表达式

一、简介

Lambda是一个匿名函数,我们可以把Lambda理解为一段可以传递的代码(将代码像数据一样传递)。

二、 回顾匿名内部类

假设我们有一个员工类Employee,我们要用一些条件对公司的员工进行筛选,为了提高代码的复用性,我们可以设计一个专门的接口:

interface MyPredicate<T> {
    public boolean condition(T t);
}

在接口中的方法condition()就是我们要进行筛选的条件,针对不同的筛选条件,我们只需要多次实现这个接口即可:

class FilterEmployByAge implements MyPredicate<Employee> {

    @Override
    public boolean condition(Employee employee) {
        return employee.getAge() > 35;
    }
}

在业务处理过程中,我们调用这个接口,其实真正需要的是它包含的condition()方法。换句话说filterEmployees1()希望接受一段方法代码作为参数,但没有办法直接传递这个方法代码本身,只能传递一个接口:

    public List<Employee> filterEmployees1(List<Employee> list, MyPredicate<Employee> myPredicate) {
        List<Employee> result = new ArrayList<>();
        for (Employee employee : list) {
            if (myPredicate.condition(employee)) {
                result.add(employee);
            }
        }
        return result;
    }

针对这种情况,我们可以使用匿名内部类的方式来简化代码:
在选择筛选条件的时候,使用匿名内部类的方式实现MyPredicate接口:

//无需再专门实现MyPredicate接口,但是filterEmployees1()方法还是需要写
        List<Employee> result2 = test.filterEmployees1(employees, new MyPredicate<Employee>() {
            @Override
            public boolean condition(Employee employee) {
                return employee.getAge() > 35;
            }
        });

三、初识Lambda

import sun.security.mscapi.CPublicKey;

import java.util.*;

public class Lambda {
    public static void main(String[] args) {
//        匿名内部类写法
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
//        使用匿名内部类
        TreeSet<Integer> treeSet = new TreeSet<>(comparator);
//        匿名内部类-->Lambda表达式
        Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);
//        使用Lambda表达式
        TreeSet<Integer> treeSet1 = new TreeSet<>(comparator1);

        List<Employee> employees = Arrays.asList(
                new Employee("A", 12, 12.11),
                new Employee("B", 18, 99.00),
                new Employee("C", 46, 45.11),
                new Employee("D", 50, 34.32),
                new Employee("E", 47, 78.12),
                new Employee("F", 67, 34.56),
                new Employee("G", 21, 100)
        );
//        需求:获取所有员工年龄大于35岁的员工信息
        Lambda test = new Lambda();
//        普通实现方式
        List<Employee> result = test.filterEmployees(employees);
        for (Employee employee : result) {
            System.out.println(employee);
        }
        System.out.println("____________________________________");
//        优化1:策略模式,在调用的时候选择策略(接口的实现),方便复用
        List<Employee> result1 = test.filterEmployees1(employees, new FilterEmployByAge());
        for (Employee employee : result1) {
            System.out.println(employee);
        }
//        优化2:策略模式+匿名内部类(比单纯的策略模式省去了一个额外的调用策略的方法)
        List<Employee> result2 = test.filterEmployees1(employees, new MyPredicate<Employee>() {
            @Override
            public boolean condition(Employee employee) {
                return employee.getAge() > 35;
            }
        });
        System.out.println("____________________________________");

        for (Employee employee : result2) {
            System.out.println(employee);
        }
//        优化3:策略模式+Lambda(比匿名内部类又少了很多冗余代码)
        List<Employee> result3 = test.filterEmployees1(employees, (e) -> e.getAge() > 35);
        System.out.println("____________________________________");
        result3.forEach(System.out::println);
//        优化4:stream
        System.out.println("____________________________________");
        employees.stream()
                .filter((e) -> e.getAge() > 35)
                .forEach(System.out::println);

    }

    //    普通实现方式
    public List<Employee> filterEmployees(List<Employee> list) {
        List<Employee> result = new ArrayList<>();
        for (Employee employee : list) {
            if (employee.getAge() > 35) {
                result.add(employee);
            }
        }
        return result;
    }

    //    调用策略模式的方法
    public List<Employee> filterEmployees1(List<Employee> list, MyPredicate<Employee> myPredicate) {
        List<Employee> result = new ArrayList<>();
        for (Employee employee : list) {
            if (myPredicate.condition(employee)) {
                result.add(employee);
            }
        }
        return result;
    }
}

//策略模式接口
interface MyPredicate<T> {
    public boolean condition(T t);
}

//策略模式的策略,每多一个策略,需要额外建一个类,不方便
class FilterEmployByAge implements MyPredicate<Employee> {

    @Override
    public boolean condition(Employee employee) {
        return employee.getAge() > 35;
    }
}
//实体类
class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee() {
    }

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

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    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;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

四、Lambda基础语法

1.箭头操作符

Java8新引入了一个操作符“->”,我们称为箭头操作符,或Lambda操作符。
箭头操作符将Lambda表达式拆分为两部分:
左侧:Lambda 表达式的参数列表。(对应接口中抽象方法的参数列表)
右侧:Lambda 表达式中所需要执行的功能,即Lambda 体。(对象接口中抽象方法的实现)

2.语法格式

格式1:对应的抽象方法无参数列表,无返回值。
示例:

        //实现
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        };
        //使用Lambda实现
        Runnable r1 = () -> System.out.println("hello");
        //两种调用
        r.run();
        r1.run();

格式2:对应抽象方法有一个参数,无返回值。
note:

  • 如果只有一个参数,那么参数的小括号可以省略。
  • Lambda表达式的参数列表数据类型可以省略不写,因为JVM编译器可以通过上下文推断出参数的数据类型(类型推断)。
  • Lambda表达式可以赋值给函数式接口,相当于实现一个简略版匿名内部类(不需要new)。
        Consumer<String> con = (x) -> System.out.println(x);
        con.accept("hello");
        System.out.println(con);

格式3:对应抽象方法有多个参数、多条语句,以及返回值。
note: 如果Lambda函数中有多条语句,那么需要大括号。

        Comparator<Integer> comparator=(x,y)->{
            int temp=Integer.compare(x,y);
            return temp;
        };

格式4:对应抽象方法有多个参数、一条语句,以及返回值。
note: 只有一条语句,那么大括号和return 都可以省略。

Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);

3.函数式接口

接口中只有一个抽象方法,这个接口称为函数式接口。一般会在接口前添加注解@FunctionalInterface来检查是否满足函数式接口,使用示例可见demo。
note:

  • Lambda表达式需要函数式接口的支持。
  • 选择函数式接口根据每个接口的特点来选择,比如Function与Comsumer的区别在于,前者的抽象函数有返回值,返回类型可以与入参不一致,可用于操作参数后生成新的对象;后者没有返回值,可用于改变入参对象。

一些常见的函数式接口:
在这里插入图片描述

4.注意项

与匿名内部类类似,Lambda表达式也可以访问定义在主体代码外部的变量,但对于局部变量,它也只能访问final类型的变量,与匿名内部类的区别是,它不要求变量声明为final,但变量事实上不能被重新赋值。
eg.

        String test="hello";
        //此时如果对test变量进行修改,编译器会报错
        Runnable r1 = () -> System.out.println(test);
        r1.run();

Java会将test的值作为参数传递给Lambda表达式,为Lambda表达式建立一个副本,它的代码访问的是这个副本,而不是外部声明的msg变量。如果允许test被修改,则程序员可能会误以为Lambda表达式读到修改后的值,引起更多的混淆。
进一步深究,为什么要建立副本,而不是直接访问外部变量,这是因为test变量定义在栈中,Lambda表达式被执行的时候,这个变量可能已经被释放。如果希望传入的值可以修改,可以考虑将变量定义为实例变量或者数组,这种值被修改后,内存地址不会变的对象。

五、方法引用与构造器引用

4.1 基本概念

方法引用:若Lambda体中的内容有方法已经实现,我们可以使用方法引用。(可以理解为方法引用是Lambda表达式的另一种表现形式)
构造器引用:与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法。

4.2 使用前提

使用方法引用,那么这个方法的参数与返回类型,要与函数式接口中抽象方法的保持一致。
例如上面提到的forEach(System.out::println)这里println的内容和传入forEach的内容一致。
使用构造器引用,构造器参数列表要与接口中抽象方法的参数列表一致(想要调用不同的构造函数,只要更改接口中抽象方法的参数即可)。

4.3 语法格式

方法引用的基本格式:
格式1:对象::实例方法名
格式2:类::静态方法名
格式3:类::实例方法名
构造器引用基本格式:
ClassName::new

六、一些Demo

1.调用Collections.sort()方法,通过定制排序比较Employee对象(先比较年龄,年龄相同比较名字):

//        自定义排序规则
        Collections.sort(employees, (o1, o2) -> {
            if (o1.getAge() == o2.getAge()) {
                return o1.getName().compareTo(o2.getName());
            } else {
                return Integer.compare(o1.getAge(), o2.getAge());
            }
} );

2.利用策略模式,声明函数式接口,接口中声明抽象方法getValue,在另一个类中编写一个方法使用接口作为参数,将一个字符串变为大写:

public class LambdaTest {
    public static void main(String args[]) {
//        在用的时候再指定策略,这里也可以使用匿名内部类的方式来实现
        System.out.println(toUpper("aabbcc",x->x.toUpperCase()));
    }
//    调用策略的方法
    public static String toUpper(String str,Operator operator){
        return operator.getValue(str);
    }
}
//策略接口
interface Operator{
    public String getValue(String str);
}

3.函数型接口Function<T,R> Demo
对类型为T的入参处理后,返回值的类型为R。

public class LambdaTest {
    public static void main(String args[]) {
        System.out.println(strHandle("AABBCC",x->x.toLowerCase()));
    }
    public static String strHandle(String str, Function<String,String> function){
        return function.apply(str);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值