Lambda表达式不再只是听说

1. Lambda表达式简介

1.1 什么是Lambda?

Lambda 表达式,也可称为闭包,可以看作为匿名内部类。它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

1.2 为什么要使用Lambda表达式?

使用Lambda表达式可以对一个接口进行非常简单的实现,接口的常规实现就是,实现类或匿名内部类,下面通过一个小的示例,对比三种实现方式

public class Test {
    public static void main(String[] args) {
        //1.使用接口实现类
        Comparator comparator = new MyComparator();
        comparator.compare(a,b);

        //2.使用匿名内部类
        Comparator comparator1 = new Comparator() {
            @Override
            public int compare(int a, int b) {
                return a - b;
            }
        };

        //3.使用Lambda表达式来实现接口
        Comparator comparator2 = (a,b) -> a - b;
    }
}

class MyComparator implements Comparator {

    @Override
    public int compare(int a, int b) {
        return a - b;
    }
}

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

通过对比,就可以看出Lambda表达式实现接口的简洁和可读性了。

1.3 Lambda对接口的要求

试想一下,如果一个接口中有多个方法,会是怎样的情形。如果方法参数列表相同,Lambda表达式无法分辨或者说是识别实现哪一个方法。虽然可以使用Lambda表达式对某些接口进行简单的实现,但是并不是所有的接口都能用Lambda表达式来实现。要求接口中定义的要实现的方法只有一个。

  • 在Java8对接口加了一个新特性:default,会默认实现,如果接口实现类不重写该方法,就会执行执行方法的默认逻辑。接口中default修饰的方法,对Lambda表达式无影响
  • @FuncionalInterface修饰函数式接口的,被修饰的接口中只能定义一个抽象方法,default修饰的方法除外。所以用Lambda表达式实现的接口,尽量用该注解修饰。

2. Lambda表达式基础语法

2.1接口准备
/**
 * 无返回值,无参数
 */
@FunctionalInterface
public interface LambdaNoneReturnNoneParameter {
    void test();
}
/**
 * 无返回值,单个参数
 */
@FunctionalInterface
public interface LambdaNoneReturnSingleParameter {
    void test(int n);
}
/**
 * 无返回值,多个参数
 */
@FunctionalInterface
public interface LambdaNoneReturnMultipleParameter {
    void test(int a, int b);
}
/**
 * 有返回值,无参数
 */
@FunctionalInterface
public interface LambdaWithReturnNoneParameter {
    int test();
}
/**
 * 有返回值,单个参数
 */
@FunctionalInterface
public interface LambdaWithReturnSingleParameter {
    int test(int a);
}
/**
 * 有返回值,多个参数
 */
@FunctionalInterface
public interface LambdaWithReturnMulpitleParameter {
    int test(int a, int b);
}
2.2 通过对上面接口的使用来了解Lambda语法
2.2.1 Lambda表达式基础语法

Lambda是一个匿名函数,包含参数列表,方法体,(参数名:参数类型,参数名:参数类型) -> {方法体}

  • (): 用来描述参数列表

  • {}: 用来描述方法体

  • ->: Lambda运算符

无返回值、无参数,接口的实现

LambdaNoneReturnNoneParameter lambda1 = () -> {
    System.out.println("No Parameter, No Return.");
};
lambda1.test();//No Parameter, No Return.

无返回值、单个参数,接口的实现

LambdaNoneReturnSingleParameter lambda2 = (int a) -> {
    System.out.println(a);
};
lambda2.test(2);//2

无返回值、多个参数,接口的实现

LambdaNoneReturnMultipleParameter lambda3 = (int a, int b) -> {
    System.out.println(a + b);
};
lambda3.test(1, 99);//100

有返回值、无参数,接口的实现

LambdaWithReturnNoneParameter lambda4 = () -> {
    System.out.println("with return, no parameter.");
    return 100;
};
int result = lambda4.test();
System.out.println(result);//100

有返回值、单个参数,接口的实现

LambdaWithReturnSingleParameter lambda5 = (int a) -> {
    return a * 2;
};
int result2 = lambda5.test(168);
System.out.println(result2);//336

有返回值、多个参数,接口的实现

