lambda表达式-入门介绍
更新时间 2021-11-01 17:26:22    浏览 0   

TIP

本文主要是介绍 lambda表达式(Java8) 基础知识。

# java8 总结 之lambda表达式

# 为什么使用lambda表达式

lambda表达式是一段可以传递的代码,因此它可以被执行一次或者多次。先举个李子,当你想在一个线程中执行一段代码时你要怎么做?

    class Worker implements Runnable{
        public void run(){
            .....//待执行的代码
        }
    }
    Worker w = new Worker();
    new Thread(w).start();

这段代码没什么问题,但你不觉得定义一个worker对象是没有必要的吗?定义它只是为了让线程执行run()方法的代码而已。

除此这外还有”匿名类的匿名实例”等方式的调用。由于java是面向对象的语言,要将代码的传递,你不得不构造一个类对象,这就让代码显的很不容易。这就让java中加入类似函数式编程的语法成为了一种必要,只是怎么实现的问题了。

# lambda表达式的语法

我们来看一个例子:比较某个字符串的长度是否小于另一个字符串

    表达式如:Integer.compare(first.length(),second.length())
    //其中 first和second是字符串 java强类型语言,要指定类型String first,String second)->Integer.compare(first.length(),second.length())

这就是一个lambda表达式,java表达式的格式:参数、箭头->,以及一个表达式(注:如果该表达式只有一个return时,可以省略不定return ).如果负责计算的代码无法用一个表达式表示,那么可以用编写方法的方式来编写:即用{}包裹代码并明确使用return语句,如

String first,String second)->{
        if(first.length()<second.length()) return -1;
        else if(first.length()>second.length()) return 1;
        else return 0;
    }
    //如果是这样int first,int second)->{
        return first +second;
    }

    则可以写为(int first,int second)-> first + second;

    如果参数可以被推导则 int sun = (first,second)->first + second;

如果lamdba表达式没有参数,你仍可以提供一对空小括号:

    ()->{ for (int i = 0 ;i < 10; i++) do();}1

如果一个lambda表达式的参数是可以被推导的,那么可以省略它们的类型,如

    Comparator<String> comparator = (first,second)//同(String first,String second)
                ->Integer.compare(first.length(), second.length());
此处编译器是可以推导出first和second是String类型的,因为他们被赋给了一个Comparator<String>字符串比较器。
1

如果一个表达式只有一个参数并且类型可以被推导,则可以不写小括号 如果表达式只有一行,则可以省略{}

    EventHandler<T> l = event -> System.out.printl("...");

# 函数式接口

对于只包含一个抽象方法的接口,你可以通过lambda表达式来创建该接口的对象。这种接口被叫做函数式接口,但函数式接口中可以包含默认方法、类方法,但只能有一个抽象方法。

java8中你可以在函数式接口上标注@FunctionalInterface注解,该注解通常方法接口定义前面,这个注解没有任何作用,只是告诉编译器执行更加严格,检查该接口必须是函数式接口,否则编译器就会报错。

我为写一个 例子

    @Test
    public void Test2() {
        String [] words = Arrays.asList("sb","e").stream().toArray(String[]::new);
        Arrays.sort(words,(first,second)->Integer.compare(first.length(), second.length()));
        System.err.println(JSON.toJSONString(words));
    }

看第四行,该表达式sort方法会接收到一个Comparator<String>接口的类的实例。即Comparator<String> s= (first,second)->Integer.compare(first.length(), second.length()).调用该对象的compare方法会执行lambda表达式中的代码。可查看Comparator接口,该接口中有一个compare抽象方法。 注:你最好将一个lambda表达式想象成一个函数,而不是一个对象,并记住它可以被转换为一个函数式接口。

# 方法引用

# 什么是方法引用

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。

注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号”::”。

# 方法引用例子

先看一个例子

首先定义一个Person类,如下:

package methodreferences;

import java.time.LocalDate;

public class Person
{

