java8新特性(一)------Lambda表达式
前言
博主之前一直接触的是java7或者6版本的jdk,一直听说java8新特性中很多东西比较好用,也一直有想去了解的想法,最近刚好有时间去研究一下,记录在这里.
正文
1. Lambda表达式的概念
lambda表达式是一个匿名函数,即没有函数名的函数,它需要函数式接口的支持.
所谓函数式接口,就是有且只有一个抽象方法,但是可以有多个非抽象方法的接口,这种接口可以被隐式的转换为lambda表达式.
在java8以前,也有很多的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator
java.io.FileFilter
JDK1.8新增了的函数式接口有java.util.function
.
2. Lambda表达式的基础语法
首先我们需要知道,在java8中引入了一个新的操作符->
,该操作符可以称为箭头操作符或者Lambda操作符,该操作符将Lambda表达式拆分为两部分:
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中所需执行的功能,也叫Lambda体
实际上,Lambda表达式其实就是对函数式接口的简洁实现,分隔符左边是函数式接口中抽象方法的参数,右边是该方法执行的逻辑内容
3. lambda表达式的语法格式(根据函数式接口方法的类型来分)
-
无参数,无返回值
格式:
() -> System.out.println(Hello Lambda!);
示例:
@Test public void test1() { //使用匿名内部类的方式 Runnable r = new Runnable() { @Override public void run() { System.err.println("Hello Word"); } }; r.run(); //使用Lambda表达式 Runnable r1 = () -> System.out.println("Hello Lambda!"); r1.run(); }
可以看到,使用Lambda表达式,代码看起来简洁多了.
-
一个参数,无返回值(如果只有一个参数,
()
可以省略)格式:
(x) -> System.out.println(x)
示例:
@Test public void test2() { Consumer<String> c = (x) -> System.out.println(x); c.accept("测试方法!"); }
-
有两个以上的参数,有返回值,且Lambda体中有多条语句
格式:
(x, y) -> { 函数逻辑... return 返回值; }
示例:
@Test public void test3 () { Comparator<Integer> c = (x, y) -> { System.out.println("测试方法!"); return Integer.compare(x, y); }; int i = c.compare(10, 4); }
对于以上的示例,还可以简化:如果只有只有一条Lambda体重只有一条语句,Lambda体中的大括号和return可以省略不写,所以上述示例可以简写如下:
@Test public void test4 () { Comparator<Integer> c = (x, y) -> Integer.compare(x, y); int i = c.compare(10, 4); }
在了解了以上的语法以后我们可以来简单的练习一下:
首先我们创建一个函数式接口:
@FunctionalInterface//这个会在后面说明
public interface TestFuncation {
Integer getValue(Integer num);
}
然后我们在测试类中测试调用:
@Test
public void test5() {
int a = operation(200, (y) -> y + 200);
System.out.println(a);
}
public Integer operation(Integer num, TestFuncation tf) {
return tf.getValue(num);
}
可以看到,在调用operation
方法的时候,我们现在不需要用匿名内部类的方式去实现他的方法,只需要使用Lambda表达式就可以很简单的将我们的逻辑写出来,在这里,Lambda表达式其实就是函数式接口对应的抽象方法的一个实现.
4. 注解@FunctionalInterface
在上面的例子中,我们看到有一个注解@FunctionalInterface
,这个注解是Java8为函数式接口引入的一个新注解,主要用于编译级别的错误检查,加上该注解,当我们定义的接口不符合函数式接口的定义时,编译器会报错.
注意:加不加该注解对于接口是不是函数式接口没有影响,此注解只是为了提醒编译器去检查接口是否包含一个抽象方法
函数式接口需要注意的地方:
-
函数式接口里允许定义默认方法
函数式接口中可以包含默认方法,因为默认方法不是抽象方法,其中有一个默认实现,所以是符合函数式接口的定义的,如下的代码就不会报错:
@FunctionalInterface interface GreetingService { void sayMessage(String message); default void doSomeMoreWork1() { // Method body } default void doSomeMoreWork2() { // Method body } }
-
函数式接口中允许定义静态方法
函数式接口中可以包含静态方法,因为静态方法不能是抽象方法,是已经实现的,所以符合函数式接口的定义,如下代码也不会报错:
@FunctionalInterface interface GreetingService { void sayMessage(String message); static void printHello(){ System.out.println("Hello"); } }
-
函数式接口允许定义
java.lang.Object
中的pulic
方法函数式接口可以包含
Object
汇总的public 方法,这些方法对于函数式接口来说,不被当成是抽象方法,因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自该类中对这些抽象方法的实现,如下代码也不会报错:@FunctionalInterface interface GreetingService { void sayMessage(String message); @Override boolean equals(Object obj); }
5. Java8四大核心函数式接口
我们发现,Lambda表达式需要函数式接口的支持,但是我们在实际的使用中不可能去自己写每一个函数式接口.其实我们不必发愁,在Java8中内置了很多的函数式接口,其中的功能已经满足我们的使用,现在让我们来一起看一下吧.
/*
*Java8内置的四大函数式接口
*
*Consumer<T>: 消费型接口
* void accept(T t);
*
*Supplier<T>: 供给型接口
* T get();
*
*Function<T, R>: 函数型接口
* R apply(T t);
*
*Predicate<T>: 断言型接口
* boolean test(T t);
*/
我们在使用的时候可以直接用Lambda表达式来使用这些接口的方法.除了以上这4个函数式接口,还有很多的子接口,有着丰富的功能,可以参考下图:
6. 方法引用和构造器引用
① 方法引用
所谓方法引用:如果Lambda体中的内容已经有方法实现了,我们就可以使用方法引用,也可以说,方法引用是Lambda表达式的另一种表现形式.
方法引用的格式有三种:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
注意:
1. 它要求该方法引用的参数和返回值和函数式接口的方法返回值、参数类型一致.
2. 若Lambda表达式参数列表中的第一个参数是示例方法的调用者,且第二个参数是实例方法的参数时,可以使用类名::实例方法名
的方式
首先我们来看看第一种形式,如下代码即可用方法引用:
@Test
public void tes6() {
User user = new User("张三", 28);
//使用Lambda表达式的普通形式
Supplier<String> sup = () -> user.getName();
String str = sup.get();
System.out.println(str);
//我们发现,其实user.getName这个方法已经存在了,且方法的返回值符合方法引用的规则,因此可以如下编写代码
Supplier<String> sup1 = user::getName;
String i = sup1.get();
System.out.println(i);
}
下面是第二种方式的栗子:
@Test
public void test7() {
Comparator<Integer> cp = (x, y) -> Integer.compare(x, y);
int i = cp.compare(4, 5);
System.out.println(i);
//类::静态方法名
Comparator<Integer> cp1 = Integer::compare;
System.out.println(cp.compare(4, 5));
}
再来看看第三种形式的方法引用(此种方式要求Lambda表达式参数列表中的第一个参数是示例方法的调用者,且第二个参数是实例方法的参数)
@Test
public void test8() {
//普通的Lambda表达式
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("aaa", "aaa"));
//使用方法引用 类::实例方法名
BiPredicate<String, String> bp1 = String::equals;
System.out.println(bp1.test("aaa", "aaa"));
}
② 构造器引用
格式: ClassName::new
**注意:**需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
示例代码:
@Test
public void test9() {
//普通Lambda表达式
Supplier<User> sp = () -> new User();
//构造器引用的方式
Supplier<User> sp1 = User::new;
}
总结
Lambda表达式的使用到这里就说的差不多了,在我们的日常开发中,合理的使用Lambda表达式,简化代码,能够增加代码的可读性,当然,也不能一味的滥用.在下一篇将会学习Java8的另一个特性------Stream API.