typora-root-url: ./
Lambda表达式
函数式编程是一种相对于命令式编程的编程范式,命令式编程关注的是怎么做,函数式编程关注的是做什么,不需要关注实现的细节。
1、lamda表达式简介
什么是lambda?
我们知道,对于一个Java变量,我们可以赋给其一个“值”,如果你想把“一块代码”赋给一个Java变量,应该怎么做呢?
比如,把右边那块代码赋给一个叫做aBlockOfCode的Java变量:
在Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。
当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加优雅, 我们可以移除一些没用的声明。
这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。
Lambda表达式是Java 8推出的⼀个新特性。从本质上讲,Lambda表达式是⼀个匿名函数。使用Lambda表达式可以对⼀个接⼝进行非常简洁的实现。之前我们在给一个接口引用赋值的时候,可以使用接⼝实现类,或者匿名内部类。但是有了Lambda表达式,我们可以更加方便的实现这个需求。
//从匿名内部类到lambda转换
//jdk8 以前--匿名内部类实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
}).start();
//jdk8--lambda
new Thread(() -> System.out.println("run")).start();
lambda表达式:返回了一个实现了指定接口的对象实例
“Java库中不存在名为Lambda的类,lambda表达式只能被赋给函数式接口的引用”
2、lambda表达式语法
java8中引入了一个新的操作符“->”,该操作符称为箭头操作符或lambda操作符,箭头操作符将lambda表达式拆分成两部分:
左侧:lambda表达式的参数列表
右侧:lambda表达式中所需执行的功能,即lambda体
-
语法格式一:无参数 无返回值
() -> System.out.println("Hello Lambda!");
-
语法格式二:有一个参数,无返回值
(x) -> System.out.println(x)
-
语法格式三:有一个参数,小括号可以不写
x -> System.out.println(x)
-
语法格式四:有两个以上的参数,有返回值,lambda表达式有多条语句(必须使用大括号)
Comparator<Integer> com = (x,y) -> { System.out.println("函数式接口"); return Integer.compare(x,y); };
-
语法格式五:若lambda体中只有一条语句,return 和 大括号都可以省略不写
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
-
语法格式六:lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型——“类型推断”
Integer.compare(Integer x,Integer y) -> Integer.compare(x,y);
总结:1)左侧:只有一个参数时,括号可省 —— 无参数/有两个及以上的参数,括号不可省
2)右侧:只有一条语句时,括号可省 —— 有返回值的,return也可省
3)参数列表的数据类型可省 —— 可根据上下文推断
函数式接口
Lambda 表达式需要“函数式接口”的支持 。虽然Lambda表达式可以很便捷的实现接口,但并不是所有的接口都可以使用Lambda表达式来实现。可以⽤Lambda表达式来简洁实现的接口是有要求的。因为Lambda表达式本质上来讲,就是⼀个匿名函数,用这个匿名函数来实现接口中的方法。所以,如果接口中有多个必须要实现的抽象方法时,Lambda表达式将无法使用。
接口中必须只有一个方法要实现(只有一个要实现,但不一定是只有一个方法)
函数式接口就是包含单一抽象方法的接口。 (单一职能制:一个接口只做一件事情)
特点:
(1)有且仅有一个抽象方法;
(2)但可以有default、static方法;
(3)可继承Object类中的public方法;(因为任何一个类都继承Object类,包含了来自java.lang.Object里对这些抽象方法的实现,也不属于抽象方法;)
(4)函数式接口里允许子接口继承多个父接口,但每个父接口中都只能存在一个抽象方法,且必须是相同的抽象方法。
可以使用注释@FunctionalInterface修饰,可以检查是否是函数式接口。
java8内置四大核心函数式接口
java.util.function包下的接口都是“无意义”的,这个“无意义”并不是说它们没有存在的意义,而是说它们都必须放到具体的语境中去才会真正的意义。或者说,它们是“非典型”的Java API就体现在,它们自己是没有语境(Context)的,它们实际上可以说就是为Lambda表达式而存在的——你不需要了解它叫什么(匿名的),它们是作为参数在对象的方法的中传递的。
其实,所有java.util.function包下的接口的方法名,我们都不必关心,因为当我们用它们的时候,压根不会去显式地调用它们,方法名也是“无意义”的,它们只是方便大家去理解他的用途。但是,方法的参数和返回值是需要我们关心和注意的。
function
包下总共有43个接口,可分为这四类核心函数式接口:Function、Supplier、Consumer、Predicate。
1.消费型接口
Consumer:消费型接口 单一抽象方法为:void accept(T t);
方法 | 描述 |
---|---|
void accept(T t) | 对给定的参数执行此操作 |
default Consumer andThen(Consumer<? super T> after) | 返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作(用于函数复合) |
-
作用:消费某个对象**(传入一个泛型参数,不返回任何值)**
-
举例:forEach: Iterable接口的foeEach方法 需要传入Consumer,大部分集合类都实现了该接口,用于返回Iterator对象进行迭代。
java.util.function包还定义了其他三种Consumer的基本变体(用于处理基本数据类型),以及一种双参数形式,BiConsumer接口的accept方法传入两个泛型参数,这两个泛型参数应为不同的类型。
接口 | 单一抽象方法 |
---|---|
IntConsumer | void accept(int x) |
DoubleConsumer | void accept(double x) |
LongConsumer | void accept(long x) |
BiConsumer | void accept(T t,U u) |
2.供给型接口
Supplier:供给型接口 T get();
方法 | 描述 |
---|---|
T get() | 获得结果 |
- 作用:创建一个对象(工厂类)(不传入参数,返回一个值)
- 举例:Optional.orElseGet(Supplier<? extends T>):当this对象为null,就通过传入supplier创建一个T返回。(Optional类是一种容器对象,要么包装值,要么为空)
其他Supplier接口:(返回基本数据类型的数据)
接口 | 单一抽象方法 |
---|---|
IntSupplier | int getAsInt() |
DoubleSupplier | double getAsDouble(double x) |
LongSupplier | long getAsLong(long x) |
BooleanSupplier | boolean getAsBoolean(T t,U u) |
3.函数式接口
Function<T,R>:函数型接口 R apply(T t);
方法 | 描述 |
---|---|
default Function<T,V> andThen(Function<? super R,? extends V> after) | 返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果(先执行当前对象的apply方法,再执行after对象的方法)。 |
R apply(T t) | 将此函数应用于给定的参数。 |
default Function<V,R> compose(Function<? super V,? extends T> before) | 返回一个组合函数,首先将 before函数应用于其输入,然后将此函数应用于结果(先执行before对象的apply,再执行当前对象的apply,将两个执行逻辑串起来)。 |
static Function<T,T> identity() | 返回一个总是返回其输入参数的函数。 |
- 作用:实现一个“一元函数”,即传入一个值经过函数的计算返回另一个值。(传入一个参数,返回一个值)
其他Function接口(BiFunction接口定义了两个泛型输入类型和一个泛型输出类型)
接口 | 单一抽象方法 |
---|---|
IntFunction | R apply(int value) |
DoubleFunction | R apply(double value) |
LongFunction | R apply(long value) |
ToIntFunction | int applyAsInt(T value) |
ToDoubleFunction | double applyAsDouble(T value) |
ToLongFunction | Long applyAsLong(T value) |
DoubleToIntFunction | int applyAsInt(double value) |
DoubleToLongFunction | Long applyAsLong(double value) |
IntToDoubleFunction | double applyAsDouble(int value) |
IntToLongFunction | long applyAsLong(int value) |
LongToDoubleFunction | double applyAsDouble(long value) |
LongToIntFunction | int applyAsInt(long value) |
BiFunction | R apply(T t, U u) |
4.断言型接口
Predicate:断言型接口 单一抽象方法: boolean test(T t);
- 作用:判断对象是否符合某个条件**(传入一个参数,返回一个布尔值)**
- 举例:主要用于流的筛选。给定一个包含若干项的流,Stream接口的filter方法传入一个Predicate并返回一个新的流,它仅包含满足给定谓词的项。
方法 | 描述 |
---|---|
default Predicate and(Predicate<? super T> other) | 接收一个Predicate类型,也就是将传入的条件和当前条件以并且的关系过滤数据。 |
static Predicate isEqual(Object targetRef) | 返回根据 Objects.equals(Object, Object)测试两个参数是否相等的谓词。 |
default Predicate negate() | 返回表示此谓词的逻辑否定的谓词。 |
default Predicate or(Predicate<? super T> other) | 接收一个Predicate类型,将传入的条件和当前的条件以或者的关系过滤数据。 |
boolean test(T t) | 在给定的参数上评估这个谓词 |
方法引用与构造器引用
双冒号::
为引用运算符,而它所在的表达式被称为方法引用。
若lambda体中的内容有方法已经实现了,我们可以使用“方法引用”,方法引用是Lambda表达式的另外一种表现形式,属于lambda表达式的一种简化语法。(lambda表达式和方法引用在任何情况下都不能脱离上下文存在,上下文指定了将表达式赋给哪个函数式接口。)
如果说lambda表达式本质上是将方法作为对象进行处理,那么方法引用就是将现有方法作为lambda表达式进行处理。
主要有三种表现形式:
1)引用特定对象的实例方法:对象::实例方法名(object::instanceMethod)
(x)->System.out.println(x);
简化为:System.out::println
2)引用静态方法:类::静态方法名(Class::staticMethod)
()->Math.random();
简化为:Math::random
3)调用特定类型的任意对象的实例方法: 类::实例方法名(Class::instanceMethod )
x->x.length();
简化为:String::length
注意:如果通过类名引用一个传入多个参数的方法,则上下文提供的第一个元素将作为方法的目标,其他元素作为方法的参数,可以使用ClassName::instanceMethod 。
(o1,o2)->o1.compareTo(o2);
简化为:String::compareTo
注意:需要实现的接口中的抽象方法的 参数列表与返回值类型 要与 当前调用的方法的参数列表、返回值类型保持一致
2.构造函数引用
格式:ClassName::new
Function<String,Student> function = (x)->new Student(x);
简化为:Function<String,Student> function = Student::new; (引用Studnet类中的构造函数)
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
由上下文决定执行哪个构造函数。
3.数组引用
格式:Type[]::new
toArray((x)->new Student[x])
简化为:toArray(Student[]::new)
接口中的默认方法
jdk8新特性——默认方法(带默认实现的方法)
Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰。
默认方法的主要优势是提供一种拓展接口的方法,而不破坏现有代码。加入我们有一个已经投入使用接口需要拓展一个新的方法,在JDK8以前,如果为一个使用的接口增加一个新方法,则我们必须在所有实现类中添加该方法的实现,否则编译会出现异常。如果实现类数量少并且我们有权限修改,可能会工作量相对较少。如果实现类比较多或者我们没有权限修改实现类源代码,这样可能就比较麻烦。
而默认方法则解决了这个问题,它提供了一个实现,当没有显式提供其他实现时就采用这个实现。这样新添加的方法将不会破坏现有代码。
默认方法的另一个优势是该方法是可选的,子类可以根据不同的需求Override默认实现.
- 当继承的父类和实现的接口中有相同签名的方法时,优先使用父类的方法。
- 当接口的父接口中也有同样的默认方法时,就近原则调用子接口的方法。
- 当实现的多个接口中有相同签名的方法时,必须在实现类中通过重写方法解决冲突问题,否则无法通过编译,在重写的方法中可以通过 接口名.super.方法名(); 的方式显示调用需要的方法。
如果一个类既extend了父类,又实现了接口。如果出现同名方法,那就遵循类优先原则。
default int add(){}
接口中的静态方法
在Java8中,接口中允许添加 静态方法,使用方式:“接口名.方法名”。
注意事项:
(1)静态方法必须有实现;
(2)无法重写静态方法;
(3)通过接口名调用静态方法;
(4)无须实现接口以使用静态方法。
实现接口的类或者子接口不会继承接口中的静态方法。
(类接口中的static方法不能被继承,也不能被实现类调用,只能被自身调用)