Lambda 表达式与方法引用(二)

在上一章我们介绍了函数式编程的概念和函数式接口。Lambda 表达式就是函数式编程的具体体现,它需要借助函数式接口才能应用在 Java 语言中。

定义

在编程语言中,lambda 表达式是一种用于指定匿名函数或者闭包的运算符。Lambda 可以很清晰地表达一个匿名函数,可以被传递。有了 Lambda 表达式之后,Lambda 表达式为 Java 添加了缺失的函数式编程特性,使我们能将函数当作一等公民看待。

在将函数作为一等公民的语言中,Lambda 表达式的类型是函数。但在 Java 中,Lambda 表达式是对象,他们必须依附于一类特别的对象类型-函数式接口,因为 Java 要保持向后兼容。

Java 的 Lambda 表达式是一种匿名函数,它是没有声明的方法,即没有访问修饰符,返回值声明和名字。

Lambda 表达式到底是什么类型那?

我们单独写一个 Lambda 表达式是不行的,也无法判断类型的,所以 Lambda 表达式是必须依托于上下文的,或者说式必须依托于函数式接口的。

Lambda 表达式是一个对象。我们通过 getClass 来看一下 Lambda 表达式到底是一个什么对象。

public class Test8 {
    public static void main(String[] args) {
        Runnable r = ()-> System.out.println("Lambda");
        System.out.println(r.getClass());
        System.out.println(r.getClass().getInterfaces()[0].getClass());

    }
}
class Test8$$Lambda$1/1607521710
class java.lang.Class

Lambda 表达式的写法

  • 一个 Lambada 表达式可以有零个或者多个参数,参数的类型即可以明确声明,编译器也可以根据上下文来推断。例如 (int a) 与 (a) 效果相同。
  • 所有参数需要包裹在圆括号内,参数之间用逗号隔开。空圆括号代表参数集为空。
  • 当只有一个参数,且其类型可以推到时,圆括号可以省略。a->return a*a;
  • Lambda 表达式的主体可以包含零条或者多条语句。如果 Lambda 表达式的主体只有一条语句,花括号{}可以省略。
(argument)-> {body}    //一个入参->函数体

(arg1, arg2) ->{body}   //两个入参->函数体

(Type arg1,Type arg2)->{body}

(int a,int b)->{return a+b;}   //加法操作

()->System.out.println("Hello World");  //打印 hello world

(String str)->{System.out.println(str);} //打印输入的 str;

()->100 //直接返回数字 100

()->{ return 3.1 } //直接返回 3.1

Lambda 表达式的作用

简单表达就是它可以作为函数式接口的实例,往更高层面去想的话,Lambda 表达式引入后,它带给 Java 最大的变化是,它可以传递行为,而不仅仅是值。

我们通过一个例子来给大家演示一下行为和值传递的区别:

需求

我们需要传入一个 int 类型的数字,我们使用这个数字进行个中运算。

实现

以前的写法:

我们会有各种各样的需求去处理这个数字。以前的写法,我们已经讲数据的处理过程和步骤定义好了,如果需要的功能不存在,我们就必须改动代码去实现调用者的功能。

//给调用者的方法类
public class CalUtil {

    public static int cal1(int a){
        return 2*a*a;
    }

    public static int cal2(int a){
        return a > 50 && a < 60? 60:a;
    }

    public static int cal3(int a){
        return a - 5;
    }

}

现在的写法

现在我们将上面的需求的行为抽象出来,输入一个 int 型数字,返回一个 int 型数字。具体怎么实现或者说怎么计算交给程序员或者实现者。我们将行为传给调用者。

我们还没介绍 JDK 中自带的函数式接口,所以我们自己定义一个。

//一个 int 输入,一个 int 输出的行为抽象而成的函数式接口
@FunctionalInterface
public interface Calculate<T> {
    T cal(T a);
}

给调用者提供的方法:

public class ClassUtils2 {
    public static int compute(int a, Calculate<Integer> calculate){
        //calculate 相当于一个行为,这就是我们所说的传递行为.
        // 函数式接口本质上还是一个匿名内部类,一个对象
        return calculate.cal(a);
    }
}

调用者调用行为的具体实现:

public class Test {
    public static void main(String[] args) {
      //输入 a,如果 a 大于 50 小于 60 则返回 60,否则返回 a
        ClassUtils2.compute(50, a -> a > 50 && a < 60? 60:a);
      //输入 a, 返回 a 的平方
        ClassUtils2.compute(2, a->a*a);
    }
}