    public Person(String name, LocalDate birthday)
    {
        this.name = name;
        this.birthday = birthday;
    }

    String name;
    LocalDate birthday;

    public LocalDate getBirthday()
    {
        return birthday;
    }

    public static int compareByAge(Person a, Person b)
    {
        return a.birthday.compareTo(b.birthday);
    }

    @Override
    public String toString()
    {
        return this.name;
    }
}

假设我们有一个Person数组,并且想对它进行排序,这时候,我们可能会这样写:

原始写法

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.Comparator;

public class Main
{

    static class PersonAgeComparator implements Comparator<Person> {
        public int compare(Person a, Person b) {
            return a.getBirthday().compareTo(b.getBirthday());
        }
    }

    public static void main(String[] args)
    {
        Person[] pArr = new Person[]{
            new Person("003", LocalDate.of(2016,9,1)),
            new Person("001", LocalDate.of(2016,2,1)),
            new Person("002", LocalDate.of(2016,3,1)),
            new Person("004", LocalDate.of(2016,12,1))};

        Arrays.sort(pArr, new PersonAgeComparator());

        System.out.println(Arrays.asList(pArr));
    }
}

其中,Arrays类的sort方法定义如下:

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;

public class Main
{

    public static void main(String[] args)
    {
        Person[] pArr = new Person[]{
            new Person("003", LocalDate.of(2016,9,1)),
            new Person("001", LocalDate.of(2016,2,1)),
            new Person("002", LocalDate.of(2016,3,1)),
            new Person("004", LocalDate.of(2016,12,1))};

        Arrays.sort(pArr, (Person a, Person b) -> {
            return a.getBirthday().compareTo(b.getBirthday());
        });

        System.out.println(Arrays.asList(pArr));
    }
});

        System.out.println(Arrays.asList(pArr));
    }
}

然而,在以上代码中,关于两个人生日的比较方法在Person类中已经定义了,因此,我们可以直接使用已存在的Person.compareByAge方法。

改进二,使用Lambda表达式,调用已存在的方法

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;

public class Main
{

    public static void main(String[] args)
    {
        Person[] pArr = new Person[]{
            new Person("003", LocalDate.of(2016,9,1)),
            new Person("001", LocalDate.of(2016,2,1)),
            new Person("002", LocalDate.of(2016,3,1)),
            new Person("004", LocalDate.of(2016,12,1))};

        Arrays.sort(pArr, (a, b) -> Person.compareByAge(a, b));

        System.out.println(Arrays.asList(pArr));
    }
}

因为这个Lambda表达式调用了一个已存在的方法,因此,我们可以直接使用方法引用来替代这个Lambda表达式,

改进三,使用方法引用

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;

public class Main
{

    public static void main(String[] args)
    {
        Person[] pArr = new Person[]{
            new Person("003", LocalDate.of(2016,9,1)),
            new Person("001", LocalDate.of(2016,2,1)),
            new Person("002", LocalDate.of(2016,3,1)),
            new Person("004", LocalDate.of(2016,12,1))};

        Arrays.sort(pArr, Person::compareByAge);

        System.out.println(Arrays.asList(pArr));
    }
}

在以上代码中,方法引用Person::compareByAge在语义上与Lambda表达式 (a, b) -> Person.compareByAge(a, b) 是等同的,都有如下特性:

  • 真实的参数是拷贝自Comparator.compare方法,即(Person, Person);
  • 表达式体调用Person.compareByAge方法;

# 四种方法引用类型

# 静态方法引用

组成语法格式:ClassName::staticMethodName 注意

  • 静态方法引用比较容易理解,和静态方法调用相比,只是把 . 换为 ::

  • 在目标类型兼容的任何地方,都可以使用静态方法引用。

    例子:

    String::valueOf 等价于lambda表达式 (s) -> String.valueOf(s)

    Math::pow 等价于lambda表达式 (x, y) -> Math.pow(x, y);

    字符串反转的例子:

