1.接口

要了解什么是 lambda 函数,您首先需要了解什么是接口。那么,让我们回顾一下要点。

接口是类概念的变体比方说,一个被严重截断的班级。与类不同,接口不能有自己的变量(静态变量除外)。您也不能创建类型为接口的对象:

  • 您不能声明类的变量
  • 你不能创建对象

例子:

interface Runnable
{
   void run();
}
标准接口示例

使用界面

那么为什么需要接口呢?接口只与继承一起使用。同一个接口可以被不同的类继承,或者也可以说——实现了接口

如果一个类实现了一个接口,它必须实现接口声明但没有实现的方法。例子:

interface Runnable
{
   void run();
}

class Timer implements Runnable
{
   void run()
   {
      System.out.println(LocalTime.now());
   }
}

class Calendar implements Runnable
{
   void run()
   {
      var date = LocalDate.now();
      System.out.println("Today: " + date.getDayOfWeek());
   }
}

该类Timer实现Runnable接口,因此它必须在自身内部声明接口中的所有方法Runnable并实现它们,即在方法体中编写代码。班级也是如此Calendar

但是现在Runnable变量可以存储对实现Runnable接口的对象的引用。

例子:

代码 笔记
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

run()类中的方法将Timer被调用类中的方法


将被调用类中的方法将被调用 run()Timer


run()Calendar

您始终可以将对象引用分配给任何类型的变量,只要该类型是对象的祖先类之一。对于TimerandCalendar类,有两种这样的类型:Objectand Runnable

如果将对象引用分配给Object变量,则只能调用Object类中声明的方法。如果将对象引用分配给Runnable变量,则可以调用该Runnable类型的方法。

示例 2:

ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());

for (Runnable element: list)
    element.run();

这段代码可以工作,因为TimerCalendar对象具有运行良好的运行方法。所以,打电话给他们不是问题。如果我们只是为这两个类都添加了一个 run() 方法,那么我们将无法以如此简单的方式调用它们。

基本上,Runnable接口只是用作放置运行方法的地方。



2.排序

让我们继续做一些更实际的事情。例如,让我们看一下对字符串进行排序。

为了按字母顺序对字符串集合进行排序,Java 有一个很棒的方法叫做Collections.sort(collection);

此静态方法对传递的集合进行排序。并且在排序过程中,它对其元素进行成对比较,以了解元素是否应该交换。

在排序期间,这些比较是使用compareTo() 方法执行的,所有标准类都有:Integer, String, ...

Integer 类的 compareTo() 方法比较两个数字的值,而 String 类的 compareTo() 方法查看字符串的字母顺序。

因此,数字集合将按升序排序,而字符串集合将按字母顺序排序。

替代排序算法

但是,如果我们不想按字母顺序而是按长度对字符串进行排序怎么办?如果我们想按降序对数字进行排序怎么办?在这种情况下你会怎么做?

为了处理这种情况,该类Collections有另一个sort()方法有两个参数:

Collections.sort(collection, comparator);

其中比较器是一个特殊的对象,它知道如何在排序操作期间比较集合中的对象。comparator 一词来自英文单词comparator,而后者又派生自compare,意思是“比较”。

那么这个特殊的对象是什么?

Comparator界面

好吧,这一切都非常简单。sort()该方法的第二个参数的类型是Comparator<T>

其中 T 是一个类型参数,指示集合中元素的类型,并且Comparator是一个具有单一方法的接口int compare(T obj1, T obj2);

换句话说,比较器对象是实现 Comparator 接口的任何类的任何对象。Comparator 接口看起来很简单:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
比较器接口的代码

compare()方法比较传递给它的两个参数。

如果该方法返回负数,则表示obj1 < obj2. 如果该方法返回正数,则表示obj1 > obj2. 如果该方法返回 0,则表示obj1 == obj2.

下面是一个按长度比较字符串的比较器对象的示例:

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
StringLengthComparator班级代码

要比较字符串长度,只需从另一个中减去一个长度。

按长度对字符串进行排序的程序的完整代码如下所示:

public class Solution
{
   public static void main(String[] args)
   {
      ArrayList<String> list = new ArrayList<String>();
      Collections.addAll(list, "Hello", "how's", "life?");
      Collections.sort(list, new StringLengthComparator());
   }
}

class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
按长度排序字符串


3.语法糖

