1、前言
许多java初学者大多数都是以jdk1.5或jdk1.6为主,但jdk的成长史里,每发布的一个版本都会带来不少的新特性,这些新特性的出现都是为了弥补或者优化先前版本的不足和缺陷的,因此作为java开发者,应该跟上jdk的发展潮流,利用好新的特性,使自己的代码变得更加简洁、更加高效、更加优雅!
jdk8早在2014年3月18日就已经发布,虽说博主从事java开发已有两年多,但也是在去年才开始接触java8,跟大多数初学者一样,新认识的许多特性刚开始不是那么好理解,这也体现了强大特性背后的代码强度与复杂;而对于新的API,若是不常用,也容易忘记,但是博主经历后明白,当熟悉掌握好java8并灵活应用于自己的代码中,将会使得代码非常简洁、优雅,同时从另一当面也体现出了个人功底(不仅仅停留在老版本的java代码表面)。
在此博主建议,学习java8,先大致学习几大核心特性以及常用API,等熟悉掌握并能够灵活应用了,便可深入学习java8的源码,并且,当读者一认识到一个新特性后,之后所写的代码就应该开始尝试使用该新特性,正所谓熟能生巧,不然,仅是学习而不应用,很容易就忘记了的。
本专栏会陆续更新发布关于java8的一些学习体会与理解,从语法特性到API,再慢慢深入到源码层次,毕竟写博客也是一种学习,一种对自身的提炼。或许自己能理解的同时也能让别人理解,这才是真正的熟悉掌握吧!之后的每一篇博客若读者发现博主有问题之处,欢迎指出,共同学习,共同进步。
2、初始Lambda表达式
实例1、实现Runnable方式创建线程
(1)匿名内部类方式实现
//以匿名内部类的方式实现Runnable
@Test
public void test11(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("以匿名内部类的方式实现Runnable!");
}
}).start();
}
相信读者对上述代码并不陌生,接下来看Lambda表达式的方式来实现。
(2)Lambda表达式实现
//以Lambda表达式实现Runnable
@Test
public void test12(){
new Thread(() -> System.out.println("以Lambda表达式实现Runnable!")).start();
}
以上两种实现方式的代码执行效果完全一样,但是Lambda表达式却更加简洁!
实例2、自定义Comparator比较器
注:下述代码用到的Person类如下:
//Person实体
public class Person {
private String name;
private Integer age;
public Person() {}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}//getter、setter方法省略
(1)匿名内部类方式实现
//匿名内部类实现自定义排序
@Test
public void test31(){
//先根据年龄大小排序,再根据名字的自然顺序排
List<Person> list1 = Arrays.asList(new Person("kaven", 20), new Person("tom", 18), new Person("sam", 18), new Person("jim", 20));
list3.sort(new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
if (p1.getAge() == p2.getAge()) {
return p1.getName().compareTo(p2.getName());
}
return p1.getAge().compareTo(p2.getAge());
}
});
System.out.println("先根据年龄大小排序,再根据名字的自然顺序排:");
for (Person person : list1) {
System.out.println(person);
}
}
(2)以匿Lambda表达式方式实现
//使用Lambda表达式实现自定义排序
@Test
public void test32(){
//先根据年龄大小排序,再根据名字的自然顺序排
List<Person> list = Arrays.asList(new Person("kaven", 20), new Person("tom", 18), new Person("sam", 18),
new Person("jim", 20));
list.sort((p1, p2) -> {
if (p1.getAge() == p2.getAge()) {
return p1.getName().compareTo(p2.getName());
}
return p1.getAge().compareTo(p2.getAge());
});
System.out.println("先根据年龄大小排序,再根据名字的自然顺序排:");
list.forEach((e) -> System.out.println(e));
}
析:
通过以上两个实例,我们可以很明显发现,java8Lambda表达式的引入,使得原本很多代码的写法变得更加洁、 优雅,那么我们就来学习一下Lambda表达式的语法及其使用技巧。
3、Lambda表达式语法
标准语法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
“->”是Lambda表达式中的专有符号
其中,“->” 左边表示参数列表,右边表示方法体
通常,左边都会省略去参数类型,而当左边只有一个参数时,可以省略 () ,但无参时不可省略 () ,右边只有一条语句时,可以省略 {} 和 return 及分号";",所以,有下面的简洁版本的语法:statment1;简洁语法:
(param1, param2, ..., paramN) -> {
statment2;
//.............
return statmentM;
}
读者看到这,可能还是有点难理解Lambda表达式的语法,甚至看了之后,依然不知道怎么用。莫急,要想充分认识Lambda表达式,还得先学习与Lambda表达式密不可分的--函数式接口。
4、函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数。
在java8以前,接口只允许有抽象方法,但java8开始,可以允许有多个default方法实现和static方法实现,但只允许有一个抽象方法,否则就不叫函数式接口。如以下代码所示:
//自定义函数式接口
@FunctionalInterface
public interface MyFunc<T> {
T getValue(T t);
static void test1() {
//...
}
static void test2() {
//...
}
default void tesr3() {
//...
}
default void tesr4() {
//...
}
}
那么,为什么要能支持Lambda表达式,就必须是函数式接口呢?
这就是Lambda表达式的强大之处了!
且看普通的方法定义
void test(int x, int y) {
...
}
普通情况,要想调用一个方法并使用其方法体中的功能,就必须在定义该方法时定义好方法体内容,然后在调用时传参,传的参数类型只能是java中所允许的类型;
而Lambda表达式"->"左右两边分别是对应的函数式接口中的那个抽象方法中的参数列表和方法体,是的,Lambda表达式"->"右边就是该抽象方法的真正执行方法体,可以简单理解为Lambda表达式支持传的参数是方法体,然后在调用时,执行你所传的那个方法体。这相当于重写该抽象方法(但通常却需要重新定义一个类,然后实现该接口,再重写该抽象方法),但Lambda表达式让这个步骤和过程变得更加简洁、优雅!
而如果函数式接口中有第二个抽象方法,并且在写Lambda表达式时是没法指定方法名的,所以函数式接口中的抽象方法必须唯一,这样才能识别出Lambda表达式中的参数和方法体是传给谁了!
因此java8中用注解@FunctionalInterface来修饰函数式接口,它的作用仅是限定或者限制该接口中只允许有一个抽象方法,不允许有多个!当然@FunctionalInterface注解也不是必须的,只要你的接口只有一个抽象方法,就可以当做函数是接口!
在认识了函数式接口后,我们在回顾前文的两个实例和Lambda语法,实例1中,在new Thread(Runnable runnable)时,之所以可以传入一个Lambda表达式,是因为jdk源码中,Runnable本质就是一个函数式接口,因为该接口只有一个抽象方法run;实例2中的Comparator也是一个函数式接口,所以也支持Lambda表达式。那么重点来了,当你自定义的接口是一个函数式接口时,也将可以支持Lambda表达式。
下面,我们结合Comparator和Runnable来解析,到底该如何使用Lambda表达式。
//解析Lambda表达式的具体用法
public void test() {
new Thread(() -> {
//.....Runnable中的run方法逻辑
}).start();
/*
* Runnable接口中的run方法签名为:public abstract void run();
* 在Lambda表达式的语法中讲到:
* "->" 左边表示Lambda表达式所需要的参数,即对应函数式接口那一个抽象方法的参数(类型与个数都要匹配),
* 右边表示Lambda表达式所需要执行的功能,即对应函数式接口那一个抽象方法所需要执行的方法体
* 而run方法是一个无参且无返回值的抽象方法,所以 "->",左边就可以写成(),右边就可以写成{//...}
*/
List<Person> list = Arrays.asList(new Person("kaven", 20), new Person("tom", 18), new Person("sam", 18),
new Person("jim", 20));
list.sort((p1, p2) -> {
if (p1.getAge() == p2.getAge()) {
return p1.getName().compareTo(p2.getName());
}
return p1.getAge().compareTo(p2.getAge());
});
/*
* Comparator<T>中的compare的方法签名为:int compare(T o1, T o2);
* 可见,该方法需要两个类型相同的参数,并返回一个int类型的数值
* 那么Lambda表达式就可以写成 (p1, p2) -> {
* //...比较逻辑;
* return int类型数值;
* }
* 其中,"->"左边的参数名可以自定义,可以使用p1,p2,也可以使用其他变量名,如(x, y)都是可以的,
* 而参数的类型编译器可以自动识别,这也为什么要求是函数式接口才支持Lambda表达式了
*/
}
5、四大函数式接口
到此为止,已经把Lambda表达式的语法、函数式接口以及Lambda表达式的使用技巧讲解清楚了,那么,我们就该认识一下java8里提供的四大函数式接口,这四大函数式接口分别适用于不同的场景,也基本满足大量的需求,不然,也可以自定义函数式接口,应用于符合自己需求的应用场景。
/**
* Lambda表达式之四大内置核心的函数式接口
* 注:本文结合java8的另一个新特性Stream来解析四大函数式接口,因为Stream对函数式接口做了很好也很多的支持
* Stream的具体相关内容,会在本专栏的另一篇博客讲解到,这里仅是提前认识,过个眼
*
* @author xian
* @date 2018年1月9日
*/
public class Demo2 {
/**
* @FunctionalInterface
* public interface Consumer<T> {
*
* void accept(T t);
*
* }
* 解释:消费型接口,对类型为T的对象应用操作,接受一个泛型参数,无返回值
*/
@Test
public void test1() {
Consumer<String> consumer = (e) -> System.out.println(e);
consumer.accept("Hello World!");
//对于Consumer接口,Stream的foreach就是一个很好的例子,如下
/*
* foreach方法结构:void forEach(Consumer<? super T> action);
*/
}
/**
* @FunctionalInterface
* public interface Supplier<T> {
*
* T get();
*
* }
* 解释: 供给型接口,返回类型为T的对象
*/
@Test
public void test2(){
Supplier<String> supplier = () -> "who are you?";
String str = supplier.get();
System.out.println(str);
//对于Supplier接口,Stream的collect就是一个很好的例子,在此先不举例子
}
/**
* @FunctionalInterface
* public interface Function<T, R> {
*
*
* R apply(T t);
*
* }
* 解释: 函数型接口,对类型为T的对象应用操作,并返回结果,结果是R类型的对象。
*/
@Test
public void test3(){
Function<Integer, String> function = (e) -> {return String.valueOf(e);};
String result = function.apply(2018);
System.out.println("整形转换为字符串 : " + result);
//对于Function接口,Stream的map就是一个很好的例子,在此先不举例子
}
/**
* @FunctionalInterface
* public interface Predicate<T> {
*
*
* boolean test(T t);
*
* }
* 解释: 断定型接口,确定类型为T的对象是否满足某约束,并返回boolean 值。
*/
@Test
public void test4(){
Predicate<Integer> predicate = (e) -> {return e > 128;};
boolean b = predicate.test(127);
System.out.println(b);
//对于Predicate接口,Stream的filter就是一个很好的例子,如下:
List<String> list = Arrays.asList("Jim", "Tom", "Sam", "Kaven");
list.stream()
.filter((e) -> {return e.length() <= 3; })
.forEach((e) -> {System.out.println(e);});;
}
/*
* 总结:
* 1、java8内置的四大函数式接口,基本满足在基本需求,当然也可以自定义函数式接口
* 2、通过上述例子可以发现,不论是那种函数式接口,都只需把握住几点就够了,Lambda表达式中,
* (a)-> 左边表示Lambda表达式所需要的参数,即对应函数式接口那一个方法的参数(类型与个数都要匹配),
* (b)-> 右边表示Lambda表达式所需要执行的功能,即对应函数式接口那一个方法所需要执行的方法体
* 然后当你调用那个接口的方法时,就会自动去执行Lambda表达式 -> 右边的方法体
*/
}
6、总结
1、使用Lambda表达式,需建立在函数式接口的基础上2、Lambda表达式中, "->" 左边表示Lambda表达式所需要的参数,即对应函数式接口那一个抽象方法的参数(类型与个数都要匹配), 右边表示Lambda表达式所需要执行的功能,即对应函数式接口那一个抽象方法所需要执行的方法体。
3、同时,Lambda表达式在本文的作用相当于了匿名内部类的形式创建了对象,但却更加简洁,其实也更加高效,因为java8做了很好的优化。
4、本文很好的告诉了读者,Lambda表达式,不论是用在哪里异或是怎么用,都要有函数式接口的支持
注:希望本文对读者有帮助!欢迎评论,有问题请指出!转载请注明出处!