我们介绍了 Lamba 表达式的相关知识,也知道了如何使用 Lambda 表达式创建函数式接口的实例了。我们继续介绍函数式接口实例的另一种实现方式,方法引用。

方法引用可以理解为 Lambda 表达式的语法糖,大部分时候通过 Lambada 表达式的语句是不能用方法引用来替换的。方法引用只是一种替换和另一种表达方式而已。

什么时候可以使用方法引用方式创建实例?

函数式接口中,需要我们传入符合要求的实例,什么样的要求那?比如没有输入,一个输出,或者一个输入,一个输出等等。我们通过 Lambda 表达式是通过匿名函数的方式去满足这些条件。但是如果我们本身就已经有方法满足这些条件了我们是不是直接可以去调用这些方法而不是写一个匿名函数那?

简单来说,方法引用就是使用已有的函数去满足函数式接口的要求,没必要再使用 Lambda 表达式的匿名函数再去写一遍重复代码了。下面我们通过几个例子来帮助大家理解一下:

第一个例子

  1. 需求:

    将集合中的 String 转换成大写。

  2. 实现:

    还是使用上面那个自己定义的函数式接口。

    @FunctionalInterface
    public interface Calculate<T> {
        T cal(T a);
    }

    提供给调用者的静态方法,用于遍历 List,然后调用函数式接口的处理方法,这个方法需要调用者自己实现。

    public static List<String> convert(List<String> inputs,Calculate<String> calculate){
      List<String> res = new ArrayList<>();
      for(String s:inputs){
        String newString = calculate.cal(s);
        res.add(newString);
      }
      return res;
    }

    那么我们使用 Lambda 表达式的方式会怎么做那?

    整个从小写字母到大些字母的转换过程都需要我们自己实现。

    List<String> inputs = Arrays.asList("hello","world","java8","learning");
    List<String> ls = ClassUtils2.convert(inputs,str->{
      return str.toUpperCase();
    });

    使用方法引用的方式会怎么做那?

    List<String> inputs = Arrays.asList("hello","world","java8","learning");
    List<String> ls2 = ClassUtils2.convert(inputs,String::toUpperCase);

    这说明 toUpperCase 方法正好满足我们自己定义的函数式接口 Calculate 的要求,一个输入,一个输出,同时它正好是实现了字符串从小写字母到大写字母的转换。所以这里就可以使用方法引用的方式 String::toUpperCase 来构建函数式接口的实例。

    这里有几个需要注意的点,第一个,对于 toUpperCase 方法来说,它是没有入参的,为什么能满足上面的函数式接口那?第二个,String::toUpperCase 语法中,String 是类,toUpperCase 是实例方法,如果按照我们的理解,类调用实例方法是不对的,这个该如何理解那?

    我们先看完例子介绍,下一章节给出答案。

   public String toUpperCase() {
        return toUpperCase(Locale.getDefault());
       }

小技巧:我们点击双冒号就可以跳转到对应的函数式接口中。

第二个例子

  1. 需求:

    将结合中的 String 字符串打印出来。

  2. 实现:

    由于 forEach 等方法现在还没有介绍,我们先用 for 循环的方式实现。我们先定义一个有一个输入,没有输出的函数式接口。

    public interface Display<T>{
        void display(T t);
    }

    给调用者提供的静态方法:

    public static void display(List<String> inputs,Display<String> d){
      List<String> res = new ArrayList<>();
      for(String s:inputs){
        d.display(s);
      }
    }

    我们看看 Lambda 表达式的方式如何实现:

    List<String> inputs = Arrays.asList("hello","world","java8","learning");
    ClassUtils2.display(inputs,str->System.out.println(str));

    使用方法引用的方式:

    ClassUtils2.display(inputs,System.out::println);

    我们再来看看这个 println 方法的定义,我们发现它正好满足有一个输入并且没有输出的函数式接口的约束,所以可以使用函数式接口替换 Lambda 表达式。

    public void println(String x) {
      synchronized (this) {
        print(x);
        newLine();
      }
    }

分类