/*
* 函数式接口
* */
interface StringFunc {
    String func(String n);
}
class MyStringOps {
    //静态方法: 反转字符串
    public static String strReverse(String str) {
        String result = "";
        for (int i = str.length() - 1; i >= 0; i--) {
            result += str.charAt(i);
        }
        return result;
    }
}
class MethodRefDemo {
    public static String stringOp(StringFunc sf, String s) {
        return sf.func(s);
    }
    public static void main(String[] args) {
        String inStr = "lambda add power to Java";
        //MyStringOps::strReverse 相当于实现了接口方法func() 
        // 并在接口方法func()中作了MyStringOps.strReverse()操作
        String outStr = stringOp(MyStringOps::strReverse, inStr);
        System.out.println("Original string: " + inStr);
        System.out.println("String reserved: " + outStr);
    }
}
输出结果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal3132

表达式MyStringOps::strReverse的计算结果为对象引用,其中,strReverse提供了StringFunc的func()方法的实现。

找到列表中具有最大值的对象

class MyClass {
    private int val;
    MyClass(int v) {
        val = v;
    }
    public int getValue() {
        return val;
    }
}
class UseMethodRef {
    public static int compareMC(MyClass a, MyClass b) {
        return a.getValue() - b.getValue();
    }
    public static void main(String[] args) {
        ArrayList<MyClass> a1 = new ArrayList<MyClass>();
        a1.add(new MyClass(1));
        a1.add(new MyClass(4));
        a1.add(new MyClass(2));
        a1.add(new MyClass(9));
        a1.add(new MyClass(3));
        a1.add(new MyClass(7));
        //UseMethodRef::compareMC生成了抽象接口Comparator定义的compare()方法的实例。
        MyClass maxValObj = Collections.max(a1, UseMethodRef::compareMC);
        System.out.println("Maximum value is: " + maxValObj.getValue());
    }
}

输出结果:
Maximum value is: 9  

UseMethodRef定义了静态方法compareMC(),它与Comparator定义的compare()方法兼容。因此,没哟必要显式的实现Comparator接口并创建其实例。

# 实例方法引用

这种语法与用于静态方法的语法类似,只不过这里使用对象引用而不是类名。

实例方法引用又分以下三种类型

# a.实例上的实例方法引用

组成语法格式:instanceReference::methodName 例子:

Function

/*
* 函数式接口
* */
interface StringFunc {
    String func(String n);
}
class MyStringOps {
    //普通方法: 反转字符串
    public String strReverse(String str) {
        String result = "";
        for (int i = str.length() - 1; i >= 0; i--) {
            result += str.charAt(i);
        }
        return result;
    }
}
class MethodRefDemo2 {
    public static String stringOp(StringFunc sf, String s) {
        return sf.func(s);
    }
    public static void main(String[] args) {
        String inStr = "lambda add power to Java";
        MyStringOps strOps = new MyStringOps();//实例对象
        //MyStringOps::strReverse 相当于实现了接口方法func() 
        //并在接口方法func()中作了MyStringOps.strReverse()操作
        String outStr = stringOp(strOps::strReverse, inStr);

        System.out.println("Original string: " + inStr);
        System.out.println("String reserved: " + outStr);
    }
}

输出结果:

Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal313233343536

这里使用了类的名称,而不是具体的对象,尽管指定的是实例方法。使用这种形式时,函数式接口的第一个参数匹配调用对象,第二个参数匹配方法指定的参数。

# b.超类上的实例方法引用

组成语法格式:super::methodName

方法的名称由methodName指定

通过使用super,可以引用方法的超类版本。

例子:

还可以捕获this 指针

this :: equals 等价于lambda表达式 x -> this.equals(x);

# c.类型上的实例方法引用

组成语法格式:ClassName::methodName

注意:

若类型的实例方法是泛型的,就需要在::分隔符前提供类型参数,或者(多数情况下)利用目标类型推导出其类型。

