1. Lambda
java8开始引入了Lambda表达式.lambda表达式是Stream API编程的基础。本篇文章不打算研究java中Lambda的底层实现。主要关注以下两点。
- 主要关注什么是Lambda
- 在Java中如何使用Lambda
- 如何使用函数式接口
1.1 什么是Lambda表达式
广义上
广义上讲Lambda表达式是一种匿名函数.即无序标识符的函数, 只拥有形参,和方法体.
Java中的Lambda
Java中的Lambda表达式是可以认为是一种匿名内部类的语法糖.
1.2 如何使用Lambda表达式
前言
Lambda表达式最主要的一个应用场景就是用来简化匿名内部类的写法, 使得代码更加的简短, 简洁.但是如果不了解Lambda表达式, 可能对于其他开发人员来说你写的代码可能就不是那么好维护.
基本语法
基本语法:
(parameters) ->{statements;}
分类
我们按照匿名内部类来对Lambda表达式进行分类。可以分为以下几类。
一. 无参,无返回值
Runnable r1 = ()->{System.out.println("Hello Lambda");};
new Thread(r1).start()
可以写得更简洁new Thread( ()->{System.out.println("Hello Lambda");}).start()
二. 一个参数, 无返回值
Consumer<String>con = (String str)->{System.out.println(str);};
这里的Consumer是函数式接口, 表示的是一个消费型函数式接口。如下是Consumer的定义. 这边的lambda表达式, 相当于new了一个匿名内部类对象,并实现了其accept方法。lambda表达式就是不用写要实现的方法名, 应为JVM会给我们自动推断, 该lambda表达式是要绑定的对应方法.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
三. 需要两个参数或两个以上, 且有返回值
例如用lambda替换创建匿名内部类Comparator
Comparator<Integer> comparator = new Comparator<Integer>() { // 匿名内部类方式
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
// lambda方式
Comparator<Integer> comparator2 = (Integer o1, Integer o2)->{ o1.compareTo(o2)};
四. 简略写法
上述写法还就可以简略, 遵守以下规则。
简化规则总结:
-
参数
的小括号
可以省略, 当且仅当只有一个参数
. -
参数
的数据类型
可以省略, 当且仅当lambda表达式
. -
方法体
的return
和大括号
可以省略, 当且仅当只有一条语句
.
以Comparator接口为例子
Comparator<Integer> comparator2 = (o1, o2)-> o1.compareTo(o2);
类型推断与lambda参数
如上述总结的规则,lambda表达式参数列表的类型可以省略。是因为编译器在后面给我们做了很多事, 编译器可以根据上下文推断出lambda表达式的参数类型
1.3 函数式接口
函数式接口(Funcation Interface)是一种特殊的接口。这类接口只包含一个抽象方法的接口, 因此也被称为SAM(Single Abstract Method).
JDK 8中又增加了java.util.function
包, 提供了常用的函数式接口。
为什么会单单从接口中定义出此类接口呢?
定义
只包含一个抽象方法的接口, 称为函数式接口(除了隐含的Object对象的公共方法)
- 函数式接口中可以额外定义多个抽象方法,但这些抽象方法签名必须和
Object
的public方法
一样(必须满足即使Object的方法, 又是public的接口, 像Object的clone()接口就不是public)
@FunctionalInterface
public interface ObjectMethodFunctionalInterface {
void count(int i);
String toString(); //same to Object.toString
int hashCode(); //same to Object.hashCode
boolean equals(Object obj); //same to Object.equals
}
- 函数式接口的抽象方法可以声明
受检查异常
(checked exception)。 在调用目标对象的这个方法时必须catch这个异常。
public class FunctionalInterfaceWithException {
public static void main(String[] args) {
InterfaceWithException target = i -> {};
try {
target.apply(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@FunctionalInterface
interface InterfaceWithException {
void apply(int i) throws Exception;
}
- 如果在Lambda表达式中抛出异常, 而目标接口中的抽象函数没有声明这个可检查.则不能在lambda表达式中抛出异常
public class FunctionalInterfaceWithException {
public static void main(String[] args) {
InterfaceWithException target = i -> {throw new Exception();};
}
}
@FunctionalInterface
interface InterfaceWithException {
void apply(int i);
}
静态方法
函数式接口是在JDK8后才引入的,而JDK8之后的接口可以添加公共静态公共方法。而函数式接口又是一种特殊的接口, 其既然是接口,当然也可以添加公共静态方法。
如下代码, 依然是公共静态方法
@FunctionalInterface
interface FunctionalInterfaceWithStaticMethod {
static int sum(int[] array) {
return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt();
}
void apply();
}
public class StaticMethodFunctionalInterface {
public static void main(String[] args) {
int sum = FunctionalInterfaceWithStaticMethod.sum(new int[]{1,2,3,4,5});
FunctionalInterfaceWithStaticMethod f = () -> {};
}
}
默认方法
语法
default 数据类型 方法名(参数列表) {
}
默认方法解耦
默认方法也是JDK8引入的新特性。它主要为了解决JDK1.8之前,接口和类之前的耦合性问题
, 在开发中当我们想给接口新的方法,其所有实现类也必须实现该接口,这样带来的代码改动很大。为了向后兼容, JDK8提供了default关键字,用该关键字修饰的方法,在接口中就提供了实现(所以才叫方法), 就是为了解决问题。
因为默认方法不是抽象方法,所以不影响我们判断一个接口是否是函数式接口
如下代码依然是一个函数式接口
@FunctionalInterface
interface InterfaceWithDefaultMethod {
void apply(Object obj);
default void say(String name) {
System.out.println("hello " + name);
}
}
class FunctionalInterfaceWithDefaultMethod {
public static void main(String[] args) {
InterfaceWithDefaultMethod i = (o) -> {};
i.apply(null);
i.say("default method");
}
}
@FunctionalInterface
该注解主要是为了来标注一个接口是函数式接口, 而且标注后可以让编译器来检查该接口是不是符合函数式接口的定义。作为开发者, 我们应该为每一个函数式接口添加上该注解.
- 规范, 提高代码可读性。
JDK8提供的新的函数式接口
java.util.function中定义了几组类型的函数式接口以及针对基本数据类型的子接口.
四大核心函数式接口
如上图根据这四种接口的定义我们可以写出其对应的lambda表达式
- 对于Consumer<T>
()->{ statement };
- 对于Supplier<T>
(t)->{ statement};
- 对于Fuction<T, R>
(t)->{ R = statement; return R; };
- 对于Predicate<T>
(t)->{ boolean r = statement;return r;}
其他函数式接口
这里简单介绍下BiFuncation
和UnaryOperator
接口, 其他其实大同小异.
-
BiFuncation<T, U, R>
接口规定了对应的lambda表达式是给定两个参数类型T和U, 经过计算返回R类型.
@Test
public void testBiFuncation() {
BiFunction<String, String, String> concate = (o1, o2)-> o1 + "---" + o2;
System.out.println(concate.apply("hello", "Mr su")); // 输出hello---Mr su
}
-
BinaryOperator<T>
接口是BiFuncation的特殊例子, 其T, U,R都是同一种类型.
@Test
public void testBinaryOperator() {
BinaryOperator<Integer> op = (o1, o2)-> o1 > o2? o1: o2;
Integer max = op.apply(2, 3);
System.out.println(max); // 输出3
}
-
UnaryOperator<T>
接口规定了参数只有一个且是T类型, 经过计算返回类型T
@Test
public void testUnaryOperator() {
UnaryOperator<String> upper = (s)-> s.toUpperCase();
System.out.println(upper.apply("i love china"));
}
方法引用与构造器引用
方法引用
函数式接口可以直接引用已经实现好的方法, 只要该方法符合其规定的规范模式。有如下三种引用方式
- 对象::实例方法名
- 类名::静态方法名
- 类名::实例方法名
// 如System.out.println()就符合Consumer定义的模式.是消费型接口
// 给定一个数据就消费掉, 不返回.所以我们可以这样写
Consumer<String> con = (s)->System.out.println(s);
Consumer<String> con2 = System.out::println;
构造器引用
语法: 引用类型名::new
Funcation<Integer, MyClass> fun = MyClass::new;
数组引用
语法: 数组类型::new
Funcation<Integer, Integer[]> fun = Integer[]::new;