函数式编程

FunctionalInterface

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

@FunctionalInterface
public interface Callable {
    V call() throws Exception;
}

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

Lambda表达式

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

String[] array = ...
Arrays.sort(array, new Comparator() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

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

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

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

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

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

方法引用

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

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这个方法的方法签名一致,即方法参数和返回类型相同,就可以直接使用方法引用,再看一个例子:

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,相当于:

public static int compareTo(this, String o);

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

Stream

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

如何创建一个Stream?

Stream.of

Stream stream = Stream.of("A", "B", "C", "D");

数组或Collection

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

Supplier

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

public static Stream generate(Supplier s) {
    Objects.requireNonNull(s);
    return StreamSupport.stream(
    new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}

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

@FunctionalInterface
public interface Supplier {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
class NatualSupplier implements Supplier {

    int n = 0;

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

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

public static void main(String[] args) {
    Stream 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

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

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

/**
 * Returns a stream consisting of the results of applying the given
 * function to the elements of this stream.
 *
 * 

This is an intermediate * operation. * * @param The element type of the new stream * @param mapper a non-interfering, * stateless * function to apply to each element * @return the new stream */ Stream map(Function mapper);

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

@FunctionalInterface
public interface Function {

    /**
     * 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

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

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

@FunctionalInterface
public interface Predicate {

    /**
     * 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的所有元素按照聚合函数聚合成一个结果

Stream 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)

/**
 * 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相当于初始值,见源码注释:

* 
{@code
*     T result = identity;
*     for (T element : this stream)
*         result = accumulator.apply(result, element)
*     return result;
* }

输出Stream

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

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

输出为集合:

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

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

输出为数组:

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

输出为Map:

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

分组输出:

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

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

你可能感兴趣的:(java)