一、简介
-
什么是Lambda?
Lambda就是一个匿名函数。
-
为什么使用Lambda?
对接口进行非常简洁的实现。可以说是一个语法糖。(原来要新建一个类实现接口,或使用内部类,匿名类)。
-
Lambda对接口要求?
要求接口中定义的必须要实现的抽象方法有且只有一个。这种接口又称为函数式接口。
java8之后接口可以包含静态方法和默认实现,这两种方法不算必须要实现的方法。
二、基础语法
-
lambda是一个匿名方法
方法包括:方法名,参数列表,返回值,方法体
匿名,所以不需要名字。
( ):用来写参数列表
{ }:用来描述方法体
->:标识这是lambda表达式,读作goes to
eg: (type parm ...) -> { ... }
//函数式接口示例 @FunctionalInterface public interface LambdaInterface { int test(int a,int b); } //实现上面的接口方法 LambdaInterface lambda1 = (int a,int b) -> { return a+b; } //lambda1 实现了接口,就可以使用该接口的方法 int ret = lambda1.test(10,20);
-
语法精简
- 参数类型可以省略,因为接口中已经定义好了;
- 当参数只有一个时,小括号可以不写,没有参数时必须写小括号;
- 当方法体只有一行时,大括号可以不写(Eclipse中jdk14好像不能写大括号);
- 当方法体只有一行,并且该行语句是一条返回语句,省略大括号的同时也必须省略return。
//上面lambda的精简写法: LambdaInterface lambda1 = (a,b) -> a+b;
-
说白了就是对一个函数式接口进行了匿名实现,然后就可以用这个接口的方法了。
三、使用案例
下面是一些lambda实现函数式接口的常用例子:
-
ArrayList.sort( )
当对我们自己定义的对象list进行排序时,sort方法需要一个参数,是一个比较器实现的参数。比较器是一个函数式接口,定义如下。
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); ... }
可以看到我们需要实现这个接口传给sort,这就可以用lambda表达式来做。
//假如有person类,有name,age域,希望能按照age降序排序 ArrayList<Person> list = new ArrayList<>(); //往list中存入一些Person对象 //现在直接sort是不行的,不知道按照什么比较,我们需要对sort传入比较器的实现 list.sort((o1, o2) -> o2.age - o1.age);
还有Collections.sort( ),Arrays.sort( )等sort也一样的。
-
TreeSet
自带排序的set,当存入自定义类型的对象时,我们就需要指定排序依据。通过传入比较器的构造方法构造TreeSet。
//还是Person类,存入treeset中,希望是降序的。 TreeSet<Person> set = new TreeSet<>((o1, o2) -> o2.age - o1.age); //由于TreeSet会去重,比较结果为0的,会被除去。上述方法就不能存年龄相同的对象, //可以修改lambda表达式的逻辑达到不去重效果 TreeSet<Person> set = new TreeSet<>((o1, o2) -> { if(o1.age >= o2.age){ return -1; }else{ return 1; } });
-
ActionListener
//ActionListener是一个函数式接口,触发后的执行动作 public interface ActionListener extends EventListener { /** * Invoked when an action occurs. * @param e the event to be processed */ public void actionPerformed(ActionEvent e); }
Timer定时器可以传入ActionListener的实现,定时触发事件,我们想每隔一秒打印当前时间,来看看怎么做。
//通过新建一个类来实现接口: class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); } } //然后创建这个类的实例就可以传入 ActionListener listener1 = new TimePrinter(); Timer t = new Timer(1000, listener1); t.start();
//通过lambda来做: //可以看到ActionListener的方法,无返回,有一个参数 ActionListener listener2 = Event -> { System.out.println("At the tone, the time is " + new Date()); }; //然后就可以用这个实例了 Timer t = new Timer(1000, listener2); t.start();
四、方法引用
方法引用是java8的新特性之一,是一种可以进一步简化lambda的方法。可以直接引用已有Java类或对象的方法或构造器,即要写的逻辑已有现成的,不需要自己再实现。
lambda表达式可用方法引用代替的场景可以简要概括为:lambda表达式的主体仅包含一个表达式,且该表达式仅调用了一个已经存在的方法。且lambda的参数和返回值与方法的一致。
java8方法引用有四种形式:
- 静态方法引用: ClassName::staticMethodName
- 构造器引用(引用构造函数): ClassName::new
- 类的任意对象的实例方法引用: ClassName::instanceMethodName
- 特定对象的实例方法引用: object::instanceMethodName
eg:
// lambda表达式使用:
Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(s -> System.out.println(s));
// 静态方法引用:
Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(System.out::println);
// 构造器引用:
Supplier<List<String>> supplier = ArrayList<String>::new;
// 类的任意对象的实例方法引用:
Arrays.sort(strs,(s1,s2)->s1.compareToIgnoreCase(s2));
// 转成方法引用:
Arrays.sort(strs, String::compareToIgnoreCase);
五、闭包
lambda代码块中可以使用外部(lambda外部)定义的变量,但是有一个重要的限制,即这个变量的值是不能改变的,既不能在lambda中改变,也不能在外部改变。说白了,如果你使用了外部变量,这个变量就好像变为了一个final变量,当你改变它时就会报错。这个特性又称为“闭包”。
lambda表达式定义的位置和它实际执行的位置往往不同,很可能延后很久才会执行。在定义时,传入的变量就将给定一个值,如果允许改变传进来的变量,在定义和执行之间又将该变量改变了,逻辑将会变得混乱,在并发执行时也将会不安全,所以禁止了这样做。
六、标准函数式接口
在jdk8中,引入了一个新的包java.util.function。这个包下有几十个定义好的接口,不过大致可以分为以下几类。
lambda搭配function包提供的函数式接口,使得编程更加简洁且容易理解。
我们可以根据自己的任务类型选用不同的接口,然后使用lambda实现即可调用。
如:
// 实现一个Predicate接口用于判断传入字符是不是"sb".
Predicate<String> pd = (s) -> s.equals("sb");
System.out.println("u r "+ pd.test("sb") + " sb.");
此外,接口Java API还提供了基本类型的规范接口,如接口IntFunction