Lambda是java8出的新特性,之前很少用Lambda表达式写代码,现在慢慢习惯用Lambda表达式,并且记得Lambda表达式的知识,方便日后查阅和温习。
Lambda表达式
Lambda表达式是在java8版本中引入的一种新的语法元素和操作符,这个操作符为“->”。
为了更加掌握Lambda表达式的使用,从那下面几个方面去学习lambda表达式:
- 什么是Lambda
- Lambda的用途意义
- Lambda的语法
- Lambda表达式的语法格式
- Lambda的函数式接口@FunctionalInterface的使用说明
- 了解Java内置函数式接口
- 方法引用
- 构造器引用
- 数组引用
- 集合排序
- list遍历
- 返回集合中符合条件的元素
- 对象间的元素比较
什么是Lambda
Lambda就是一个匿名函数,其实可理解为没有函数名的函数。其实就是函数式接口的一种表现形式。
Lambda的用途意义
就是让你的代码更加简洁,使用起来更加方便。同时也是简化了匿名委托的使用,这里出现了一个专有名词匿名委托,那么什么是匿名委托呢?下面通过代码了解匿名委托。
public class LambdaTest{
public static void main(Strign[] args){
//一个匿名内部类 java7之前
Runnable runnable = new Runnable(){
@Override
public void run(){
System.out.println("普通写法");
}
} ;
//java8 用lambda表达式
Runnable rb = ()->{
System.out.println("lambda表达式写法");
};
}
}
上面代码看出,new Runnable(){}就是一个匿名委托。如果都将一块代码块赋值给变量,那么java7之前写法与java8的lambda表达式的写法,很显然lambda表达式写法更加简洁优雅,代码量也少,也达到了简化匿名委托的效果。
Lambda的语法
Lambda表达式形式:(参数)->{}
- 左侧:指定了Lambda表达式所需要的所有参数。
- 右侧:指定了Lambda体,Lambda体中是Lambda表达式执行的功能代码块。
下面通过一个无参与带参的Lambda表达式作例子说明,并与普通写法作对比,这样更加容易理解和掌握lambda表达式。
//无参写法
Runnable runnable = new Runnable(){
@Override
public void run(){
System.out.println("普通写法");
}
} ;
//java8 用lambda表达式
Runnable rb = ()->System.out.println("lambda表达式写法");
//带参写法
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
//执行的功能代码块
}
});
mListView.setOnItemClickListener((parent, view, position, id) -> {
//执行的功能代码块
});
上述代码简单易懂,那么我们进一步去学习Lambda表达式的语法和用法。
Lambda表达式的语法格式
下面我们就了解一下Lambda表达式的语法格式有那些:
-
语法格式一:无参数,无返回值。
()->System.out.println("lambda表达式写法");
-
语法格式二:有一个参数,并且无返回值。
(a)->System.out.println(a);
-
语法格式三:只有一个参数,小括号可以省略不写。
a->System.out.println(a);
-
语法格式四:如果Lambda体中只有一条代码语句,return和大括号都可以省略不写。
(int a,int b)->a+b;
-
语法格式五:有两个或两个以上的参数,有返回值,并且Lambda体中有多条代码语句。
(int a,int b)->{ System.out.println("函数式接口"); return a+b; };
-
语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即“类型推断”。
(Integer a,Integer b)->Integer.compare(a,b);
下面通过代码实现上面的六种语法格式去学习,当然还有一些高级用法(如Stream中使用Lambda表达式,Stream放到一下次去学习),我们先从简单的基础的开始学习:
public class TestLambda {
public static void main(String []args){
//语法格式一
LambdaCallback1 callback1 = ()->System.out.println("无参数,无返回值");
callback1.test();
//语法格式二
LambdaCallback2 callback21 = (s)->System.out.println(s);
callback21.test("语法格式二");
//语法格式三
LambdaCallback2 callback22 = s->System.out.println(s);
callback22.test("语法格式三");
//语法格式四
LambdaCallback4 callback31 = (a,b)->a+b;
System.out.println("语法格式四 "+callback31.test(4,5));
//语法格式五
LambdaCallback4 callback32 = (a,b)->{
System.out.println("语法格式五 函数式接口");
return a+b;
};
System.out.println("语法格式五 "+callback32.test(1,9));
//语法格式六
LambdaCallback3 callback4= (s,a)->{
System.out.println("s: "+s);
System.out.println("s: "+a);
};
callback4.test("语法格式六",110);
}
@FunctionalInterface
public interface LambdaCallback1{
void test();
}
@FunctionalInterface
public interface LambdaCallback2{
void test(String str);
}
@FunctionalInterface
public interface LambdaCallback3{
void test(String str,int a);
}
@FunctionalInterface
public interface LambdaCallback4{
int test(int str,int a);
}
}
Lambda的函数式接口@FunctionalInterface的使用说明
- Lambda表达式需要“函数式接口“的支持,函数式接口:接口中有且仅有一个抽象方法的接口,称作为函数式接口。
- 使用注解@FunctionalInterface修饰,表示检查此接口是一个符合Java语言规范定义的函数式接口。
- 函数式接口默认继承java.lang.Object,如果重写了java.lang.Object中的抽象方法,也不算是抽象方法
如果一个接口中包含不止一个抽象方法,那么使用@FunctionalInterface注解编译时会报错。例子如下代码:
@FunctionalInterface
public interface LambdaCallback5{
void test();
int sum(int a,int b);
}
上述LambdaCallback5类编译时报错:multiple non-overriding abstract methods found in interface Action
@FunctionalInterface的正确使用如上述代码的LambdaCallback1~4接口。下面这个接口也是一个正确的函数式接口:
@FunctionalInterface
public interface LambdaCallback6 {
// 抽象方法
void add();
// java.lang.Object中的方法不是抽象方法 属于重写Object中的方法
boolean equals(Object var1);
// default 不是抽象方法 默认方法,通过实现类直接调用,不需要实现类实现该方法,提高了扩展性。
default void defaultMethod(){
}
// static 不是抽象方法 静态方法,直接接口调用。跟普通的static方法是一样的,不需要实现类去调用。
static void staticMethod(){
}
}
默认(default)方法只能通过接口的实现类来调用,不需要实现方法,也就是说接口的实现类可以继承或者重写默认方法。
静态(static)方法只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。就跟普通的静态类方法一样,通过方法所在的类直接调用。
有人会想如果不添加@FunctionalInterface注解的接口能不能支持lambda表达式,其实通过上面使用说明的第二点特点,就可以知道,不添加@FunctionalInterface注解,只要接口符合函数式接口
也是支持lambda表达式:看下面代码:
public class TestLambda {
@Test
public void mainTest(){
LambdaCallback7 callback7= (name,age)->"名字: "+name+" 年龄: "+age;
System.out.println(callback7.test("lambda",3));
onTest(callback7,"android",5);
}
private void onTest(LambdaCallback7 callback7,String name,int age){
System.out.println(callback7.test(name,age));
}
public interface LambdaCallback7{
String test(String name,int age);
}
}
//打印出的结果为
I/System.out: 名字: lambda 年龄: 3
I/System.out: 名字: android 年龄: 5
找到源码验证,其实就是JDK中有默认支持处理。如果是单接口(单接口是指一个接口类里有且仅有一个抽象方法)
就可以转lambda表达式。@FunctionalInterface注解不是必须的,加不加都无所有了,如果添加了这个注解只是方便了编译器检查和提示作用而已,如果不符合函数式接口的定义,则注解会报错,这样减少一些误操作。同时也验证了@FunctionalInterface的使用说明中的第二点特点。
了解Java内置函数式接口
平时我们在写安卓代码时,你也会发现那些xxxListener接口也是内置的函数式接口,如OnClickListener、OnLongClickListener、OnTouchListener等等。前面写到过的Runnable接口也是内置的函数式接口。下面例举一些java内置的函数式接口。
接口 | 参数 | 返回类型 | 示例 |
---|---|---|---|
Predicate |
T | boolean | 我是断言函数式接口 |
Consumer |
T | void | 输出一个值 |
Function |
T | R | 获取Student对象的分数 |
Supplier |
None | T | 工厂方法 |
UnaryOperator |
T | T | 逻辑非(!) |
BinaryOperator |
(T,T) | T | 求两个数的最大值 |
例子:
//xxxListener
//普通写法
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO
}
});
//lambda表达式写法
findViewById(R.id.button).setOnClickListener( v->{
//TODO
});
//Consumer
Consumer consumer1 = new Consumer() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer1.accept("普通写法");
Consumer consumer2 = s->System.out.println(s);
consumer2.accept("lambda内置写法");
//对象的方法引用 下面会详细讲
Consumer consumer3 = System.out::println;
consumer3.accept("对象的方法引用的写法");
方法引用
当要传递给Lambda体的操作已经有了实现的方法时,可以使用方法引用。(实现抽象方法的参数列表必须与方法引用方法的参数列表保持一致)
方法引用:使用操作符“ :: ”将方法名和对象或类的名字分隔开( ClassName::MethodName )。有三种主要使用情况,如下:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
/**
* Consumer的作用顾名思义,是给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作。
* accept(T t)是一个抽象方法。对给定的参数T执行定义的操作。
*/
Consumer consumer2 = s->System.out.println(s);
consumer2.accept("lambda内置写法");
Consumer consumer3 = System.out::println;
consumer3.accept("对象的方法引用的写法");
/**
* BinaryOperator表示对两个相同类型的操作数的操作。产生与操作数相同类型的结果。
* 这是BiFunction的一个特殊例子,它的结果和操作数都是相同的数据类型。
* 这个是一个函数式接口,该接口有一个函数方法apply(Object,Object)。
*/
//取两个数的最大值
BinaryOperator bo1 = (d1,d2)->Math.max(d1,d2);
System.out.println("最大值为:"+bo1.apply(10.2d,20d));
BinaryOperator bo2 = Math::max;
System.out.println("最大值为:"+bo2.apply(11.9d,10.11d));
上述代码:consumer2与consumer3和bo1与bo2的效果都是一样的,只是他们所用的语法糖不一样而已。
构造器引用
格式:ClassName :: new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义方法,与构造器参数列表要与接口中抽象方法的参数列表一致。
public class Student{
private String name;
private String subject;
private Integer score;
public Student(){}
public Student(String name) {
this.name = name;
}
public Student(String name, String subject, Integer score) {
this.name = name;
this.subject = subject;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
//函数式接口
public interface ScoreCompareInterface {
//比较分数
Integer compareScore(T t1,T t2);
}
public static void main(String []args){
Supplier sl1 = ()->new Student();
Supplier sl2 = Student::new;
Function f1 = name->new Student(name);
Function f2 = Student::new;
}
}
数组引用
格式:type[] :: new
Function fun1 = size->new Student[size];
Function fun2 = Student[]::new;
集合排序
对学生对象集合按数学科目分数进行排序。
public static void main(String []args){
Student s1 = new Student("张三","数学",90);
Student s2 = new Student("李四","数学",96);
Student s3 = new Student("王老五","数学",10);
List studentList = new ArrayList<>();
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);
//排序
Collections.sort(studentList,(st1, st2)->st1.getScore().compareTo(st2.getScore()));
System.out.println(studentList);
}
List遍历
//list遍历
studentList.forEach(item-> System.out.print(item.getScore() +" "));
返回集合中符合条件的元素
返回集合中符合条件的元素(返回分数大于或等于90分的学生对象)
//返回集合中符合条件的元素(返回分数大于或等于90分的学生对象)
studentList.removeIf(item->item.getScore()<90);
studentList.forEach(item-> System.out.print(item.getScore() +" "));
removeIf的源码,将集合中的每个元素放入test方法中,返回true则移除。
default boolean removeIf(Predicate super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
对象间的元素比较
比较两个学生对象的分数 如果不用lambda表达式,那么就要写匿名内部类,下面用lambda表达式:
ScoreCompareInterface scoreCompare =
(stu1,stu2)-> stu1.getScore().compareTo(stu2.getScore());
int compare = scoreCompare.compareScore(s1,s2);
if(compare==-1){
System.out.println(String.format("s1学生%s分数%s 低于 s2学生%s分数%s",
s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}else if(compare==1){
System.out.println(String.format("s1学生%s分数%s 高于 s2学生%s分数%s",
s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}else{
System.out.println(String.format("s1学生%s分数%s 等于 s2学生%s分数%s",
s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}