JDK8新特性之Lambda表达式

Lambda,音标[ˈlamdə],中文翻译“拉姆达”,第11个希腊字母λ(大写Λ)。

1. 引入原因

JDK8引入Lambda表达式是为了简化匿名类相关代码。当接口比较简单,只有一个方法时,我们也不得不写许多无关业务的代码来实现匿名类。而Lambda表达式却允许将功能(functionality)视作方法参数或者视代码为数据,以省去非业务代码。

通过下边两段代码可以看出,Lambda表达式写法确实简洁很多。

//匿名类写法
new Thread(new Runnable() {
        @Override
         public void run() {
                System.out.println(Thread.currentThread().getName());
         }
}).start();
//Lambda表达式写法
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

2. 语法

Lambda表达式由3部分组成。

① 参数:小括号内以逗号分隔的形参列表。如果只有一个形参,可省去小括号。以下几种写法都是正确的:

//多个形参
(P1,P2,P3 ... Pn)
//一个形参,可以省去小括号
(P1)  或 P1
//没有形参,需要保留小括号
()

② 箭头 ->

③ 主体:表达式或大括号括内语句块

表达式可以理解为单行,不需要显式返回结果的语句;语句块则是多行或者需要显示返回结果的语句。语句块必须在大括号内。例如:

//表达式
System.out.println("I am an expression")
或者
x == y

//语句块
{
     boolean r = x == y;
     System.out.println(r ? "x等于y" : "x不等于y");
     return r;
}

3. 使用示例

假设我们在开发一个名为KK的即时聊天工具,里边有个很重要的功能就是根据条件查找好友。好友有姓名、年龄、性别和手机号码等属性:

import lombok.Data;
/**
 * 好友类
 */
@Data
public class Friend {

    public enum Sex {
        MALE, FEMALE, OTHER
    }

    private String name;
    private int age;
    private Sex gender;
    private String mobile;
}

好友人数上限是500个,我们可以暂时把所有好友都放在一个List里。

最开始,我们只想根据性别搜索,所以写了一个很简单的方法:

public static void search(List<Friend> friends, Sex gender) {
    for (Friend f : friends) {
        if (f.gender == gender) {
            System.out.println(f);
        }
     }
}

后来我们又想加上年龄作为搜索条件,于是修改成了这样:

public static void search(List<Friend> friends, Sex gender, int high, int low) {
    for (Friend f : friends) {
        if (f.gender == gender && f.age < high && f.age >= low) {
            System.out.println(f);
        }
    }
}

改了参数列表,会导致原本调用此方法的地方报错,无法编译。为了避免出现这种情况,我们决定把判断方法独立到一个类里,并修改查询方法,如下:

/**
 * 判断方法类
 */
public interface CheckFriend {
    /**
     * 入参都是Friend, 不论是Friend属性改变或者判断条件变化,都不会影响调用方
     * @param friend
     * @return true or false
     */
    boolean check(Friend friend);
}

//相应的查询方法修改成这样
public static void search(List<Friend> friends, CheckFriend cf) {
    for (Friend f : friends) {
        if (cf.check(f)) {
            System.out.println(f);
        }
    }
}

//调用查询方法时,使用匿名类
public static void main(String[] args) {
    List<Friend> friends = new ArrayList<>(512);
    Friend.search(friends, new CheckFriend() {
        @Override
        public boolean check(Friend friend) {
             return friend.getAge() > 15 && friend.getAge() <= 20 && friend.getGender() == Friend.Sex.MALE && friend.getName().startsWith("张三");
        }
    });
}

CheckFriend接口只有一个方法,我们可以使用Lambda表达式来简化代码:

//根据年龄、性别和姓名查询
Friend.search(friends, friend -> friend.getAge() > 15 && friend.getAge() <= 20 && friend.getGender() == Friend.Sex.MALE && friend.getName().startsWith("张三"));

如果其他调用方查询条件不同,也可以很方便的修改:

