在许多文章中,我们探讨了针对不同语言的函数式编程概念,其中F#和Scala是对话的重点。 但是,由于我在工作场所进行过Java开发,因此探索这些相同的概念似乎很有趣并且令人大开眼界,因为自从我上次认真使用Java以来已有很长时间了。
高阶函数
如此处所述, 高阶函数是什么? 高阶函数是简单函数,可以将函数作为参数接收,并可以返回另一个函数作为结果。
在现代Java中 ,我们可以轻松地做到这一点。 语法不是最好的,并且因为没有类型推断,我们必须显式声明函数类型,在Java中这意味着某种接口 。 让我们看看如何。
首先,假设我们有一个对象集合,也就是狗的集合,并且我们有一个作用于每只狗的函数。 我们希望能够在每个对象(狗)上调用此函数。
让我们看看如何创建这样的函数。
@FunctionalInterface
interface DogAge {
Integer apply ( Dog dog ) ;
}
List < Integer > getAges ( List < Dog > dogs, DogAge f ) {
List < Integer > ages = new ArrayList <>() ;
for ( Dog dog : dogs ) {
ages.add ( f.apply ( dog )) ;
}
return ages;
}
我们定义一个给定狗的接口,它从中提取一些Integer值。 然后,我们定义一个函数getAges ,将传递的函数(目前为接口 )应用于每只狗。
现在,我们必须创建要应用于每只狗的实际功能。
DogAge f = dog -> dog.getAge () ;
getAges( dogs, f ) ;
注意,我们不必像在旧版Java中那样实际定义DogAge实现。 这将是以下方式,但是请不要再使用它了。
DogAge dontUseMe = new DogAge () {
@Override
public Integer apply ( Dog dog ) {
return dog.getAge () ;
}
} ;
前者实际上是由编译器在看到第一个时生成的。
我们可以更进一步,并执行以下操作。
getAges( dogs, dog -> dog.getAge ()) ;
在这里,我们将函数直接传递给getAges方法。
某种程度上, getAges是一个高阶函数,因为它可以接收函数作为参数。 Java通过接收接口来使签名保持怪异,但是我想这会在该语言的将来版本中得到改善。
为了有一个比较点,让我们在Scala中定义getAges并查看差异。 另外,我们将立即更改函数名称,以便更通用。
def extractStringFromDogs(dogs: List[Dog], f: Dog => String) =
dogs.map(f)
在Java中 ,我们可以做到。
@FunctionalInterface
interface DogMapper {
String apply ( Dog dog ) ;
}
List<String> extractStringFromDogs(List<Dog> dogs, DogMapper f) {
return dogs.stream().map(dog -> f.apply(dog)).collect(Collectors.toList);
}
碰巧Java中已经有一个结构可以解决这个问题。 那就是Function <A,B> 。 换句话说,我们可以做到。
List<String> extractStringFromDogs(List<Dog> dogs, Function<Dog, String> f) {
return dogs.stream().map(dog -> f.apply(dog)).collect(Collectors.toList);
}
extractStringFromDogs(dogs, dog -> dog.getName());
现在,如何定义实际上返回其他函数的函数呢?
在Scala中 ,我们可以执行以下操作。
scala> def sum(): (Int, Int) => Int = (a, b) => a + b
sum : ()(Int, Int) => Int
scala> sum()
res1 : (Int, Int) => Int = $$Lambda$1067/2036949810@715f45c6
scala> sum()(4,5)
res2 : Int = 9
scala> res1(2, 3)
res3 : Int = 5
在这里, sum返回一个可以在其他时间存储和评估的函数。 这是功能语言的非常强大且重要的构造。 我们可以在Java中做同样的事情吗?
让我们首先为这个特定问题定义我们自己的函数类型( Functional Interface )。
@FunctionalInterface
interface TakeTwo {
Integer apply ( Integer a, Integer b ) ;
}
如我们所见, TakeTwo在语义上与我们在Scala中定义的相同。
现在,我们可以再次定义sum方法。
TakeTwo sum () {
return ( a, b ) -> a + b;
}
TakeTwo mySum = sum() ;
Integer finalSum = mySum.apply ( 5, 6 ) ;
这与我们在Scala中所做的完全相同,只是在Scala中 ,语法简洁明了,无需定义将函数接口用作函数类型。 是的,可以达到相同的结果。
再一次,我们实际上不必自己定义TakeTwo ,因为在Java中已经定义了一个等效的接口BiFunction 。 通过使用它,我们可以通过以下方式写和 。
BiFunction < Integer, Integer, Integer > sum () {
return ( a, b ) -> a + b;
}
更多功能接口。
为了支持函数式编程, Java集成了许多这些函数式接口 。 他们之中有一些是:
消费者
Java:
public interface Consumer < T > {
void accept ( T t ) ;
....
}
斯卡拉
T => Unit
谓语
爪哇
public interface Predicate < T > {
boolean test ( T t ) ;
...
}
斯卡拉
T => boolean
供应商
爪哇
public interface Supplier < T > {
T get () ;
}
斯卡拉
:=> T
功能
爪哇
public interface Function < T, R > {
R apply ( T t ) ;
...
}
斯卡拉
T => R
双功能
爪哇
public interface BiFunction < T, U, R > {
R apply ( T t, U u ) ;
...
}
斯卡拉
(T, U) => R
这些只是新Java及其在Scala中的对等功能( 功能接口 )中的几种。 注意,在Scala中,我们不必为其定义任何接口,只需在其中具有函数即可,我们可以根据需要定义它们。
结论
Java肯定会以某种方式向函数式编程迈进,尽管语法不是最方便的一种,但结果是相同的。
另一方面, Scala语法要精确得多,并且可以更好地显示意图,而无需将接口创建为函数类型。
我只是希望Java继续发展,同时减少冗长程度并添加新的功能构造,因为最后,我们工程师将是从中获得真正好处的人。
From: https://hackernoon.com/finally-functional-programming-in-java-ad4d388fb92e