静态方法引用和类型上的实例方法引用拥有一样的语法。编译器会根据实际情况做出决定。

一般我们不需要指定方法引用中的参数类型,因为编译器往往可以推导出结果,但如果需要我们也可以显式在::分隔符之前提供参数类型信息。

例子:

String::toString 等价于lambda表达式 (s) -> s.toString()

这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。

在泛型类或泛型方法中,也可以使用方法引用。

interface MyFunc<T> {
    int func(T[] als, T v);
}
class MyArrayOps {
    public static <T> int countMatching(T[] vals, T v) {
        int count = 0;
        for (int i = 0; i < vals.length; i++) {
            if (vals[i] == v) count++;
        }
        return count;
    }
}
class GenericMethodRefDemo {
    public static <T> int myOp(MyFunc<T> f, T[] vals, T v) {
        return f.func(vals, v);
    }
    public static void main(String[] args){
        Integer[] vals = {1, 2, 3, 4, 2, 3, 4, 4, 5};
        String[] strs = {"One", "Two", "Three", "Two"};
        int count;
        count=myOp(MyArrayOps::<Integer>countMatching, vals, 4);
        System.out.println("vals contains "+count+" 4s");
        count=myOp(MyArrayOps::<String>countMatching, strs, "Two");
        System.out.println("strs contains "+count+" Twos");
    }
}

输出结果:
vals contains 3 4s
strs contains 2 Twos

分析:

在程序中,MyArrayOps是非泛型类,包含泛型方法countMatching()。该方法返回数组中与指定值匹配的元素的个数。注意这里如何指定泛型类型参数。例如,在main()方法中,对countMatching()方法的第一次调用如下所示:

count = myOp(MyArrayOps::<Integer>countMatching,vals,4);

这里传递了类型参数Integer。

注意,参数传递发生在::的后面。这种语法可以推广。当把泛型方法指定为方法引用时,类型参数出现在::之后、方法名之前。但是,需要指出的是,在这种情况(和其它许多情况)下,并非必须显示指定类型参数,因为类型参数会被自动推断得出。对于指定泛型类的情况,类型参数位于类名的后面::的前面。

# 构造方法引用

构造方法引用又分构造方法引用和数组构造方法引用。

# a.构造方法引用 (也可以称作构造器引用)

组成语法格式:Class::new

构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字。

例子:

String::new, 等价于lambda表达式 () -> new String()

interface MyFunc {
    MyClass func(int n);
}
class MyClass {
    private int val;
    MyClass(int v) {
        val = v;
    }
    MyClass() {
        val = 0;
    }
    public int getValue() {
        return val;
    }
}
class ConstructorRefDemo {
    public static void main(String[] args) {
        MyFunc myClassCons = MyClass::new;
        MyClass mc = myClassCons.func(100);
        System.out.println("val in mc is: " + mc.getValue());
    }
}

输出结果:
val in mc is: 100

# b.数组构造方法引用:

组成语法格式:TypeName[]::new

例子:

int[]::new 是一个含有一个参数的构造器引用,这个参数就是数组的长度。

等价于lambda表达式 x -> new int[x]。

假想存在一个接收int参数的数组构造方法

IntFunction

# 任意对象(属于同一个类)的实例方法引用

如下示例,这里引用的是字符串数组中任意一个对象的compareToIgnoreCase方法

 String[] stringArray = { "Barbara", "James", "Mary", "Linda" };
 Arrays.sort(stringArray, String::compareToIgnoreCase);

参考1 (opens new window)

参考2 (opens new window)

参数3 (opens new window)

书:写给大忙人看的java SE 8

# 参考文章

  • https://blog.csdn.net/lhn1234321/article/details/80161303
更新时间: 2021-11-01 17:26:22
  0
手机看
公众号
讨论
左栏
全屏
上一篇
下一篇
扫一扫 手机阅读
可分享给好友和朋友圈