Java8新特性之Lambda表达式学习
Lambda表达式
Lambda表达式是Java8推出的非常强大的特性之一。借助它,我们对一些接口的简单实现不再需要写那么多繁琐,多余的代码,只需写一些关键性的代码,即简洁又优雅,装X与无形之中。但是正因如此,可读性也就不要奢求了。
使用要求:待实现的接口中只能有一个需要被实现的方法,不包括default修饰的方法。而这种接口,就称之为函数式接口,通常用@FunctionalInterface注解修饰。可参考另一篇博文函数式接口
基础语法
语法形如 () -> {}
->
是Lambda运算符,->
的左边部分()
是接口对应方法的入参列表,右边{}
是接口对应方法的实现体
接口中待实现的方法:无入参,无返回值(以Runnable接口为例)
@FunctionalInterface public interface Runnable { public abstract void run(); } @Test public void test1() { Runnable runnable = () -> {System.out.println("hello world!");}; runnable.run(); } //对于实现体中只包含一句话的情况,可以将{}省去不写 Runnable runnable = () -> System.out.println("hello world!");
接口中待实现的方法:有一个入参,无返回值(以Consumer接口为例,该接口为四大内置接口之一)
@FunctionalInterface public interface Consumer<T> { void accept(T t); ... } //测试:将字符串作为入参,做打印处理 @Test public void test1() { Consumer<String> consumer = (str) -> System.out.println(str); consumer.accept("hello consumer!"); } //可以看出入参的参数类型是可以省略不写的(写上也没错) //另外,若参数列表中只有一个参数,则()可以省略不写,习惯上还是写上 Consumer<String> consumer = str -> System.out.println(str);
接口中待实现的方法:有两个及以上的参数,有返回值,有多条语句(以Comparator接口为例)
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); } //测试:将一串数字按升序排序 @Test public void test3() { Comparator<Integer> comparator = (x, y) -> { System.out.println("hello lambda!"); return Integer.compare(x, y); }; List<Integer> list = Arrays.asList(1, 3, -5, 2); Collections.sort(list, comparator); list.forEach(System.out::println);//一个循环打印的语句 }
接口中待实现的方法:参数不定,有返回值,但是实现体中只有一条语句(仍以Comparator接口为例)
//测试:将一串数字按升序排序 @Test public void test3() { Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); List<Integer> list = Arrays.asList(1, 3, -5, 2); Collections.sort(list, comparator); list.forEach(System.out::println);//一个循环打印的语句 } //可见当只有一条语句时,return和{}都可以省略
简答总结:
左右遇一括省:"->"左边或右边均只有一个参数或一条语句时,()或{}可以省略
右侧只有一条语句,且有返回值时,return可省略
参数类型可以省略
方法引用
- 若Lambda表达式右侧的实现体中已经有方法实现了,那我们就可以使用“方法引用”。
引用条件:被引用方法的参数列表与返回值类型需要与接口中待实现的方法的参数列表与返回值类型保持一致。
三种语法格式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
示例如下:
对象::实例名(以Consumer接口和PrintStream中的println方法为例)
@Test public void test4() { PrintStream ps = System.out; Consumer<String> consumer = ps::println; //等同于 Consumer<String> consumer = System.out::println; //等同于 Consumer<String> consumer = (str) -> System.out.println(str); consumer.accept("hello lambda!"); } //因为Consumer中void accept(T t)方法 //可以对应于PrintStream中的void println(T t)方法 //所以可以使用方法引用,且入参列表与方法的()符号可以省略
类::静态方法名(以Comparator接口和Integer类中静态方法compare为例)
@Test public void test5() { Comparator<Integer> comparator = Integer::compare; //等同于Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); List<Integer> list = Arrays.asList(1, 3, -5, 2); Collections.sort(list, comparator); list.forEach(System.out::println); } //Comparator中int compare(T o1, T o2) 对应于 Integer中static int compare(int x, int y)
类::实例方法名(以BiPredicate接口和String类中实例方法equals为例)
@Test public void test6() { BiPredicate<String, String> bp = String::equals; //等同于BiPredicate<String, String> bp = (x, y) -> x.equals(y); String x = "hello"; String y = "hello"; System.out.println(bp.test(x, y)); } //使用条件:当参数列表(x, y)中x为该方法的调用者,y为该方法的入参时,才能使用。
构造器引用
- 同方法引用类似,只是书写格式不同
- 格式为:ClassName::new
示例
现假定有一个People类,属性如下(有三个构造方法):
public class People { private String name; private Integer age; private Integer salary; public People() {} public People(String name) { this.name = name; } public People(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "People [name=" + name + ", age=" + age + ", salary=" + salary + "]"; } }
利用Supplier接口生成一个People对象
@Test public void test7() { Supplier<People> supplier = People::new; //Supplier<People> supplier = () -> new People(); People people = supplier.get(); System.out.println(people); } //可以看到会打印出属性值均为null的People对象 People [name=null, age=null, salary=null]
疑问:一个类中有可能有很多个构造方法,那到底会调用哪一个呢?(先看下面的示例)
@Test public void test8() { Function<String, People> function = People::new; // Function<String, People> function = (name) -> new People(name); People people = function.apply("Tony"); System.out.println(people); } //运行结果: People [name=Tony, age=null, salary=null]
可以看到这一次利用Function接口的apply方法生成的people对象中,name属性是有值的,这说明在生成对象时调用的构造器是有参构造器public People(String name){}。
同样是People::new却调用了不同的构造器,由此可以明白构造器引用具体选择哪个构造器取决于接口中待实现方法的入参。
上述所有示例中多处用到了Java8内置的四大核心函数式接口,可参考函数式接口