一文详解java--lambda函数

171 阅读6分钟

一文详解java--lambda函数

  • lambda函数的定义:

Lambda函数是一种匿名函数,也称为箭头函数或者匿名函数表达式。它是函数式编程的一个重要概念,在许多编程语言中都有支持。

Lambda函数通常用于需要定义一个简短的函数而又不想单独命名一个函数的情况。

Lambda表达式可以很方便地实现函数式接口的匿名实现,使得代码更简洁、易读。

lambda函数在各种语言中可能有不同的表现形式,这里以java语言为例进行介绍。

  • lambda函数的形式:
(parameter1,parameter2)->expression
or
(parameter1,parameter2)->{expression;
               return sth;}

整个表达式的左侧是参数列表,可以有多个参数,也可以只有一个参数或者没有参数,多个参数和没有参数的情况下,括号()是一定需要的,多个参数用逗号隔开,当参数只有一个时,可以省略括号;

末尾的expression是主体表达式,也就是方法体的内容,当只有一句表达式时,可以省略闭包{},当有多句表达式时则需要闭包{};如果方法有返回值,有两种形式return 返回值,第一种是不写return关键字,在方法体只有一行且该行是符合返回值类型的表达式的时候,java会自动将其视作返回值,比如:

public class Test {
    private static void doSomething(Testable testable) {
        if (testable.testAble()) {
            System.out.println("can test!");
        } else {
            System.out.println("can not test!");
        }
    }
    public static void main(String[] args) {
        doSomething(() -> false);
    }
}
interface Testable{
    boolean testAble();
}

那另外一种当然就是直接写明return关键字啦;另外可以说明一点的是方法的引用,如果在需要引用的方法是入参对象的对象方法,则可采取更为简洁的方式,比如:

public class Test {
    private static void doSomething(Testable testable) {
        if (testable.testAble("")) {
            System.out.println("can test!");
        } else {
            System.out.println("can not test!");
        }
    }
    public static void main(String[] args) {
        doSomething(str -> str.isBlank());
        //doSomething(String::isBlank);
    }
}
interface Testable{
    boolean testAble(String str);
}

连接参数列表和方法体的就是中间的箭头**->**,没啥好说的;

  • lambda函数的原理:

javac 遇到 lambda 表达式时会把它解释为一个方法的主体,这个方法具有特定的签名。不过,是哪个方法呢为了解决这个问题,javac 会查看周围的代码。

lambda表达式必须满足以下条件才算是合法的 Java 代码:

  • lambda表达式必须出现在期望使用接口类型实例的地方;
  • 期望使用的接口类型必须只有一个强制方法;
  • 这个强制方法的签名要完全匹配 lambda表达式。

如果满足上述条件,编译器会创建一个类型,实现期望使用的接口,然后把 lambda 表达 式的主体当作强制方法的实现。

  • lambda函数的试用场景:

