黑马程序员技术交流社区

标题: 【郑州校区】Lambda表达式 [打印本页]

作者: 我是楠楠    时间: 2018-6-12 11:10
标题: 【郑州校区】Lambda表达式
本帖最后由 我是楠楠 于 2018-6-12 11:14 编辑

【郑州校区】Lambda表达式

Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(函数式接口)的实例。
一、表达式入门
先观察下段代码
[AppleScript] 纯文本查看 复制代码
Comparator<String> com = new Comparator<String>(){
    @Override
    public int compare(String o1, String o2) {
        return Integer.compare(o1.length(), o2.length());
    }
};

TreeSet<String> ts = new TreeSet<>(com);
该段代码采用匿名内部类方式去创建了一个Comparator对象,并实现了compare方法。但是该段代码中,最关键的代码其实就一行:
[AppleScript] 纯文本查看 复制代码
Integer.compare(o1.length(), o2.length());
使用Lambda表达式解决上述问题:
[AppleScript] 纯文本查看 复制代码

Comparator<String> com = (x, y) -> Integer.compare(x.length(), y.length());
TreeSet<String> ts = new TreeSet<>(com);
该句
[AppleScript] 纯文本查看 复制代码
(x, y) -> Integer.compare(x.length(), y.length())
;即为Lamba表达式。
二、Lambda表达式的语法
Lambda表达式的主要作用就是代替匿名内部类的烦琐语法。
由三部分组成:
基本语法:
(parameters) -> expression
(parameters) ->{ statements; }
详细解释:
代码演示:

[AppleScript] 纯文本查看 复制代码
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);
    }
}
三、函数式接口
​        Lambda表达式的类型,也被称为“目标类型”,Lambda表达式的目标类型必须是“函数式接口”。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但是只能声明一个抽象方法。
​        如果采用匿名类型内部类来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用lambda表达式来创建对象,该表达式创建出来的对象目标就是这个函数接口(可以用@FunctionalInterface注解来对函数接口实行限制)
@FunctionalInterface
通过JDK8源码javadoc,可以知道这个注解有以下特点:
1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
下面这个接口就是一个正确的函数式接口:

[AppleScript] 纯文本查看 复制代码
// 正确的函数式接口  
@FunctionalInterface  
public interface TestInterface {  
  
    // 抽象方法  
    public void sub();  
  
    // java.lang.Object中的方法不是抽象方法  
    public boolean equals(Object var1);  
  
    // default不是抽象方法  
    public default void defaultMethod(){  
  
    }  
  
    // static不是抽象方法  
    public static void staticMethod(){  
  
    }  
}  
四、方法引用与构造函数引用
​        若Lambda 体中的内容有方法已经实现了,我们可以使用“方法引用”或"构造引用"。其实关于方法引用和构造引用大家可以简单理解为lambda的简写形式。
Lambda支持的方法引用和构造引用[td]
种类
示例
说明
对应的Lambda表达式
引用类方法类名::类方法函数式接口中被实现方法的全部参数传给该类方法作为参数(a,b,...)->类名.类方法(a,b,...)
引用特定对象的实例方法特定对象::实例方法函数式接口中被实现方法的全部参数传给该方法作为参数(a,b,...)->特定对象.实例方法(a,b,...)
引用某类对象的实例方法类名::实例方法函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数(a,b,...)->a.实例方法(b,...)
引用构造器类名::new函数式接口中被实现方法的全部参数传给该构造器作为参数(a,b,...)->new  类名(a,b,...)
示例代码1. 引用类方法
这是一个标准的函数式接口:

[AppleScript] 纯文本查看 复制代码
@FunctionalInterface
interface Converter{
    Integer convert(String from);
}
下面是传统Lambda表达式和引用类方法的形式比较:

[AppleScript] 纯文本查看 复制代码
//传统Lambda表达式写法
Converter con = from -> Integer.valueOf(from);
Integer val = con.convert("99");
System.out.println(val);    //  输出99
System.out.println("-----------------------------------------");
//引用类方法
//函数式接口中被实现方法的全部参数传给该类方法作为参数
Converter con2 = Integer::valueOf;
Integer val2 = con2.convert("99");
System.out.println(val2);   //  输出99
对比之后,会发现引用类方法形式的写法更加简洁。
2. 引用特定对象的实例方法
引用特定对象的实例方法为函数式接口中被实现方法的全部参数传给该方法作为参数        
[AppleScript] 纯文本查看 复制代码

//传统Lambda表达式写法
Converter con = from -> "itcast".indexOf(from);
//函数式接口中被实现方法的全部参数传给该方法作为参数
Converter con2 = "itcast"::indexOf;
3.引用某类对象的实例方法
函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数

[AppleScript] 纯文本查看 复制代码
//传统Lambda表达式写法
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcde", "abcde"));

System.out.println("-----------------------------------------");
//引用某类对象的实例方法
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));
4.引用构造器
  函数式接口中被实现方法的全部参数传给该构造器作为参数

[AppleScript] 纯文本查看 复制代码
public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() {
    }

    public Employee(String name) {
        this.name = name;
    }

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

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
}
下面是传统Lambda表达式和引用构造器的形式比较:
[AppleScript] 纯文本查看 复制代码

Supplier<Employee> sup = () -> new Employee();
System.out.println("------------------------------------");
// 无参构造
Supplier<Employee> sup2 = Employee::new;
// 一个参数的构造器
Function<String, Employee> fun = Employee::new;
System.out.println(fun.apply("a"));
// 两个参数的构造器
BiFunction<String, Integer, Employee> fun2 = Employee::new;
System.out.println(fun2.apply("b", 11));
五、Lambda表达式与匿名内部类的区别[td]
匿名内部类
Lambda表达式
抽象类甚至普通类创创建实例(不管有多少个抽象方法)只能为函数式接口创建实例(单方法)
抽象方法体允许调用接口中的默认方法不允许调用接口中的默认方法

传智播客·黑马程序员郑州校区地址
河南省郑州市 高新区长椿路11号大学科技园(西区)东门8号楼三层
联系电话 0371-56061160/61/62
来校路线  地铁一号线梧桐街站A口出





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2