Java一直是面向对象的编程语言。java编程中的所有内容都围绕着对象(为简单起见,除了一些基本类型)。在java中没有函数,函数是类的一部分,我们需要使用类/对象来调用函数。
如果学习过一些其他编程语言,如C++,JavaScript; 一般称它们为函数式编程语言,因为可以编写函数并在需要时使用它们。其中一些语言支持面向对象编程以及函数编程。
面向对象并不坏,但它给程序比较冗长。例如,假设要创建一个Runnable
实例。通常使用下面的匿名类来完成它。
Runnable r = new Runnable(){
@Override
public void run() {
System.out.println("My Runnable");
}};
上面的代码使用的实际部分就是run()
方法中的代码。剩下的代码是因为java程序的结构方式。
Java 8 Functional
接口和Lambda
表达式通过删除大量的样板代码帮助我们编写更小更清晰的代码。
Java 8 Functional 接口
只有一个抽象方法的接口称为 Functional 接口。添加了@FunctionalInterface
注释,以便可以将接口标记为Functional
接口。
使用它并不是强制性的,但最好将它与Functional
接口一起使用,以避免意外添加额外的方法。如果接口使用@FunctionalInterface
进行注释,并且使用多个抽象方法,则会抛出编译器错误。
java 8 Functional
接口的主要好处是可以使用lambda
表达式来实例化它们,并避免使用庞大的匿名类实现。
Java 8已经重写了Collections API,并引入了使用大量Functional
接口的新Stream API。Java 8在java.util.function
包中定义了许多Functional
接口。一些有用的java 8 Functional
接口是:Consumer
,Supplier
,Function
和Predicate
。
java.lang.Runnable
是使用单个抽象方法run()
的Functional
接口的一个很好的例子。
下面的代码片段为Functional
接口提供了一些使用指南:
interface Foo { boolean equals(Object obj); }
// Not functional because equals is already an implicit member (Object class)
interface Comparator<T> {
boolean equals(Object obj);
int compare(T o1, T o2);
}
// Functional because Comparator has only one abstract non-Object method
interface Foo {
int m();
Object clone();
}
// Not functional because method Object.clone is not public
interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<String> arg); }
interface Z extends X, Y {}
// Functional: two methods, but they have the same signature
interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}
// Functional: Y.m is a subsignature & return-type-substitutable
interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}
// Not functional: No method has a subsignature of all abstract methods
interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}
// Not functional: No method has a subsignature of all abstract methods
interface X { long m(); }
interface Y { int m(); }
interface Z extends X, Y {}
// Compiler error: no method is return type substitutable
interface Foo<T> { void m(T arg); }
interface Bar<T> { void m(T arg); }
interface FooBar<X, Y> extends Foo<X>, Bar<Y> {}
// Compiler error: different signatures, same erasure
Lambda表达式
Lambda表达式可以在java面向对象的世界中可视化函数式编程的方式。对象是java编程语言的基础,函数必须在对象之上,所以Java语言只支持将lambda
表达式用于函数接口。
由于Functional
接口中只有一个抽象函数,因此将lambda表达式应用于该方法时不会产生混淆。Lambda表达式语法是(参数) - >(正文)。下面来看看如何使用lambda表达式在匿名Runnable
上。
Runnable r1 = () -> System.out.println("My Runnable");
让我们试着理解上面lambda表达式中发生的过程。
Runnable
是一个Functional
接口,所以可以使用lambda表达式来创建它的实例。
由于run()
方法不带参数,因此lambda表达式也没有参数。
就像if-else
块一样,我们可以避免花括号({}
),因为在方法体中有一个语句。对于多个语句,则必须像任何其他方法一样使用花括号。
为什么需要Lambda表达式?
1 . 减少代码行
使用lambda表达式的一个明显好处是减少了代码量,可以使用lambda表达式而不是使用匿名类来创建功能接口的实例。
2 . 顺序和并行执行支持
使用lambda表达式的另一个好处是可以从Stream API顺序和并行操作支持中受益。
为了解释这一点,让我们举一个简单的例子,编写一个方法来测试传递的数字是否为素数。
传统上编写如下代码。代码没有完全优化,但很好。
// 传统写法
private static boolean isPrime(int number) {
if(number < 2) return false;
for(int i=2; i<number; i++){
if(number % i == 0) return false;
}
return true;
}
上面代码的问题在于它本质上是顺序的,如果数量非常大,则需要花费大量时间。代码的另一个问题是有很多退出点并且它不可读。下面看看如何使用lambda表达式和流API编写的方法。
// 声明式写法
private static boolean isPrime(int number) {
return number > 1
&& IntStream.range(2, number).noneMatch(
index -> number % index == 0);
}
IntStream
是一系列原始的int
值元素,支持顺序和并行聚合操作。这是Stream的int
原始特化。
为了更具可读性,还可以编写为如下 -
private static boolean isPrime(int number) {
IntPredicate isDivisible = index -> number % index == 0;
return number > 1
&& IntStream.range(2, number).noneMatch(
isDivisible);
}
IntStream的range()
方法会将增量步长为1的从startInclusive
(包括)到endExclusive
(不包括)的顺序有序IntStream
返回。
noneMatch()
方法返回此流的元素是否与提供的谓词匹配。如果不是确定结果所必需的,它可能不会评估所有元素的谓词。
3 . 将行为传递给方法
下面来看看如何使用lambda表达式,通过一个简单的例子传递方法的行为。假设我们必须编写一种方法来对列表中的数字求和,如果它们符合给定的标准。可以使用Predicate
并编写如下方法。
public static int sumWithCondition(List<Integer> numbers, Predicate<Integer> predicate) {
return numbers.parallelStream()
.filter(predicate)
.mapToInt(i -> i)
.sum();
}
一些其它用法:
//sum of all numbers
sumWithCondition(numbers, n -> true)
//sum of all even numbers
sumWithCondition(numbers, i -> i%2==0)
//sum of all numbers greater than 5
sumWithCondition(numbers, i -> i>5)
4 . 延迟带来更高的效率
使用lambda表达式的另一个优点是延迟评估,例如,假设需要编写一个方法来找出3
到11
范围内的最大奇数并返回它的平方。
通常编写代码如下所示:
private static int findSquareOfMaxOdd(List<Integer> numbers) {
int max = 0;
for (int i : numbers) {
if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
max = i;
}
}
return max * max;
}
上面的程序将始终按顺序运行,但可以使用Stream API来实现这一点,并从中获得懒惰的好处。让我们看看如何使用Stream API和lambda表达式以函数式编程方式重写的代码。
public static int findSquareOfMaxOdd(List<Integer> numbers) {
return numbers.stream()
.filter(NumberTest::isOdd) //Predicate is functional interface and
.filter(NumberTest::isGreaterThan3) // we are using lambdas to initialize it
.filter(NumberTest::isLessThan11) // rather than anonymous inner classes
.max(Comparator.naturalOrder())
.map(i -> i * i)
.get();
}
public static boolean isOdd(int i) {
return i % 2 != 0;
}
public static boolean isGreaterThan3(int i){
return i > 3;
}
public static boolean isLessThan11(int i){
return i < 11;
}
对双冒号(::
)运算符是在Java 8中引入并用于方法引用。Java编译器负责将参数映射到被调用的方法。它是lambda表达式的简短形式i - > isGreaterThan3(i)
或i - > NumberTest.isGreaterThan3(i)
。
Lambda表达式示例
下面将为lambda表达式提供一些代码片段,并附有解释它们的小注释。
() -> {} // No parameters; void result
() -> 42 // No parameters, expression body
() -> null // No parameters, expression body
() -> { return 42; } // No parameters, block body with return
() -> { System.gc(); } // No parameters, void block body
// Complex block body with multiple returns
() -> {
if (true) return 10;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
}
(int x) -> x+1 // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1 // Single inferred-type argument, same as below
x -> x+1 // Parenthesis optional for single inferred-type case
(String s) -> s.length() // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length() // Single inferred-type argument
t -> { t.start(); } // Single inferred-type argument
(int x, int y) -> x+y // Multiple declared-type parameters
(x,y) -> x+y // Multiple inferred-type parameters
(x, final y) -> x+y // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y // Illegal: can't mix inferred and declared types
方法和构造函数引用
方法引用用于引用不调用它的方法; 构造函数引用类似地用于引用构造函数,而不创建命名类或数组类型的新实例。
方法和构造函数引用的示例:
System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new
这就是Java 8 Functional接口和Lambda表达式的全部内容。建议考虑使用它,虽然这种语法对Java来说是新的,需要一些时间来掌握它。