函数式编程
1. lambda
1.1 函数式编程思想概述
- 面向对象的思想:
做一件事,找一个能解决这个事情的对象,调用对象的方法完成事情。 - 函数式编程的思想:
只要能获取到结果就可以了,谁去做,怎么做都无所谓。只关心结果不关心过程
1.2 lambda表达式的演化
1. 匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("创建了一个新的线程1");
}
}).start();
// 我们使用内部类的根部目的是将这个方法传进去,具体一点就是将 用于打印的这条语句传进去,而创建Runnable这个类了是没有必要的。
2. lambda表达式(最优写法)
new Thread(()-> System.out.println("使用lambda表达式创建了一个新的进程!")).start();
// 注意:可以把lambda表达式看作是匿名内部类的一种演变,但是二者还是有所不同的。匿名内部类会生成一个匿名内部类的文件,但是lambda表达式不会出现这种问题。
1.3 lambda表达式的标准格式
-
参数、箭头、方法 (参数列表)-> {一些重写方法中的代码}
():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔。
->:传递的意思,把参数传递给方法体
{}:重写接口的抽象方法的方法体
-
demo
1. 创建一个接口,这个接口中只有一个抽象方法
public interface Cook {
/**
* 定义一个做饭的抽象方法
*/
void makeFood();
}
2. 创建一个方法,这个方法的参数就是上面这个抽象类
public static void eatFood(Cook cook){
cook.makeFood();
}
3. 调用该方法
public static void main(String[] args) {
eatFood(()-> System.out.println("厨师做饭"));
}
1.4 lambda表达式简化规则
- lambda表达式:可推导,可省略
凡是根据上下文推导出来的内容都可以省略书写
-
lambda表达式可省略的内容:
-
(参数列表):参数列表中的数据类型,可以省略不写。
-
(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
-
{一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略{},return,‘;’分号
注意:要省略必须这三个一起省略
-
1.5 lambda表达式的使用前提
-
使用lambda表达式必须具有接口,且要求接口有且仅有一个抽象方法。但是可以有其他的静态或者默认的方法,私有方法也是可以的。
-
使用lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为lambda对应的接口类型,才能使用lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”
1.6 @FunctionalInterface
-
作用:可以检测接口是否是一个函数式接口
-
如果是函数时接口编译就会成功
-
如果不是函数式接口编译就会失败
通常造成编译失败的原因有,没有抽象方法,或者抽象方法不止一个
-
2. 函数式编程
lambda表达式有延时执行的特点,可以利用这个特点对一些代码进行优化
2.1 lambda作为参数和返回值
1. lambda作为参数使用的场景经常出现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("创建了一个新的线程1");
}
}).start();
2. lambda作为返回值使用
public static Cook getCook(){
return ()-> System.out.println("你获取到了一个厨师");
}
// 这种方式也是挺常见的,但是没有作为参数常见。其本质就是生成一个实现了该接口的匿名内部类。但是该过程没有生成匿名内部类的文件。
2.2 常用的函数式接口
通常这些接口都被放在java.util.function包中
2.2.1 Supplier接口 —英文翻译 供应商
package java.util.function;
/**
* Represents a supplier of results. 表示结果的提供者
*
* <p>There is no requirement that a new or distinct result be returned each
* time the supplier is invoked.不需要每次调用供应商时都返回新的或不同的结果
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #get()}.
*
* @param <T> the type of results supplied by this supplier 由该供应商提供的返回结果的类型
*
* @since 1.8
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
- 作用:用来获取一个泛型参数指定类型的对象数据
- Supplier<T> 接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。
// 定义一个方法,该方法将一个函数式接口作为参数,所以在调用该方法的时候可以使用lambda来传递参数。 本质: 在调用该方法的时候 会生成一个该接口的对象,即匿名内部类。lambda表达式中就是该类中唯一抽象函数的实现。 而在该方法中使用了该匿名内部类去掉用了唯一抽象方法的实现。
public static User getUser(Supplier<User> supplier){
return supplier.get();
}
User zhanghan = getUser(() -> new User("zhanghan", 18));
System.out.println(zhanghan);
// 而 Supplier<T> 该接口存在的意义,就是不用我们自己再去写一个这种获取对象的函数式接口
2.2.2 Consumer接口
package java.util.function;
import java.util.Objects;
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
* 表示接受单个输入参数但不返回结果的操作。与大多数其他功能接口不同,消费者需要通过副作用进行操作。
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
- Consumer<T>接口正好有Supplier<T>接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
- void accept(T t);
public static void accept(Consumer<User> userConsumer,User user){
userConsumer.accept(user);
}
accept((user)-> System.out.println(user),zhanghan);
// 不要被这个函数式接口的名字所迷惑,虽然消费的确形象却也容易限制思维。 这个函数式接口的作用就是给定一个参数,对这个参数做操作还不用返回值。 需要将,操作方法和对象一起当作参数传进方法。
-
default Consumer andThen(Consumer<? super T> after)
作用:需要两个Consumer接口,可以把两个consumer接口组合到一起,再对数据进行消费。
1. 本质:一共生成了三个匿名内部类,而第三个匿名内部类的accept方法就是前两个accept方法的集合。
2. public static void andThenAccept(Consumer<String> c1,Consumer<String> c2,String string){
c1.andThen(c2).accept(string); // 谁在前面就先消费谁
}
andThenAccept((t1)-> System.out.println(t1.toUpperCase())
,(t2)-> System.out.println(t2.toLowerCase())
,"Hello");
3. 分析:
a. 首先会根据(t1)-> System.out.println(t1.toUpperCase())创建一个匿名内部类c1,
它的accept方法 accept(String str){System.out.println(t1.toUpperCase());}
b. 根据(t2)-> System.out.println(t2.toLowerCase() 创建一个匿名内部类c2,
它的accept方法 accept(String str){System.out.println(t1.toLowerCase());}
c. 创建一个String 对象 string = "Hello"
d. 执行方法体中的代码。
c1.andThen(c2) => return (string) -> { c1.accept(string); c2.accept(string); } => c3 匿名内部类 c3的accetp方法 accept(string){c1.accept(string),c2.accept(string)}
e. c3.accept(string)
2.2.3 Predicate接口
package java.util.function;
import java.util.Objects;
/**
* Represents a predicate (boolean-valued function) of one argument.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #test(Object)}.
*
* @param <T> the type of the input to the predicate
*
* @since 1.8
*/
@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);
/**
* Returns a composed predicate that represents a short-circuiting logical
* AND of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code false}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ANDed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* AND of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* Returns a composed predicate that represents a short-circuiting logical
* OR of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code true}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ORed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* OR of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* Returns a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}.
*
* @param <T> the type of arguments to the predicate
* @param targetRef the object reference with which to compare for equality,
* which may be {@code null}
* @return a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
- 本质:给它一个对象和一个判定条件他去判断这个对象符不符合这个条件,然后给你一个返回
1. boolean test(T t)
public static boolean isTure(Predicate<String> predicate,String str){
return predicate.test(str);
}
System.out.println(isTure((str)->{if ("zhanghan".equals(str)) return true; else return false;},"zhang"));
2. default Predicate<T> negate()
public static boolean isNegateTure(Predicate<String> predicate,String str){
return predicate.negate().test(str);
}
System.out.println(isNegateTure((str)->{if ("zhanghan".equals(str)) return true; else return false;},"zhang"));
// 分析:一共会创建两个匿名内部类,第二个匿名内部类调用了第一个匿名内部类的test方法
3. default Predicate<T> and(Predicate<? super T> other)
public static boolean isAndTure(Predicate<String> predicate,Predicate<String> predicate2,String str){
return predicate.and(predicate2).test(str);
}
System.out.println(isAndTure((str1)->"zhanghan".equals(str1)?true:false
,(str2)->str2.length() > 3,"zhanghan"));
// 分析:一共创建了三个匿名内部类,在第三个匿名内部类的test方法中做了一个与的判断。
4. default Predicate<T> or(Predicate<? super T> other)
public static boolean isOrTure(Predicate<String> predicate,Predicate<String> predicate2,String str){
return predicate.or(predicate2).test(str);
}
System.out.println(isOrTure((str1)->"zhan".equals(str1)?true:false
,(str2)->str2.length() > 3,"zhanghan"));
5. static <T> Predicate<T> isEqual(Object targetRef)
// todo 这个后期补上 等学完stream相关的知识
2.2.4 Function接口 --感觉更实用一些
package java.util.function;
import java.util.Objects;
/**
* Represents a function that accepts one argument and produces a result.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object)}.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*
* @since 1.8
*/
@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);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
- 本质:将一个对象传入进行相关的操作返回,并且返回的结果还不用和传入时相同
1. R apply(T t)
public static String convert(Function<Integer,String> f1,Integer n1){
return f1.apply(n1);
}
System.out.println(convert(x -> String.valueOf(x) + "nb",23));
2. default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
public static Object convert2(Function<Integer,String> f1,Function<String,List> f2,Integer n1){
return f1.andThen(f2).apply(n1);
}
System.out.println(convert2(x -> String.valueOf(x) + "nb",y -> {List<String> list = new ArrayList<>();
list.add(y);list.add("zhanghan");
return list;},23));
// 分析:主要过程就是创建三个匿名内部类,在第三个匿名内部类中,第一个转换的结果作为第二个转换的原数据。 要注意的是继承关系,
3. default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
// 作用和上一个一样也是为了连续的类型变换
4. static <T> Function<T, T> identity()
Function.identity() // 只能使用这种方式调用
3. stream流
- 这里所说的stream流不是I/O stream流 I/O流主要是进行读写操作,这里的stream流主要是对集合的一些操作。
3.1 流式思想概述
- 这里的流的概念更类似于流水线的概念
- 得益于lambda表达式的延迟执行的特性。在流的过成功集合元素并不会真的被处理,只有到最后的时候才会按照指定策略执行操作。
- 流的执行过程并不会存储数据,只是对数据进行运算,就像是生产的流水线一样,每一个步骤只会对商品进行加工,而不会将商品留下来。
- 数据源 流的来源,可以是集合,数组等。
- Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路。
- 内部迭代:以前对集合遍历都是通过Iterator或者增强for的方式。显式的在集合外部进行迭代,这叫做外部迭代。stream提供了内部迭代的方式,流可以直接调用遍历方法。
- 使用步骤:a. 获取一个数据源 ->b. 数据转换 ->c. 执行操作获取想要的结果, 每次转换原有的stream对象不改变,返回一个新的stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
3.2 获取流
java.util.stream.Stream<T>
是Java 8 新加入的最常用的流接口。(这并不是一个函数式接口)
- 所有的
Collection
集合都可以通过stream
默认方式获取流。 Stream
接口的静态方法of
可以获取数组对应的流。
3.3 常用方法
- 延迟方法:返回值类型仍然是
stream
接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余的方法均为延迟方法。) - 终结方法:返回值类型不再是
Stream
接口自身类型的方法,因此不再支持链式调用。 - 注意:延迟操作所返回的stream对象已经不是原先的
3.3.1 逐一处理:forEach
/**
* Performs an action for each element of this stream.
*
* <p>This is a <a href="package-summary.html#StreamOps">terminal
* operation</a>.
*
* <p>The behavior of this operation is explicitly nondeterministic.
* For parallel stream pipelines, this operation does <em>not</em>
* guarantee to respect the encounter order of the stream, as doing so
* would sacrifice the benefit of parallelism. For any given element, the
* action may be performed at whatever time and in whatever thread the
* library chooses. If the action accesses shared state, it is
* responsible for providing the required synchronization.
*
* @param action a <a href="package-summary.html#NonInterference">
* non-interfering</a> action to perform on the elements
*/
void forEach(Consumer<? super T> action);
// 使用
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
list.stream().forEach(x -> System.out.println("字符串是:"+ x));
3.3.2 过滤:filter
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
// 使用
list.stream().filter(x -> x.charAt(0) >'c' && x.charAt(0) <'f'?true:false).forEach(x -> System.out.println(x));
// ps:String 与 String之间是不能直接进行比较操作的。但是char之间是可以的
3.3.3 映射:map
/**
* 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.of("123456").map(x -> Integer.parseInt(x)).forEach(x-> System.out.println(x.getClass()));
3.3.4 统计个数:count
/**
* Returns the count of elements in this stream. This is a special case of
* a <a href="package-summary.html#Reduction">reduction</a> and is
* equivalent to:
* <pre>{@code
* return mapToLong(e -> 1L).sum();
* }</pre>
*
* <p>This is a <a href="package-summary.html#StreamOps">terminal operation</a>.
*
* @return the count of elements in this stream
*/
long count();
// 使用
System.out.println(list.stream().count());
3.3.5 截取前几个:limit
/**
* Returns a stream consisting of the elements of this stream, truncated
* to be no longer than {@code maxSize} in length.
*
* <p>This is a <a href="package-summary.html#StreamOps">short-circuiting
* stateful intermediate operation</a>.
*
* @apiNote
* While {@code limit()} is generally a cheap operation on sequential
* stream pipelines, it can be quite expensive on ordered parallel pipelines,
* especially for large values of {@code maxSize}, since {@code limit(n)}
* is constrained to return not just any <em>n</em> elements, but the
* <em>first n</em> elements in the encounter order. Using an unordered
* stream source (such as {@link #generate(Supplier)}) or removing the
* ordering constraint with {@link #unordered()} may result in significant
* speedups of {@code limit()} in parallel pipelines, if the semantics of
* your situation permit. If consistency with encounter order is required,
* and you are experiencing poor performance or memory utilization with
* {@code limit()} in parallel pipelines, switching to sequential execution
* with {@link #sequential()} may improve performance.
*
* @param maxSize the number of elements the stream should be limited to
* @return the new stream
* @throws IllegalArgumentException if {@code maxSize} is negative
*/
Stream<T> limit(long maxSize);
// 使用
list.stream().limit(4).forEach(s -> System.out.println(s));
3.3.6 跳过前几个:skip
/**
* Returns a stream consisting of the remaining elements of this stream
* after discarding the first {@code n} elements of the stream.
* If this stream contains fewer than {@code n} elements then an
* empty stream will be returned.
*
* <p>This is a <a href="package-summary.html#StreamOps">stateful
* intermediate operation</a>.
*
* @apiNote
* While {@code skip()} is generally a cheap operation on sequential
* stream pipelines, it can be quite expensive on ordered parallel pipelines,
* especially for large values of {@code n}, since {@code skip(n)}
* is constrained to skip not just any <em>n</em> elements, but the
* <em>first n</em> elements in the encounter order. Using an unordered
* stream source (such as {@link #generate(Supplier)}) or removing the
* ordering constraint with {@link #unordered()} may result in significant
* speedups of {@code skip()} in parallel pipelines, if the semantics of
* your situation permit. If consistency with encounter order is required,
* and you are experiencing poor performance or memory utilization with
* {@code skip()} in parallel pipelines, switching to sequential execution
* with {@link #sequential()} may improve performance.
*
* @param n the number of leading elements to skip
* @return the new stream
* @throws IllegalArgumentException if {@code n} is negative
*/
Stream<T> skip(long n);
// 使用
list.stream().skip(4).forEach(s -> System.out.println(s));
3.3.7 组合:concat
/**
* Creates a lazily concatenated stream whose elements are all the
* elements of the first stream followed by all the elements of the
* second stream. The resulting stream is ordered if both
* of the input streams are ordered, and parallel if either of the input
* streams is parallel. When the resulting stream is closed, the close
* handlers for both input streams are invoked.
*
* @implNote
* Use caution when constructing streams from repeated concatenation.
* Accessing an element of a deeply concatenated stream can result in deep
* call chains, or even {@code StackOverflowException}.
*
* @param <T> The type of stream elements
* @param a the first stream
* @param b the second stream
* @return the concatenation of the two input streams
*/
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
// 使用
Stream.concat(stream,stream1).forEach(x -> System.out.println(x));
3.3.8 收集:collect
/**
* Performs a <a href="package-summary.html#MutableReduction">mutable
* reduction</a> operation on the elements of this stream using a
* {@code Collector}. A {@code Collector}
* encapsulates the functions used as arguments to
* {@link #collect(Supplier, BiConsumer, BiConsumer)}, allowing for reuse of
* collection strategies and composition of collect operations such as
* multiple-level grouping or partitioning.
*
* <p>If the stream is parallel, and the {@code Collector}
* is {@link Collector.Characteristics#CONCURRENT concurrent}, and
* either the stream is unordered or the collector is
* {@link Collector.Characteristics#UNORDERED unordered},
* then a concurrent reduction will be performed (see {@link Collector} for
* details on concurrent reduction.)
*
* <p>This is a <a href="package-summary.html#StreamOps">terminal
* operation</a>.
*
* <p>When executed in parallel, multiple intermediate results may be
* instantiated, populated, and merged so as to maintain isolation of
* mutable data structures. Therefore, even when executed in parallel
* with non-thread-safe data structures (such as {@code ArrayList}), no
* additional synchronization is needed for a parallel reduction.
*
* @apiNote
* The following will accumulate strings into an ArrayList:
* <pre>{@code
* List<String> asList = stringStream.collect(Collectors.toList());
* }</pre>
*
* <p>The following will classify {@code Person} objects by city:
* <pre>{@code
* Map<String, List<Person>> peopleByCity
* = personStream.collect(Collectors.groupingBy(Person::getCity));
* }</pre>
*
* <p>The following will classify {@code Person} objects by state and city,
* cascading two {@code Collector}s together:
* <pre>{@code
* Map<String, Map<String, List<Person>>> peopleByStateAndCity
* = personStream.collect(Collectors.groupingBy(Person::getState,
* Collectors.groupingBy(Person::getCity)));
* }</pre>
*
* @param <R> the type of the result
* @param <A> the intermediate accumulation type of the {@code Collector}
* @param collector the {@code Collector} describing the reduction
* @return the result of the reduction
* @see #collect(Supplier, BiConsumer, BiConsumer)
* @see Collectors
*/
<R, A> R collect(Collector<? super T, A, R> collector);
// 传入一个收集器
Set<String> set = list.stream().collect(Collectors.toSet());