下面我们来介绍方法引用的几种实现方式。

  • 类名::静态方法

    要求静态方法的输入和输出和函数式接口一样,将 Lambda 替换为 方法引用时,必须要求这个方法客观存在。可以理解为用类调用它的静态方法。

    假如我们需要根据成绩为学生排名:

    我们先定义一个静态方法用来根据学生的成绩进行比较

    public class StudentUtil {
        public static int compareByScore(Student s1,Student s2){
            return s1.getMark()-s2.getMark();
        }
    }

    我们使用 List 接口的 sort 方法来进行比较,这个 sort 方法接收一个函数式接口 Comparator 的实例。这个函数式接口需要两个相同的对象输入,一个 int 值输出。刚好和我们定义的静态方法吻合。

    int compare(T o1, T o2);

    我们既可以使用 Lambda 表达式也可以使用方法引用的方式来进行比较:

    public class Test2 {
    
        public static void main(String[] args) {
            Student s1 = new Student("zhang",60,"Male");
            Student s2 = new Student("li",80,"Female");
            Student s3 = new Student("zhao",90,"Male");
            Student s4 = new Student("wang",100,"Female");
    
            List<Student> students = Arrays.asList(s1,s2,s3,s4);
    
            students.sort((ss1,ss2)->StudentUtil.compareByScore(ss1,ss2));
    
            students.sort(StudentUtil::compareByScore);
        }
    }

    我们通过一个图片来让大家理清 Lambda 表达式和方法引用的关系:

  • 对象引用名::实例方法名

    可以理解为实例对象调用实例方法。

    假如我们需要返回一个字符串的第几个字符。这里我们定义一个一个输入一个输出的函数式接口。

    @FunctionalInterface
    public interface Function<T,R> {
        R cal(T a);
    }

    我们先创建一个 String s 实例,然后通过方法引用调用实例 s 的 实例方法 charAt。

    public class Test3 {
        public static void main(String[] args) {
            String s = new String("hello");
            Function<Integer, Character> f = s::charAt;
            Character c2 = f.cal(4);
    
            System.out.println(c2);
        }
    }

    charAt 满足一个 int 输入,一个 char 输出。

    public char charAt(int index) {
      if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
      }
      return value[index];
    }

    我们通过一个图片来让大家理清 Lambda 表达式和方法引用的关系:

  • 类名::实例方法名

    其实也是实例对象调用实例方法。

    上面的 String::toUpperCase 就是这样一个例子,类名和实例方法的组合让大家很不解(问题1)。而且 toUpperCase 是没有入参的,但是也满足一个输入一个输出的函数式接口也是大家困惑的地方(问题2)。

    类名::实例方法名来使用方法引用时,会将函数式接口中要求的入参中的第一个参数作为调用这个实例方法的调用者,其余参数传入实例方法中。这样上面的问题就有了答案。

    问题1:归根结底,还是实例对象调用实例方法。实例对象是 Lambda 表达式的第一个参数。

    问题2:虽然 toUpperCase 没有入参,但是类名::实例方法名的调用方式,会将调用的实例对象算作一个入参。这样就满足了一个入参,一个返回值的规则。

    我们把上面的代码拿过来:

    String::toUpperCase 这个方法引用中,通过 List 中的每一个 String 实例对象来调用 toUpperCase 方法。

    List<String> inputs = Arrays.asList("hello","world","java8","learning");
    List<String> ls2 = ClassUtils2.convert(inputs,String::toUpperCase);

    我们通过一个图片来让大家理清 Lambda 表达式和方法引用的关系:

  • 类名::new

    实际上就是调用这个类的构造方法来生成一个对象。

    我们通过 Lambda 表达式和方法引用的方式分别来返回一个对象。使用这种方式的时候,函数式接口要求的入参会全部放入到构造函数中。

    我们先定义一个 Person 对象,它需要 name 和 age 两个属性来构建:

    public class Person {
    
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }

    我们在定义一个两个入参,一个返回值的函数式接口:

    public interface BiFunction<T,R,U> {
        U apply(T t,R r);
    }
    

    用这个函数式接口来构建 Person:

    public class Test4 {
    
        BiFunction<String,Integer,Person> biFunction1 = (name,age)-> new Person(name,age);
    
        BiFunction<String,Integer,Person> biFunction2 = Person::new;
    
        Person p1 = biFunction1.apply("wang",18);
    
        Person p2 = biFunction2.apply("li",20);
    }
    

    我们通过一个图片来让大家理清 Lambda 表达式和方法引用的关系:

原文地址:https://www.cnblogs.com/paulwang92115/p/12128711.html

时间: 2024-08-02 03:06:16

Lambda 表达式与方法引用(二)的相关文章

