Lambda表达式是Java8更新的一大新特性,与同期更新的Stream是此版本的最大亮点。
“这是你从来没有玩过的全新版本” —– 扎扎辉
-> 举个栗子 : 创建一个线程
//old
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1");
}
});
//lambda
Thread thread2 = new Thread(() -> {
System.out.println("thread2");
});
结构:(参数1,参数2) -> {代码块}
- “->” 符号前面是参数,可以有0个、1个、多个。0个参数时就写个(),1个参数时可以不写括号,多个参数时要写括号。参数类型最好写上,方便阅读,可不写,同样可以通过编译,不写的时候就涉及到”类型推断“这个新特性了。
- “->” 符号后面就是代码块,只有一行代码时可不写大括号”{}”,多行代码时要写,这里的代码块和普通方法中的代码块没有区别,也可以通过返回或者抛出异常来退出。
提下这里的new Runnable(),其实这里并不是实例化了一个接口,大家也知道接口不能实例化,参考内部类和匿名内部类的知识,这里应该是这样的:
- ①首先构造了一个”implements Runnable “的无名内部类(方法内的内部类)
- ②然后构造了这个无名local内部类的一个实例,(把①中构造的内部类实例化了)
- ③然后用Runnable来表示这个无名local内部类的type
此处new Thread 的方法,接收的是一个Runnable目标对象
回到Lambda,为什么可以不声明参数类型?
- 所有Lambda表达式中的参数类型都是由编译器推断得出的。
- 将Lambda表达式赋值给一个局部变量或者传递给一个方法作为参数,局部变量或方法参数的类型就是Lambda表达式的目标类型。
看几段代码:
JButton button = new JButton();
//old写法
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked");
}
});
//第一层:Lambda表达式写法
button.addActionListener(e -> System.out.println("button clicked"));
第二层:这是addActionListener()方法及签名
/**
* Adds an <code>ActionListener</code> to the button.
* @param l the <code>ActionListener</code> to be added
*/
public void addActionListener(ActionListener l) {
listenerList.add(ActionListener.class, l);
}
第三层:这是接口ActionListener
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e);
}
这里我标了3层,一层一层看,
- addActionListener(ActionListener l)方法接收一个ActionListener类型参数
- ActionListener接口**有且只有一个方法**actionPerformed(ActionEvent e)
- actionPerformed(ActionEvent e)有个参数,类型是ActionEvent
第一层声明Lambda表达式的时候,就是这一段代码:”e -> System.out.println(“button clicked”)”,可以断定,编译器通过上下文推断出:这里需要一个ActionListener()类型的匿名内部类,所以就把old写法简化成了这一行代码,这个接口唯一的方法接收的类型也可以知道就是ActionEvent,所以参数类型也省略不写了。编译成class文件的时候会加上。
这里就牵扯出另一个概念–函数式接口,有没有发现一个共同点,文中提到的Runnable接口、ActionListener接口都只有一个方法(或许是这样才能直接推断出参数的类型,如果有多个方法那不是不好确定是哪个方法,什么参数类型了)。
* - 可用作Lambda表达式类型的接口都有且只有一个方法。*
查看新版本(1.8开始)Runnable接口源码会发现多了一个注解:@FunctionalInterface,查看此注解源码,里面有一段注释这样说:
/**
*
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
* 翻译过来大概就是:可以创建功能接口的实例。lambda表达式、方法引用或构造函数引用。
* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
* /
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
最后提一个点,Lambda表达式中需要引用它所在方法里的变量时,变量只能是final或者既成事实上的final变量。换句话说,Lambda表达式引用的是值,是在使用赋给该变量的一个特定的值。
String name = "Mistra";
name = "A";
Runnable runnable2 = () -> {
System.out.println("runnable1" + name);
};
这样写的话 System.out.println(“runnable1” + name);处会报错:Variable used in lambda expression should be final or effectively final
如果试图多次改变变量的值,并在Lambda中引用它就会报错
什么是既成事实上的final变量?比如说这里name我就声明并赋值了,没有第二次改变它的值,它的值就是原始值并不会改变了,就是既成事实上的final变量。在这里值不会改变了,相当于跟用final修饰是一样的了。
最后贴代码,Lambda表达式的常见用法:
package mistra.com.jdk8.lambda;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import java.util.function.BinaryOperator;
/**
* @Author: Mistra.WangRui
* @Time 2018-09-01 15:04
* @Description: Lambda表达式的常见用法
**/
public class LambdaTest1 {
public static void main(String[] args) {
/**
* 1.遍历集合
*/
String[] strings = {"A", "B", "C", "D", "E"};
List<String> names = Arrays.asList(strings);
//"->"符号的前面就是参数,后面就是代码块,没有参数时前面就写空括号(),
// 代码块跟普通方法的代码块一样可以用返回或者异常来退出
names.forEach(name -> {
System.out.println(name);
});
//Java8双冒号操作符
names.forEach(System.out::println);
/**
* 2.代替匿名内部类
*/
JButton button = new JButton();
//old
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked");
}
});
//lambda 只有一个参数时可以省略括号,代码块只有一行时可以不写大括号{}
button.addActionListener(e -> System.out.println("button clicked"));
//old
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1");
}
});
thread1.start();
//lambda 没有参数,所以是空括号()
Thread thread2 = new Thread(() -> {
System.out.println("thread2");
});
thread2.start();
Runnable runnable1 = () -> {
System.out.println("runnable1");
};
runnable1.run();
/**
* 3.创建函数 变量add不是两个数字之和,而是将两个数字相加的那行代码
*/
BinaryOperator<Long> add = (x, y) -> x + y + 1;
Long result = add.apply(5l,5l);
System.out.println(result);//result=11
String name = "Mistra";
//name = "A";
Runnable runnable2 = () -> {
System.out.println("runnable1" + name);
};
runnable1.run();
}
}