你觉得怎么样,这段代码能不能写得更紧凑一些?基本上,只有一行包含有用的信息 — obj1.length() - obj2.length();

但是代码不能存在于方法之外,所以我们必须添加一个compare()方法,并且为了存储方法我们必须添加一个新类 — StringLengthComparator。而且我们还需要指定变量的类型……一切似乎都是正确的。

但是有一些方法可以使这段代码更短。我们为您准备了一些语法糖。两勺!

匿名内部类

您可以在方法内部编写比较器代码main(),编译器将完成剩下的工作。例子:

public class Solution
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list, "Hello", "how's", "life?");

        Comparator<String> comparator = new Comparator<String>()
        {
            public int compare (String obj1, String obj2)
            {
                return obj1.length() – obj2.length();
            }
        };

        Collections.sort(list, comparator);
    }
}
按长度对字符串排序

您可以创建一个实现接口的对象,Comparator而无需显式创建类!编译器会自动创建它并给它一些临时名称。让我们比较一下:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
匿名内部类
Comparator<String> comparator = new StringLengthComparator();

class StringLengthComparator implements Comparator<String>
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
}
StringLengthComparator班级

在两种不同的情况下,相同的颜色用于指示相同的代码块。在实践中差异很小。

当编译器遇到第一个代码块时,它会简单地生成相应的第二个代码块,并为该类指定一些随机名称。


4. Java中的Lambda表达式

假设您决定在代码中使用匿名内部类。在这种情况下,您将拥有如下代码块:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
匿名内部类

这里我们将变量的声明与匿名类的创建结合起来。但是有一种方法可以使这段代码更短。例如,像这样:

Comparator<String> comparator = (String obj1, String obj2) ->
{
    return obj1.length() – obj2.length();
};

分号是必需的,因为这里我们不仅有一个隐式类声明,还有一个变量的创建。

像这样的表示法称为lambda 表达式。

如果编译器在您的代码中遇到这样的符号,它只会生成详细版本的代码(带有匿名内部类)。

请注意,在编写 lambda 表达式时,我们不仅省略了类名,还省略了方法名。Comparator<String>int compare()

编译在确定方法时没有问题,因为只能为具有单一方法的接口编写 lambda 表达式。顺便说一下,有一种方法可以绕过这条规则,但是当您开始更深入地研究 OOP 时,您就会了解到这一点(我们谈论的是默认方法)。

让我们再次看一下详细版本的代码,但是我们将在编写 lambda 表达式时可以省略的部分变灰:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
};
匿名内部类

似乎没有遗漏任何重要的东西。事实上,如果Comparator接口只有一个compare()方法,编译器可以从剩余代码中完全恢复灰色代码。

排序

顺便说一句,现在我们可以这样写排序代码了:

Comparator<String> comparator = (String obj1, String obj2) ->
{
   return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);

或者甚至像这样:

Collections.sort(list, (String obj1, String obj2) ->
   {
      return obj1.length() – obj2.length();
   }
);

我们只是立即用comparator分配给comparator变量的值替换了变量。

类型推断

但这还不是全部。这些示例中的代码可以写得更紧凑。首先,编译器可以自行确定 和obj1变量obj2Strings. 其次,如果方法代码中只有一个命令,也可以省略花括号和返回语句。

缩短的版本将是这样的:

Comparator<String> comparator = (obj1, obj2) ->
   obj1.length() – obj2.length();

Collections.sort(list, comparator);

如果comparator我们不使用变量,而是立即使用它的值,那么我们将得到以下版本:

Collections.sort(list, (obj1, obj2) ->  obj1.length() — obj2.length() );

嗯,你怎么看?只有一行代码,没有多余的信息——只有变量和代码。没有办法让它变短!或者有吗?



5. 它是如何运作的

事实上,代码可以写得更紧凑。但稍后会详细介绍。

您可以编写一个 lambda 表达式,其中您将使用一个接口类型和一个方法。

例如,在代码中,你可以写一个 lambda 表达式,因为方法的签名是这样的:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

sort(Collection<T> colls, Comparator<T> comp)

当我们将ArrayList<String>集合作为第一个参数传递给排序方法时,编译器能够确定第二个参数的类型是. 并由此得出结论,这个接口只有一个方法。其他一切都是技术问题。Comparator<String>int compare(String obj1, String obj2)