LambdaWithReturnMulpitleParameter lambda6 = (int a, int b) -> {
    return a * b;
};
int result3 = lambda6.test(9, 10);
System.out.println(result3);//90

3.Lambda表达式语法精简

3.1参数类型精简

由于在接口的抽象方法中,已经定义了参数的数量和类型,所以在Lambda表达式中,参数类型可以省略
备注:如果要省略参数类型,则每一个参数的类型都要省略,千万不要只省略一部分参数。

LambdaNoneReturnMultipleParameter lambda1 = (a, b) -> {
    System.out.println(a + b);
};
lambda1.test(1,2);//3
3.2参数列表小括号精简

如果参数列表中,参数的数量只有一个,此时小括号可以省略

LambdaNoneReturnSingleParameter lambda2 = a -> {
    System.out.println(a);
};
lambda2.test(2);//2
3.3 方法体大括号精简

如果方法体中只有一条语句,此时大括号可以省略

LambdaNoneReturnSingleParameter lambda3 = a -> System.out.println(a);
lambda2.test(3);//3
3.4 return精简

如果方法体中只有一条语句,并且这条语句是返回语句,则在省略大括号的同时,也必须省略return

LambdaWithReturnMulpitleParameter lambda4 = () -> 4;
int result = lambda4.test();
System.out.println(result);//4

LambdaWithReturnMulpitleParameter lambda5 = (a, b) -> a + b;
int result2 = lambda5.test(1, 2);
System.out.println(result2);//3

4.Lambda表达式语法进阶

4.1方法引用

如果有多处需要实现同一接口执行同样的操作,每次都要重复Lambda表达式中方法体逻辑,可以采取方法引用来解决这个问题,将Lambda表达式方法体中逻辑抽取到某个类的静态方法中,即使多处需要实现接口,只需通过Lambda表达式进行方法引用即可。引用某个类的方法类名::方法,类似通过类名调用方法,方法需要是静态的。如果是需要引用非静态方法,还需要先实例化对象,然后调用方法,意义不太。

public class Test3 {
    public static void main(String[] args) {
        //方法引用:
        //可以快速的将一个Lambda表达式的实现指向一个已经实现的方法
        //方法的隶属者:: 方法名
        //注意:
        //1.参数数量和类型必须要和接口中定义的方法保持一致
        //2.返回值的类型也必须要和接口中定义的方法保持一致

        LambdaWithReturnSingleParameter lambda1 = a -> change(a);
        int result = lambda1.test(3);
        System.out.println(result);
        //方法引用:引用了change方法的实现
        LambdaWithReturnSingleParameter lambda2 = Test3::change;
        int result2 = lambda2.test(5);
        System.out.println(result2);
    }

    private static int change(int a) {
        return a * 2;
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
4.2构造方法引用

先定义一个Person类,它的无参构造和带参如下:

public class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("Person类的无参构造执行了.");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person类的有参构造执行了.");
    }
}

常规Lambda表达式实现:只有一条语句{}可省略

构造引用语法如下:

类名:: new

public class Test4 {
    public static void main(String[] args) {
        //常规Lambda表达式
        PersonCreator creator1 = () -> new Person();
        Person person1 = creator1.getPerson();//Person类的无参构造执行了.


        //无参构造引用
        PersonCreator creator2 = Person::new;
        Person person2 = creator2.getPerson();//Person类的无参构造执行了.

        //带参构造引用
        PersonCreatorWithParameter creator3 = Person::new;
        creator3.getPerson("小明", 12);//Person类的有参构造执行了.
        
    }
}
//需求:
interface PersonCreator{
    Person getPerson();
}

interface PersonCreatorWithParameter{
    Person getPerson(String name, int age);
}

5.Lambda表达式之综合案例

5.1 集合排序Comparator
public class Exercise1 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //ArrayList
        //需求:已知在一个ArrayList中有若干个Person对象,将这些Person对象按照年龄进行降序排序
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("xiaoming", 10));
        list.add(new Person("lilei", 11));
        list.add(new Person("hanmeimei", 12));
        list.add(new Person("lily", 9));
        list.add(new Person("lucy", 9));
        list.add(new Person("polly", 3));
        list.add(new Person("uncle wang", 40));
        //排序
        list.sort((a1, a2) -> a2.age - a1.age);
        System.out.println(list);
        //[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lily', age=9}, Person{name='lucy', age=9}, Person{name='polly', age=3}]
    }
}

