FunctionalInterface

我们把只定义了单方法的接口称之为FunctionalInterface,用注解@FunctionalInterface标记。例如,Callable接口:

1
2
3
4
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}

支持函数式编程的都可以使用Lambda表达式

Lambda表达式

当我们用Arrays.sort()排序时,可以传入一个Comparator实例,并采用匿名类的方式来实现:

1
2
3
4
5
6
String[] array = ...
Arrays.sort(array, new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});

但是这种写法还是挺繁琐的,所以从JDK8开始,我们可以用Lambda表达式来替代这种繁琐的写法:

1
2
3
4
String[] array = ...
Arrays.sort(array, (s1, s2) -> {
return s1.compareTo(s2);
});

使用Lambda只需要写出方法的定义,参数类型可以省略,编译器会自动推断出String类型,-> { ... }表示方法体,如果只有一行return代码,还可以更加简洁:

1
Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));

返回值的类型也是由编译器自动推断的,这里推断出的返回值是int,因此,只要返回int,编译器就不会报错。

方法引用

除了使用Lambda之外,还可以直接传入方法引用:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
String[] array = new String[]{"Apple", "Orange", "Banana", "Lemon"};
Arrays.sort(array, Main::cmp);
System.out.println(String.join(", ", array));
}

static int cmp(String s1, String s2) {
return s1.compareTo(s2);
}

上面的代码是啥意思?可以看到Arrays.sort需传入一个数组和Comparator接口,在Comparator中有个方法int compare(T o1, T o2),我们自己定义的方法cmpcompare这个方法的方法签名一致,即方法参数和返回类型相同,就可以直接使用方法引用,再看一个例子:

1
2
3
4
5
public static void main(String[] args) {
String[] array = new String[]{"Apple", "Orange", "Banana", "Lemon"};
Arrays.sort(array, String::compareTo);
System.out.println(String.join(", ", array));
}

查看StringcompareTo方法发现参数只有一个,但是前面不是说方法签名要一致吗,这又是怎么回事?因为之前的方法是个静态方法,这里是一个实例方法,实例方法第一个隐含参数总是传入this,相当于:

1
public static int compareTo(this, String o);

所以String::compareTocompare方法签名是一致的

Stream

一个全新的流失API,可以存储有限或无限个元素,Stream是惰性计算,计算通常时发生在最后结果的获取,因此,Stream API的基本用法就是:创建一个Stream,然后做若干次转换,最后调用一个求值方法获取真正计算的结果:

如何创建一个Stream?

Stream.of

1
Stream<String> stream = Stream.of("A", "B", "C", "D");

数组或Collection

1
2
Stream<String> stream = Arrays.stream(new String[]{"A", "B", "C"});
Stream<String> stream1 = List.of("A", "B", "C").stream();

Supplier

通过Stream.generate()需传入Supplier对象

1
2
3
4
5
public static<T> Stream<T> generate(Supplier<? extends T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}

Supplier是一个函数式接口,我们可以自己实现这个接口

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Supplier<T> {

/**
* Gets a result.
*
* @return a result
*/
T get();
}
1
2
3
4
5
6
7
8
9
class NatualSupplier implements Supplier<Integer> {

int n = 0;

@Override
public Integer get() {
return n++;
}
}

基于Supplier创建的Stream会不断调用get()产生下一个元素,可以用来表示无限序列

1
2
3
4
public static void main(String[] args) {
Stream<Integer> stream = Stream.generate(new NatualSupplier());
stream.limit(20).forEach(System.out::println);
}

因为它会不断调用get(),我们必须设定一个界限stream.limit(20)

基本类型

因为Java的泛型不支持基本类型的,只能用Integer等包装类型,但是Stream会对频繁的拆箱装箱,所以为了提高效率,Java标准库给我们提供了三种使用基本类型的Stream -> IntStreamLongStreamDoubleStream

map

Stream.map()是一个转换方法,将一个Stream转为另一个Stream

1
2
Stream<Integer> stream = List.of(1, 2, 3).stream();
Stream<Integer> streamMap = stream.map(item -> item * item);

我们看看map(),最终会返回一个新结果的Stream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Stream.map()传入的是函数式接口Functionapply()最终return计算的结果

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface Function<T, R> {

/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
...
}

filter

Stream.filter()Stream的另一个常用转换方法

filter即过滤,过滤掉不满足条件的元素,满足条件的构成一个新的Stream

1
2
Stream<Integer> stream = List.of(3, 4, 6).stream();
Stream<Integer> streamMap = stream.filter(item -> item % 2 == 0);

filter接收Predicatetest()过滤掉不满足条件的元素

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface Predicate<T> {

/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}

reduce

Stream的一个聚合方法,把一个Stream的所有元素按照聚合函数聚合成一个结果

1
2
Stream<Integer> stream = List.of(3, 4, 6).stream();
Integer reduce = stream.reduce(0, (acc, n) -> acc + n);

Stream.reduce()接收BinaryOperator,而它又继承自BiFunction,在BiFunction中有R apply(T t, U u)

1
2
3
4
5
6
7
8
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);

所以BinaryOperator实际上是重写了父接口的方法apply(),通过这个方法进行累加计算

第一个参数0相当于初始值,见源码注释:

1
2
3
4
5
6
* <pre>{@code
* T result = identity;
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
* }</pre>

输出Stream

Streammap()filter()操作时是不会进行任何计算的,reduce会立即得出结果

如何把进行了转换操作的元素保存下来呢?

输出为集合:

collect()并传入Collectors.toList()对象:

1
2
Stream<Integer> stream = List.of(3, 4, 6).stream();
List<Integer> list = stream.map(n -> n * n).collect(Collectors.toList());

输出为数组:

1
2
Stream<String> stream = List.of("Apple", "Banana", "Pear").stream();
String[] array = stream.toArray(String[]::new);

输出为Map:

1
2
3
4
5
6
7
8
Stream<String> stream = List.of("Apple:Banana", "Pear:Peach").stream();
Map<String, String> map = stream.collect(Collectors
.toMap(
// 映射为key
s -> s.substring(0, s.indexOf(':')),
// 映射为value
s -> s.substring(s.indexOf(':') + 1)
));

分组输出:

1
2
3
Stream<String> stream = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots").stream();
Map<String, List<String>> groups = stream.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()
));

上述代码中Collectors.groupingBy以元素的首字母为依据做一个分组