一、示例引入
我们将带有参数变量的表达式,称之为Lambda表达式,先看一个基本的示例,对其有一个基本的认识。class User{
public String name;
public int score;
public User(String name, int score) {
super();
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "User [name=" + name + ", score=" + score + "]";
}
}
@Test
public void arrayComparableTest(){
User[] us = new User[]{new User("张三", 10), new User("李四", 15), new User("王五", 12)};
//以前的写法
Arrays.sort(us, new Comparator<User>() {
// 对数组中的元素进行排序
@Override
public int compare(User o1, User o2) {
return Integer.compare(o1.score, o2.score);
}
});
//分析:
//1. sort方法,第二个参数,一定是一个Comparator接口。
//2. Comparator接口,一定需要实现一个compare方法。
//3. compare方法,一定需要返回一个int类型的结果。
//优化1 使用Lambda表达式,将一定、必不可少的东西删除了,让编译器自己去推导出结果
Arrays.sort(us, (User o1, User o2) -> { return Integer.compare(o1.score, o2.score); });
//优化2 compare方法中只有一行代码,并且,这一行代码,一定会返回一个int类型的值。那么,我们去删除它。
Arrays.sort(us, (User o1, User o2)-> Integer.compare(o1.score, o2.score));
//优化3 Compare方法,有两个参数,这两个参数的类型,可以通过传递的数组来推导元素类型,一定是User类型。那么,我们删除它
Arrays.sort(us, (o1, o2)-> Integer.compare(o1.score, o2.score));
//优化4 因为java8的comparator接口中有comparing静态方法,它可以像下面这样用:
Arrays.sort(us, Comparator.comparingInt((o) -> o.score));
//优化5 方法引用
Arrays.sort(us, Comparator.comparingInt(User::getScore));
System.out.println(Arrays.toString(us));
}
二、Lambda表达式基本语法
java中,引入了一个新的操作符 ->
,该操作符在很多资料中,称为箭头操作符,或者lambda操作符;
Lambda表达式的基本语法:方法参数列表 -> 表达式
箭头操作符将lambda分成了两个部分:
1. 左侧:lambda表达式的参数列表
2. 右侧:lambda表达式中所需要执行的功能,即lambda函数体
参数列表:是接口的方法参数列表
表达式:是接口方法的具体逻辑实现。
1、参数列表
1.1、方法没有参数
没有参数,需要注意的是,参数列表的 () 是一定不能省略的。因为括号才表示是方法的参数列表。
示例
new Thread(() -> System.out.println("没有参数")).start();
1.2、方法有一个参数
- 如果参数写类型,那么,必须写方法的()
Frame f = new Frame("lambda");
f.setBounds(100, 100, 100, 300);
Button b = new Button("按钮");
b.addActionListener((ActionEvent event) -> System.out.println("按钮被点击"));
f.setVisible(true);
- 如果参数不写类型(编译器可以推导出来参数的类型),那么,可以省略方法的()
Frame f = new Frame("lambda");
f.setBounds(100, 100, 100, 300);
Button b = new Button("按钮");
b.addActionListener(event -> System.out.println("按钮被点击"));
f.setVisible(true);
1.3、方法有两个或多个参数
如果是两个或者多个参数,那么需要注意,不管是否有参数类型,都必须有()
Integer[] arr = new Integer[]{1,5,3,4};
Arrays.sort(arr, (x, y) -> Integer.compare(x, y));
System.out.println(Arrays.toString(arr));
1.4、如果有其他的修饰符修饰
可以用final修饰局部变量,此时,如果要写额外的修饰符的时候,参数必须得有类型。不管是一个参数还是多个参数。
Integer[] arr = new Integer[]{1,5,3,4};
Arrays.sort(arr, (final Integer x, final Integer y) -> Integer.compare(x, y));
System.out.println(Arrays.toString(arr));
2、表达式
- 如果表达式只有一行代码
可以不需要{},直接写,不能写return关键字。编译器帮助我们推导return。 - 如果表达式超过一行
那么这一段代码,就必须写{},就必须遵循方法的规则,并且如果方法有返回,就必须有返回语句。
3、变量作用域
this,在匿名内部类中,如果我们使用this,那么,这个this其实是匿名内部类对自己的引用。但是,在Lambda表达式中的this,却是指向的该Lambda表达式所在方法,这个方法所在的类的引用。这一点需要跟以前的情况区分开。
即:
匿名类中的this是“匿名类对象”本身;
Lambda表达式中的this是“调用Lambda表达式的对象”。
三、方法引用
方法引用我们可以把它看做是 仅仅调用特定方法的Lambda的一种快捷写法。
示例:
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println
这里,我们引入一种新的语法结构 ::
,如果Lambda表达式中,接口方法的实现,只调用其他方法来真正完成逻辑,并且接口方法的参数原封不动的传递给了那个完成逻辑的方法
,那么我们就可以使用这种语法。
在Lambda表达式中,支持三种方法引用:
- 类 :: 静态方法
(args) -> ClassName.staticMethod(args)
//可以写成
ClassName::staticMethod
- 对象 :: 普通方法
(arg0, rest) -> arg0.instanceMethod(rest)
//可以写成
ClassName::instanceMethod
示例一:(系统方法)
//例如
@Test
public void test3(){
Integer[] arr = new Integer[]{1,5,3,4};
// 对象 :: 普通方法
Arrays.sort(arr, this::myCompare);
System.out.println(Arrays.toString(arr));
}
public int myCompare(int x, int y){
return Integer.compare(x, y);
}
示例二:(自定义)
// 定义的函数式接口
@FunctionalInterface
public interface IWork {
void work(int x, int y);// 接口方法没有返回值。
}
public class lambdatest {
public void wrap(IWork work){
System.out.println("some work");
work.work(3, 4);
}
@Test
public void test1(){
// IWork work = this::getSum;
// IWork work2 = (x,y)-> getSum(x,y);
this.wrap(this::getSum);// 使用对象来调用普通方法,注意,这个方法是有返回值的。
}
public int getSum(int x, int y){
System.out.println(x + y);
return x + y;
}
}
构造函数示例:
//不带参数的构造函数
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
//等价于
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
//带参数的构造函数
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);
//等价于
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);
//带有两个参数的构造函数
iFunction<String, Integer, Apple> c3 = Apple::new;
Apple c3 = c3.apply("green", 110);
//等价于
BiFunction<String, Integer, Apple> c3 =(color, weight) -> new Apple(color, weight);
Apple c3 = c3.apply("green", 110);
//不将构造函数实例化,但可以引用它,这有一些有趣的应用,如下:
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static {
map.put("apple", Apple::new);
map.put("orange", Orange::new);
// etc...
}
//利用构造函数引用,新创建了一个方法,用两个参数可以得到具有制定重量的不同水果
public static Fruit giveMeFruit(String fruit, Integer weight){
return map.get(fruit.toLowerCase())
.apply(weight);
}