Lambda的基础使用

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的使用说明

  1. Lambda表达式需要“函数式接口“的支持,函数式接口:接口中有且仅有一个抽象方法的接口,称作为函数式接口。
  2. 使用注解@FunctionalInterface修饰,表示检查此接口是一个符合Java语言规范定义的函数式接口
  3. 函数式接口默认继承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 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()));
}

你可能感兴趣的:(Lambda的基础使用)