JAVA8--Lambda表达式
发现很久也没有写过博客了,也谢谢大家的关注,最近我会继续更新博客。在这段时间里面我打算继续为我的博客添加一下新的内容,包括 JAVA8的语法特性、继续完成Netty框架的介绍、JAVA多线程、docker jenkins在项目中的运用等。
在是回归到今天的主题上面来 ,lambda表达式。其实在其他语言上面已经早早出现了lambda表达式,但是对于一些弱类型的语言来说使用lambda表达式会更加的自然一些,但是怎么说都好Java提供了lambda表达式也是方面开发人员的一个好事,我们以后可以少写太多太多的匿名内部类了。现在我们先来看看lambda表达式会简洁到怎么的一个地步:
public static final void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("TONY");
list.add("WING");
list.add("HE");
list.add("YAN");
list.add("CHAO");
list.sort((a, b) -> b.compareTo(a));
for (String name : list) {
System.out.println(name);
}
}
我们再来对比以前原来的写法
public static final void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("TONY");
list.add("WING");
list.add("HE");
list.add("YAN");
list.add("CHAO");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
for (String name : list) {
System.out.println(name);
}
}
通过lambda表达式去编写的确是简单轻松了许多。下来我们就来详细的讲讲lambda表达式是什么使用的,希望之后通过lambda表达式能够让我们的项目代码更加简洁易用。
一、lambda表达式的语法
lambda表达式的语法相当简单,如下面伪代码所示
1、(参数) -> 表达式代码
2、(参数) -> {表达式代码}
上面两种表达式,其实都是一样的。只不过加上{}可以写多行代码而已,为了彻底明白lambda表达式的语法我们先来举个栗子。
list.sort((String a, String b) -> b.compareTo(b)); //方式1
list.sort((a, b) -> b.compareTo(a)); //方式2
list.sort((a, b) -> { //方式3
Integer result = b.compareTo(a);
return result;
});
list.sort((String a, String b) -> { //方式4
Integer result = b.compareTo(a);
return result;
});
首先我们可以直接定义参数的类型,当然我们可以通过JAVA的类型推断直接不写参数类型也可以。重点来了,在只有一行代码的时候,我们可以直接写上具体的有具体返回值的代码即可。如上面所示,我直接返回b.compareTo(a)就可以了,连return也不用写,当然写也是不合法的,只要加上{}写return才是合法的。
除了有返回值的,当然也有void的。我们来看看一下的栗子~
public static final void main(String[] args) {
Runnable runnableA = () -> System.out.print("RUN"); //方式A
Runnable runnableB = () -> { //方式B
System.out.print("RUN");
};
}
好啦基本上明白lambda表达式的语法了,那什么时候才能使用lambda表达式呢?这个要认真介绍一下函数式接口
二、函数式接口
要使用lambda表达式,前提是方法的参数的类型声明必须是相对应的函数式接口类型。例如Runnable、Comparator等都是函数式接口。当然我们也可以定义我们的函数式接口,但是在JAVA8里面就已经添加了很多实用通用型函数式接口,当然这个也是为了JAVA8的lambda表达式去准备的。
说了那么多,那为什么Runnable和Comparator这些会是函数式接口呢,其实聪明的你相信已经猜到了,就是只有一个需要实现的接口方法的接口就是函数式接口。其实也很容易去理解,毕竟lambda表达式只能表达一个方法的实现,所以如果接口里面有多个需要实现的接口方法,这可搞不掂了。
我们试试自己定义一个函数式接口,然后通过lambda表达式进行实现试试吧····
@FunctionalInterface
public interface FilterInterface<T> {
boolean check(T target);
}
上面就是最典型的函数式,但是@FunctionalInterface 又是什么东西呢。FunctionalInterface标明是一个函数式接口,当打上这个annotation之后会检查当前接口是否满足函数式接口的规范,作用和override一样都是用于编译检查的。所以不打@FunctionalInterface也是没有问题的,但是为了规范也为了减少出错还是打上去比较稳妥,毕竟在你重写方法的时候也会打上@Override嘛
然后我们开始试试用lambda表达式来写实现
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
names = getEnglishNames(names, (name) -> name.equals("TONY") || name.equals("WING"));
}
public static List<String> getEnglishNames(List<String> sources, FilterInterface<String> filter) {
List<String> results = new ArrayList<>();
for (String name : sources) {
if (filter.check(name)) {
results.add(name);
}
}
return results;
}
嗯~ 可能你看见了,我上面函数式接口用了泛型。现在对于lambda表达式已经有一个很基础的概念了,下面就介绍一下JAVA所提供的标准函数式接口,省去你定义函数式接口的麻烦。
三、使用java.util.function 提供的标准函数式接口
来来来,先来个官方的表格让大家看看 JAVA8的标准库设计大师给了我们什么好东西。
Interface | Description |
---|---|
BiConsumer<T,U> | Represents an operation that accepts two input arguments and returns no result. |
BiFunction<T,U,R> | Represents a function that accepts two arguments and produces a result. |
BinaryOperator<T> | Represents an operation upon two operands of the same type, producing a result of the same type as the operands. |
BiPredicate<T,U> | Represents a predicate (boolean-valued function) of two arguments. |
BooleanSupplier | Represents a supplier of |
Consumer<T> | Represents an operation that accepts a single input argument and returns no result. |
DoubleBinaryOperator | Represents an operation upon two |
DoubleConsumer | Represents an operation that accepts a single |
DoubleFunction<R> | Represents a function that accepts a double-valued argument and produces a result. |
DoublePredicate | Represents a predicate (boolean-valued function) of one |
DoubleSupplier | Represents a supplier of |
DoubleToIntFunction | Represents a function that accepts a double-valued argument and produces an int-valued result. |
DoubleToLongFunction | Represents a function that accepts a double-valued argument and produces a long-valued result. |
DoubleUnaryOperator | Represents an operation on a single |
Function<T,R> | Represents a function that accepts one argument and produces a result. |
IntBinaryOperator | Represents an operation upon two |
IntConsumer | Represents an operation that accepts a single |
IntFunction<R> | Represents a function that accepts an int-valued argument and produces a result. |
IntPredicate | Represents a predicate (boolean-valued function) of one |
IntSupplier | Represents a supplier of |
IntToDoubleFunction | Represents a function that accepts an int-valued argument and produces a double-valued result. |
IntToLongFunction | Represents a function that accepts an int-valued argument and produces a long-valued result. |
IntUnaryOperator | Represents an operation on a single |
LongBinaryOperator | Represents an operation upon two |
LongConsumer | Represents an operation that accepts a single |
LongFunction<R> | Represents a function that accepts a long-valued argument and produces a result. |
LongPredicate | Represents a predicate (boolean-valued function) of one |
LongSupplier | Represents a supplier of |
LongToDoubleFunction | Represents a function that accepts a long-valued argument and produces a double-valued result. |
LongToIntFunction | Represents a function that accepts a long-valued argument and produces an int-valued result. |
LongUnaryOperator | Represents an operation on a single |
ObjDoubleConsumer<T> | Represents an operation that accepts an object-valued and a |
ObjIntConsumer<T> | Represents an operation that accepts an object-valued and a |
ObjLongConsumer<T> | Represents an operation that accepts an object-valued and a |
Predicate<T> | Represents a predicate (boolean-valued function) of one argument. |
Supplier<T> | Represents a supplier of results. |
ToDoubleBiFunction<T,U> | Represents a function that accepts two arguments and produces a double-valued result. |
ToDoubleFunction<T> | Represents a function that produces a double-valued result. |
ToIntBiFunction<T,U> | Represents a function that accepts two arguments and produces an int-valued result. |
ToIntFunction<T> | Represents a function that produces an int-valued result. |
ToLongBiFunction<T,U> | Represents a function that accepts two arguments and produces a long-valued result. |
ToLongFunction<T> | Represents a function that produces a long-valued result. |
UnaryOperator<T> | Represents an operation on a single operand that produces a result of the same type as its operand. |
看见了吗?一大堆当然我不会一个个去说,说一些重点的,其他那些估计也少用,而且会用以下说的 其他一看就懂了。
来来来同学们划重点,其中最重要的几个接口:Predicate、Consumer、Function、Supplier
1、java.util.function.Predicate<T>
我们来看看Predicate的源码。【由于篇幅有限,我把定义了default的方法全部删掉了】
/**
* 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);
}
没错这个就是跟我定义的FilterInterface<T>接口差不多,主要是用于做参数的判断。我们将之前的代码改改····
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
names = getEnglishNames(names, (name) -> name.equals("TONY") || name.equals("WING"));
}
//filter改成Predicate接口
public static List<String> getEnglishNames(List<String> sources, Predicate<String> filter) {
List<String> results = new ArrayList<>();
for (String name : sources) {
if (filter.test(name)) { //check方法改成Predicate的test方法
results.add(name);
}
}
return results;
}
这样就没有必要自己去定义一个新的函数式接口了。
2、java.util.function.Consumer<T>
老规矩看看里面的源码【由于篇幅有限,我把定义了default的方法全部删掉了】
/**
* 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);
}
嗯~!估计也不用我多说了~ 就是接受一个参数,然后做一些自定义业务,业务方法中【即自定义的lambda表达式】不返回任何值。
来个demo看看怎么用:
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
doSomethingWithNames(names, (name) -> {
if (name.equals("TONY") || name.equals("WING")) {
System.err.println("English name : " + name);
} else {
System.out.println(name);
}
});
}
public static void doSomethingWithNames(List<String> names, Consumer<String> doSomething) {
for (String name : names) {
doSomething.accept(name);
}
}
3、java.util.function.Function<T>
老规矩上源码
/**
* 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);
}
传入一个T类型的参数,返回一个R类型的值。貌似这个更加灵活了,再举一个栗子:
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
doSomethingWithNames(names, (name) -> {
return name.length();
});
}
public static void doSomethingWithNames(List<String> names, Function<String, Integer> doSomething) {
for (String name : names) {
System.out.println("result:" + doSomething.apply(name));
}
}
4、java.util.function.Supplier<T>
上源码
/**
* 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();
}
这个多数用作于获得一个对象,再次举个栗子:
public static final void main(String[] args) {
printSomething(() -> new StringBuffer("TONY").append("&WING"));
}
public static void printSomething(Supplier s) {
System.out.println(s.get().toString());
}
5、各种类型的标准函数式接口
为什么有DoubleConsumer、BiConsumer<T,U>、DoubleFunction<R>、IntBinaryOperator、ToDoubleBiFunction<T,U> 等等这些标准的函数式接口呢?其实原因很简单,有些是为了使用基础类型不需要去装箱拆箱的操作【例如:DoubleFunction<R>、LongToIntFunction】,有些是为了提供多个参数【例如:BiConsumer<T,U>】。
继续看看源码就懂了
/**
* Represents an operation that accepts two input arguments and returns no
* result. This is the two-arity specialization of {@link Consumer}.
* Unlike most other functional interfaces, {@code BiConsumer} 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, Object)}.
*
* @param <T> the type of the first argument to the operation
* @param <U> the type of the second argument to the operation
*
* @see Consumer
* @since 1.8
*/
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
}
/**
* Represents a function that accepts a long-valued argument and produces a
* double-valued result. This is the {@code long}-to-{@code double} primitive
* specialization for {@link Function}.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #applyAsDouble(long)}.
*
* @see Function
* @since 1.8
*/
@FunctionalInterface
public interface LongToDoubleFunction {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
double applyAsDouble(long value);
}
是不是都清晰了?其实JAVA针对lambda表达式所设计的函数式接口,已经是足够的通用了。其实我们已经看了许多的function包里的函数式接口了,其他慢慢摸索就可以啦。但是除了这些还有其他玩法喔···· 就是我们忽略的default方法,本文后面会介绍到。
6、标准函数式接口复合使用
刚刚不说了遗漏了关于function包里面的函数式接口的default方法的讲解吗?现在就来说说,其实在这些接口里面有很多复合使用的方式提供给我们,我们先举例看看源码是怎么定义的
/**
* 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);
}
}
我们可以看到Predicate里面可以有 or and 等等这些default方法,所以我们可以通过这些方法进行and or的操作。看看如何去利用Predicate的or方法进行复合使用:
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
//等价于之前 (name)->name.equals("TONY") || name.equals("WING")
Predicate<String> predicate = (name)->name.equals("TONY");
predicate = predicate.or((name)->name.equals("WING"));
names = getEnglishNames(names,predicate);
doSomethingWithNames(names, System.out::println);
}
//filter改成Predicate接口
public static List<String> getEnglishNames(List<String> sources, Predicate<String> filter) {
List<String> results = new ArrayList<>();
for (String name : sources) {
if (filter.test(name)) {
results.add(name);
}
}
return results;
}
再来看看Consumer类,里面也有个andThen的方法。
@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); };
}
}
最后看看Function方法,Function方法也有个andThen和compose方法 具体用法其实看看代码就相当清楚了
/**
* 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;
}
}
四、Lambda表达式异常
关于lambda表达式的异常处理,如果在函数式接口当中没有定义其抛出的异常的话lambda表达式必须try catch捕获并处理异常,不能直接抛出。例如下面这种情况就会出现编译失败
//lambda表达式
doSomethingWithNames(names, (name) -> {
throw new Exception();
return name.length();
});
//对应的函数式接口
@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); //没有定义抛出的异常,所以上面的lambda表达式编译失败
}
如果我们希望lambda表达式当中不直接处理异常,我们可以在函数式接口的方法上面定义抛出的异常
@FunctionalInterface
public interface FilterInterface<T> {
boolean check(T target) throws Exception;
}
五、方法引用
当我看完JAVA的方法引用后,我觉得虽然简短,但是看上去易读性并不高。可能是我们这些老JAVA程序员来说,以前的语法已经对于我们来说根深蒂固的原因吧。先看看什么是方法引用
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
names.sort(Comparator.comparing(String::length));
}
是不是有点难懂,我一开始也是觉得有点难懂。什么鬼啊···· 好说说究竟是什么鬼
首先我们先看看Comparator.comparing方法里面究竟是什么东西
/**
* Accepts a function that extracts a {@link java.lang.Comparable
* Comparable} sort key from a type {@code T}, and returns a {@code
* Comparator<T>} that compares by that sort key.
*
* <p>The returned comparator is serializable if the specified function
* is also serializable.
*
* @apiNote
* For example, to obtain a {@code Comparator} that compares {@code
* Person} objects by their last name,
*
* <pre>{@code
* Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
* }</pre>
*
* @param <T> the type of element to be compared
* @param <U> the type of the {@code Comparable} sort key
* @param keyExtractor the function used to extract the {@link
* Comparable} sort key
* @return a comparator that compares by an extracted key
* @throws NullPointerException if the argument is null
* @since 1.8
*/
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
可以看到的是里面是一个Function的参数,会通过keyExtractor获得其返回的值,然后通过这个值去进行排序。那么String::length 又代表了什么啊?事实上String::length就是Function入参的类型,然后通过类型获得其对象的方法引用。简单来说就是入参会是String类型,并且调用其对象的length方法获得值,然后在comparing方法当中获得这个值并通过compareTo方法进行对比。排序后的结果如下:
HE
YAN
TONY
WING
Process finished with exit code 0
我们再举个例子应该就懂了,打印输出一个列表:
public static final void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("TONY");
names.add("YAN");
names.add("WING");
names.add("HE");
names.sort(Comparator.comparing(String::length));
doSomethingWithNames(names, System.out::println);
class Test {
private String a;
Test() {
a = "ABC";
}
@Override
public String toString() {
return a;
}
}
printSomething(Test::new);
}
public static void doSomethingWithNames(List<String> names, Consumer<String> doSomething) {
for (String name : names) {
doSomething.accept(name);
}
}
public static void printSomething(Supplier s) {
System.out.println(s.get().toString());
}
给我的感觉真的是,可以啥写····· 怎么都好多联系几次基本上就明白怎么去写了。
六、总结
其实看完lambda表达式之后,感觉的确是比以前方便简洁了许多intellij IDE 针对lambda表达式也会有接口跳转。但是对于一些项目比较老旧就无法使用了,如果是作为一个产品有可能部署到一个JAVA环境版本较低的系统上使用时还说要三思而后行。不过JAVA8这次对于语法上提供很更多的支持也是一件非常好的事情,起码能够跟上时代潮流的节奏嘛···· 后面会陆陆续续添加JAVA8的新语法介绍 有兴趣的可以留意下。如果有写的不对的地方希望有评论留言给我,我会及时更正。大家也可以在评论区踊跃评论·····