再看Lambda表达式实现TreeSet集合排序,通过TreeSet构造,我们知道TreeSet自带排序功能

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

通过打印日志,发现age为9的lucy没有了,这是因为TreeSet的去重功能。与Lambda表达式无关。若想保留age为9的重复元素,可进行如下操作。

public class Exercise2 {
    public static void main(String[] args) {
        //需求:已知在一个ArrayList中有若干个Person对象,将这些Person对象按照年龄进行降序排序
        //会去重age为9的一个元素
        //TreeSet<Person> list = new TreeSet<>((a1, a2) -> a2.age - a1.age);
        
        //[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lily', age=9}, Person{name='polly', age=3}]
        //保留重复元素
        TreeSet<Person> list = new TreeSet<>((a1, a2) -> {
            if (a1.age >= a2.age) {
                return -1;
            } else {
                return 1;
            }
        });
        //[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lucy', age=9}, Person{name='lily', age=9}, Person{name='polly', age=3}]
        
        list.add(new Person("xiaoming", 10));
        list.add(new Person("lilei", 11));
        list.add(new Person("hanmeimei", 12));
        list.add(new Person("lily", 9));
        list.add(new Person("lucy", 9));
        list.add(new Person("polly", 3));
        list.add(new Person("uncle wang", 40));
       
        System.out.println(list);
    }
}
5.2集合遍历 forEach()
public class Exercise3 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //集合遍历
        //需求:已知在一个ArrayList中有若干个Person对象,将这些Person对象按照年龄进行降序排序
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);

        //将集合中的每一个元素都带入到方法accept中
        list.forEach(System.out::println);

        //输出集合中所有的偶数
        list.forEach(element -> {
            if (element % 2 == 0) {
                System.out.println(element);
            }
        });
    }
}
5.3 删除集合中满足条件的元素removeIf()
public class Exercise4 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //需求:删除满足条件的元素
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("xiaoming", 10));
        list.add(new Person("lilei", 11));
        list.add(new Person("hanmeimei", 12));
        list.add(new Person("lily", 9));
        list.add(new Person("lucy", 9));
        list.add(new Person("polly", 3));
        list.add(new Person("uncle wang", 40));

        //删除集合中年龄大于10的元素
        //常规实现
        /*ListIterator<Person> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Person element = iterator.next();
            if (element.age > 10) {
                iterator.remove();
            }
        }*/

        //Lambda表达式实现
        //将集合中每一个元素带入到test方法中,如果返回true,则删除该元素
        list.removeIf(element -> element.age > 10);
        System.out.println(list);
    }
}
5.4 线程实例化
public class Exercise5 {
    public static void main(String[] args) {
        //需求:开启一个线程,进行数字输出
        //常规操作
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        });
        thread1.start();
		
        //Lambda实现
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        });
        thread2.start();
    }
}

6.闭包问题

常规认知在java中,局部变量的生命周期在局部方法调用完毕时结束。也就是说在getNumber方法调用结束后,num应该销毁了。但是在getNumber方法中,又通过Lambda闭包提升了num的生命周期,这样就可以在外部获取方法中的局部变量了。

public class ClosureDemo {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        int n = getNumber().get();
        System.out.println(n);//10

    }

    //Supplier<T>  无参、返回值为T类型的接口
    private static Supplier<Integer> getNumber() {
        int num = 10;
        return () -> {
          return num;
        };
    }
}

在Lambda闭包中引用的变量,必须是常量,也就是说会默认添加final修饰符。

public class ClosureDemo2 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //Consumer<T>  参数类型为T 无返回值的接口
        int a = 9;
        //Lambda表达式使用局部变量时,默认添加了final修饰符
        Consumer<Integer> consumer = element -> System.out.println(a);
//        a++; //此时a 已经是final了,不可改变
        //(闭包)Lambda表达式中使用的局部变量应该是final,不可以改变
//        Consumer<Integer> consumer2 = element -> System.out.println(a++);
        consumer.accept(1);
    }
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值