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
接口的对象的引用。
例子:
代码 | 笔记 |
---|---|
|
run() 类中的方法将Timer 被调用类中的方法将被调用类中的方法将被调用 run() Timer run() Calendar |
您始终可以将对象引用分配给任何类型的变量,只要该类型是对象的祖先类之一。对于Timer
andCalendar
类,有两种这样的类型:Object
and 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();
这段代码可以工作,因为Timer
和Calendar
对象具有运行良好的运行方法。所以,打电话给他们不是问题。如果我们只是为这两个类都添加了一个 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();
}
}
要比较字符串长度,只需从另一个中减去一个长度。
按长度对字符串进行排序的程序的完整代码如下所示:
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();
}
}
在两种不同的情况下,相同的颜色用于指示相同的代码块。在实践中差异很小。
当编译器遇到第一个代码块时,它会简单地生成相应的第二个代码块,并为该类指定一些随机名称。
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
变量obj2
是Strings
. 其次,如果方法代码中只有一个命令,也可以省略花括号和返回语句。
缩短的版本将是这样的:
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)
GO TO FULL VERSION