在java中,lambda函数的使用场景都是用来替换要使用匿名类对象的;一般来说,某个功能接口里面只有一个对外暴露的方法,则在实现相关内容时,可使用lambda函数替换原始的new匿名类对象的形式;还想强调一点,这两种形式可以互相替换,lambda函数的作用域范围是跟匿名内部类的作用域范围一致的,当我们想使用一个在其外部定义的对象,编译器会要求其参数引用是final的,否则是编译不过的,关于内部类的相关内容可以参考查看《java编程思想》,我将其第四版的pdf文件网盘链接放在这儿,有需要的可以**自取**;

  1. 线程的使用:

    普通线程的声明和启动方式一般如下:

    public class Test {
        private static int count = 0;
        private static void doSomething(Testable testable) {
            if (testable.testAble("")) {
                System.out.println("can test!");
            } else {
                System.out.println("can not test!");
            }
        }
        public static void main(String[] args) {
            doSomething(String::isBlank);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    do {
                        System.out.println("count == " + count++);
                    } while (count <= 10);
                }
            }).start();
         /* new Thread(() -> {
                do {
                    System.out.println("count == " + count++);
                } while (count <= 10);
            }).start();*/
        }
    
    }
    interface Testable{
        boolean testAble(String str);
    }
    
  2. android开发最常用到的setOnclickListener方法:

    private void initViews() {
        btn_start = findViewById(R.id.start_btn);
        btn_cancel = findViewById(R.id.cancel_btn);
        
      //btn_start.setOnClickListener(view -> Toast.makeText(MainActivity2.this, "start", Toast.LENGTH_SHORT).show());
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity2.this, "start", Toast.LENGTH_SHORT).show();
            }
        });
        
      //btn_cancel.setOnClickListener(view -> Toast.makeText(MainActivity2.this, "cancel", Toast.LENGTH_SHORT).show());  
        btn_cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity2.this, "cancel", Toast.LENGTH_SHORT).show();
            }
        });
    }
  1. jdk的Stream接口类中提供的一些方法,用来操作流类型,可以很方便处理集合:

    这些方法接受功能接口类型作为参数,功能接口是指带有@FunctionalTnterface注解的接口;

    //以下是比较常用的几个方法
    Stream<T> filter(Predicate<? super T> predicate);//实现条件过滤功能
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);//实现执行相关方法后返回特定值R类型对象或者R的子类对象
    void forEach(Consumer<? super T> action);//遍历整个流
    
    public class Person {
        private String name,email;
        private int age;
        private Gender sex;
        public enum Gender {
            MALE,FEMALE
        }
    
        public Person(String name, String email, int age, Gender sex) {
            this.name = name;
            this.email = email;
            this.age = age;
            this.sex = sex;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Gender getSex() {
            return sex;
        }
    
        public void setSex(Gender sex) {
            this.sex = sex;
        }
    
        @Override
        public String toString() {
            return name + " " + age + " " + sex + " " + email;
        }
        public void printEven(){
            System.out.println(this);
        }
        public static void main(String[] args) {
            List<Person> mList = new ArrayList<>();
            mList.add(new Person("qitao", "61224@qq.com",23, Person.Gender.MALE));
            mList.add(new Person("qitao222", "60224@qq.com",60, Person.Gender.MALE));
            mList.add(new Person("qitao333", "603224@qq.com",33, Person.Gender.MALE));
            mList.add(new Person("qitao42", "603201@qq.com",45, Person.Gender.MALE));
            mList.add(new Person("qitao123123", "6031224@qq.com",18, Person.Gender.MALE));
    
            mList.stream().filter(person -> person.getSex() == Person.Gender.MALE
            && person.getAge() < 40).map(Person::getEmail).forEach(System.out::println);
    
        }
    }
    
  2. 自定义功能接口,这个其实就是第三点的自定义的形式,在确保自定义的接口满足功能接口条件的情况下,可以使用lambda函数,我在第一点的代码中其实已经写到了:

    private static void doSomething(Testable testable) {
            if (testable.testAble("")) {
                System.out.println("can test!");
            } else {
                System.out.println("can not test!");
            }
        }
    interface Testable{
        boolean testAble(String str);
    }
    doSomething(String::isBlank);
    

    上面的例子中,interface Testable并没有添加@FunctionalIterface注解,其实无关紧要,注解只是告诉编译器他是一个功能接口,以在编译器可以检测相关错误,比如:

    hhh.png

  3. 简单的对集合遍历操作:

    上面第3点说过了可以用流类型很方便的操作集合,jdk还提供了其他的遍历集合的方式,在Iterable接口中也提供了forEach方法:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    

    一般我们遍历集合的方式有几种,最常见的是for循环和增强for循环,不过写起来都不够优雅:

    for (int i = 0;i<mList.size();i++) {
        System.out.println(mList.get(i));
    }
    for (Person person : mList) {
        System.out.println(person);
    }
    

    而采用forEach方式就会更加简洁明了:

    mList.forEach(System.out::println);
    
  • 总结:

上面列举了一些常用的使用场景,很大程度上可以为遍历集合服务,然后就是Android中最常见的给View设置点击事件监听,当然Android中还有其他场景用到了lambda函数,而且随着Android版本的更新,源码中使用lambda函数的地方越来越多,看得出来一种趋势;kotlin中的这样的比较新的语言当然也支持lambda函数,而且使用频率更高,有机会写一写;总的来说,对于java而言,只要明白其支持lambda函数的原理,在什么时候用就会得心应手了,然后记一记针对集合的这些写法,在使用相关API时,尝试直接写出lambda函数慢慢地就熟悉了,前期地话,还是可以采用new 匿名类的方式,IDE一般会给出响应提示,按着其提示操作就好啦。