//根据性别和手机号码查询
Friend.search(list, friend -> friend.getGender() == Friend.Sex.MALE && friend.getMobile().startsWith("188"));

到这里,我们已经对Lambda表达式有了基本认识。虽然已经精简了一部分代码,但是还有优化空间,比如可以使用函数式接口Predicate<T>来替代CheckFriend:

//使用Predicate做判断
public static void searchByPredicate(List<Friend> friends, Predicate<Person> tester) {
    for (Friend f : friends) {
        if (tester.test(f)) {
            System.out.println(f);
        }
    }
}
//调用查询方法
Friend.searchByPredicate(list, friend -> friend.getGender() == Friend.Sex.MALE && friend.getMobile().startsWith("188"));

4. 目标类型推断

方法所期望的Lambda表达式类型就是目标类型,Java编译器根据Lambda表达式所在位置的上下文或情景来推断表达式类型即为目标类型推断。因此,上边的示例中,同一个

Lambda表达式:

friend -> friend.getGender() == Friend.Sex.MALE && friend.getMobile().startsWith("188")

既可以做为CheckFriend类型用到search方法,也可以做为Predicate类型用到

searchByPredicate方法。这也要求我们只能在 Java 编译器可以确定目标类型的情况下使用 Lambda表达式:

  • 变量声明
  • 赋值语句
  • 返回语句
  • 数组初始化器,比如初始化一个CheckFriend数组
CheckFriend[] arr = new CheckFriend[]{f -> f.getAge() == 18, f -> f.getAge() > 18};
  • 方法或构造函数参数

对于方法参数,java编译器还需要根据重载解析和类型参数来推断目标类型。

  • Lambda表达式主体
Function<Integer, Predicate<String>> func = x -> s -> s.length() > x;
  • 条件表达式 ?:
Function<Integer, String> func = true ? (i -> "Positive") : (i -> "Negative");
  • 强制类型转换

5. 访问外部变量

与匿名类一样,Lambda表达式也可以访问其外部作用域的局部变量,且这些变量也必须是final或有效final的;不同的是,Lambda表达式不会引入新的作用域。

假设有个接口Tester:

public interface Tester {
    void test(int x);
}

先来看一个匿名类的示例:

public class OuterClass {

    //OuterClass作用域
    public int x = 0;

    void haha(int x) {
        //haha方法内作用域
        int y = x;
        int z = 1;
        new Tester() {
            //tt匿名类作用域
            int y = 2; //可以定义与方法内作用域相同名称的变量

            @Override
            public void test(int x) {
                //z = 2; //想要改变外部作用域变量的值,报错
                System.out.println("test x: " + x);
                System.out.println("OuterClass x: " + OuterClass.this.x);
                System.out.println("y: " + y);
                System.out.println("tester y: " + this.y);
                System.out.println("haha z: " + z);
            }
        }.test(y);
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.haha(22);
    }
}

//结果
test x: 22
OuterClass x: 0
y: 2
tester y: 2
haha z: 1

再来看Lambda表达示的:

public class OuterClass {
    //OuterClass作用域
    public int x = 0;

    void haha(int x) {
        //haha方法内作用域
        int y = x;
        int z = 1;
        //Tester tt = x -> { //与外部作用域变量x重名,报错
        //  int y = 1; //与外部作用域变量y重名,报错
        Tester tt = w -> {
            //z = 2; //想要改变外部作用域变量的值,报错
            System.out.println("w: " + w);
            System.out.println("x: " + x);
            System.out.println("z: " + z);
            System.out.println("OuterClass x: " + this.x);
        };
        tt.test(y);
    }

    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.haha(22);
    }
}

//结果
w: 22
x: 22
z: 1
OuterClass x: 0

以上就是关于Lambda表达式的一些基础知识,后期我会在此基础上介绍JDK8中其他的特性,比如函数式接口等。

参考内容

[1] Lambda Expressions (The Java" Tutorials > Learning the Java Language > Classes and Objects)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值