Java8 之 lambda 表达式、方法引用、函数式接口、默认方式、静态方法

今天我来聊聊 Java8 的一些新的特性,确实 Java8 的新特性的出现,给开发者带来了非常大的便利,可能刚刚开始的时候会有点不习惯的这种写法,但是,当你真正的熟悉了之后,你一定会爱上这些新的特性的,这篇文章就来聊聊这些新特性. lambda 表达式 lambda 表达式在项目中也是用到了,这种新的语法的加入,对于使用 Java 多年的我,我觉得是如虎添翼的感觉哈,这种新的语法,大大的改善了以前的 Java 的代码,变得更加的简洁,我觉得这也是为什么 Java8 能够很快的流行起来的原因吧.

java8之lambda表达式(方法引用)

有些时候,你想要传递给其他代码的操作已经有实现的方法了.示例: button.setOnAction(event -> System.out.println(event); 如果你能够只将println方法传递给setOnAction方法,就更好了!下面是改后的代码: button.setOnAction(System.out::println); 表达式System.out::println是一个方法引用,等同于lambda表达式: x -> System.out.println(x) 正如

Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)——诺诺&quot;涂鸦&quot;记忆

----------   诺诺学习技术交流博客.期待与您交流!    ---------- 详情请查看:http://blog.csdn.net/sun_promise  方法引用 (注:此文乃个人查找资料然后学习总结的,若有不对的地方,请大家指出,非常感谢!) 1.方法引用简述 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法.方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文.计算时,方法引用会创建函数式接口的一个实例. 当Lambda表达式中

JDK8的lambda表达式、方法引用

(部分转自:https://www.cnblogs.com/xiaoxi/p/7099667.html) 1. lambda表达式 以前定义一个Thread: 1 final int i = 0; 2 new Thread(new Runnable() { 3 @Override 4 public void run() { 5 System.out.println("i = " + i); 6 } 7 }).start(); lambda表达式定义: 1 int i = 0; 2 ne

Lambda表达式与方法引用

1 Lambda表达式 1.1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是"拿数据做数据" 面向对象思想强调"必须通过对象的形式来做事情" 函数式思想则尽量忽略面向对象的复杂语法:"强调做什么,而不是以什么形式去做" 而我们要学的Lambda表达式就是函数式思想的体现 1.2 体验Lambda表达式 需求:启动一个线程,在控制台输出一句话:多线程程序启动了 方式1: 定义一个类MyRunnable实现Runnab

编写高质量代码改善C#程序的157个建议——建议37:使用Lambda表达式代替方法和匿名方法

建议37:使用Lambda表达式代替方法和匿名方法 在建议36中,我们创建了这样一个实例程序: static void Main(string[] args) { Func<int, int, int> add = Add; Action<string> print = Print; print(add(1, 2).ToString()); } static int Add(int i, int j) { return i + j; } static void Print(stri

.net mvc lambda表达式Contains方法

Lambda表达式Contains方法(等价于SQL语句中的like)使用注意事项: 众所周知,想在EntityFrame实体框架中使用类似于SQL语句中like的效果时就的使用Contains方法了.可是关于Contains方法使用过程中会出现的细节问题,并没有专门的文章来指出来. 1.使用Contains方法的必备条件: Contains等价于SQL中的like语句.不过Contains只针对于字符串(string)类型的数据而言.如果是int等数值类型,则不会有Contains方法的存在,

十二、C# 委托与Lambda表达式(匿名方法的另一种写法)

委托与Lambda表达式 1.委托概述 2.匿名方法 3.语句Lambda 4.表达式Lambda 5.表达式树 一.委托概述 相当于C++当中的方法指针,在C#中使用delegate 委托来提供相同的功能, 它将方法作为对象封装起来,允许在"运行时"间接地绑定一个方法调用. 声明的委托相当于一种自定义的数据类型. 1.背景 冒泡排序 1 static class SimpleSort1 2 { 3 public static void BubbleSort(int[] items)

Lambda表达式之方法的引用

以下都是示例 public class Syntax3 { public static void main(String[] args) { //方法引用 //可以快速的将一个lambda表达式的实现指向一个已经实现的方法. //语法: 方法的隶属者 :: 方法名 //注意: //1. 参数数量和类型一定要和接口中定义的方法一致 //2. 返回值的类型也要和接口定义的方法一致 LambdaSingleReturnSingleParameter lambda = a -> change(a); l