1. Lambda表达式
1.1 示例
Thread thread = new Thread(() -> {
System.out.println("HelloWorld");
});
thread.start();
复制代码
1.2 定义
“Lambda表达式
理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它 有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表”(参考Java8实战)
Lambda表达式包含3部分:
- 参数列表:函数参数列表
- 箭头:分割参数列表和主体
- Lambda主体:函数主体
1.2.1 参数列表
参数列表使用括号()来表示,其中参数可以声明参数类型,也可以不声明参数类型(编译器会根据上下文推断)。
1.2.2 函数体(Lambda主体)
Lambda表达式的函数体,既可以是代码块
,也可以是表达式
。 代码块:和普通方法的方法主体一致; 表达式:表达式会被执行并返回结果,其本质上是一种return语句的简写(省略了return和{})
1.2.3 特殊表达
Lambda不仅可以省略参数类型、用表达式表示方法体,其实还可以直接使用方法引用
来替代Lambda表达式,例如如下三种方式是等价的:
Consumer<String> consumer1 = (String str) -> {
System.out.println(str);
};
Consumer<String> consumer2 = (str) -> {
System.out.println(str);
};
Consumer<String> consumer3 = System.out::println;
复制代码
- 暂时还是没太理解第三种的本质。。
1.3 特点
Lambda表达式拥有如下特点:
- 匿名:和普通方法相比,没有明确的方法名。
- 函数:Lambda函数和方法区别是不属于某个特定的类。
- 传递:Lambda表达式可以作为参数传递给方法,或者存储在变量中。
- 简洁:无需匿名内部类那样写很多复杂的函数。
1.4 Lambda表达式和匿名内部类
Lambda表达式
用来为某个抽象方法提供实现,个人可以粗略地讲其理解为一种匿名内部类
更加通俗易懂,但两者并不完全相同。
相同点
两者有如下相同点:
- 都没有表现出来的类名(匿名内部类会生成.class文件,Lambda表达式好像会在第一次被使用时动态生成一个类?);
- 都可以访问外部的变量和方法。
- 只能访问外部final变量,不能修改外部变量。
final String word = "Hello";
Runnable runnable = () -> {
// 不可以在Lambda表达式中修改外部变量,否则会提示Variable used in lambda expression should be final or effectively final
// word = "HelloWorld";
function1();
System.out.println(word);
};
runnable.run();
Runnable runnable1 = new Runnable() {
@Override
public void run() {
function1();
// 不可以在内部类中修改外部变量,否则会提示Variable is accessed within inner class. Needs to be final or effectively final
// word = "HelloWorld";
System.out.println(word);
}
};
runnable1.run();
复制代码
不同点
- 实现父类(或接口)不同。Lambda表达式只可以实现函数式接口,而匿名内部类可以实现拥有多个抽象方法的接口、抽象类和普通类。
- 匿名内部类可以调用接口中其它方法,但Lambda表达式不可以。(本质上应该是下一个不同点的第2小点)
- 两者作用域不同。(1)Lamda表达式中
this
指的是外部类的对象,而内部类中this
指的是内部类对象。见作用域示例1。 (2)Lamda表达式中调用的方法也都是外部类的,但内部类中调用的方法是优先内部的。
作用域示例1
public class HelloLambda {
Runnable r1 = () -> {
System.out.println("Lambda, 1: " + this);
System.out.println("Lambda, 2: " + toString());
};
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Inner class, 1: " + this);
System.out.println("Inner class, 2: " + toString());
}
};
@Override
public String toString() {
return "Hello, lambda!";
}
public static void main(String[] args) {
new HelloLambda().r1.run();
new HelloLambda().r2.run();
}
}
复制代码
输出:
作用域示例2
Lambda表达式r1由于外部类中没有run()方法,所以报错;但r2由于内部类中有run()方法,所有可以通过编译,实际上就是循环调用了。
2. 函数式接口
函数式接口
就是有且仅有一个抽象方法的接口。
Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。(其实可以将Lambda表达式看做匿名内部类,该匿名内部类实现了函数式接口)。
特殊说明:
- 函数式接口中除了特定的抽象方法外,还可以有非抽象方法;
- 如果在接口中有重写了Obejct的public方法的抽象方法,那么该方法也不影响函数式接口抽象方法的计数。
接下来介绍几种常见函数式接口。
2.1 Runnable和Callable接口
Runnable
Runnable接口源码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
复制代码
观察Runnable源码,可以发现该接口中只有一个抽象方法run(),故可以使用Lambda表达式来作为该函数式接口的实例,并提供抽象方法的实现。
Runnable runnable = () -> {
System.out.println("Hello world!");
};
复制代码
Callable
Callable接口源码:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
复制代码
Callable接口的Lambda表达式示例:
Callable<String> callable = () -> {
return "HelloWorld";
};
复制代码
Callable接口有返回值,Lambda主体可以直接使用表达式替代return语句,如下:
Callable<String> callable = () -> "HelloWorld";
复制代码
2.2 Comparator接口
源码(部分)
@FunctionalInterface
public interface Comparator<T> {
// 抽象方法
int compare(T o1, T o2);
// 重写Obejct类public方法的抽象方法,不进行计数
boolean equals(Object obj);
// 非抽象方法
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
}
复制代码
观察源码,可以发现Comparator实现类:(1)需要声明泛型;(2)需要实现compare方法,且参数列表有两个,参数类型与泛型类上声明一致。
Lambda表达式示例
简单示例
Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
复制代码
列表倒序排序示例:
List<Integer> numbers = Lists.newArrayList(1, 2, 3);
numbers.sort((x1, x2) -> x2.compareTo(x1));
numbers.forEach((x) -> {
System.out.println(x);
});
复制代码
输出:
按照对象属性排序
按user年龄倒序排序:
List<User> users = Lists.newArrayList(new User(18), new User(22), new User(24));
users.sort((u1, u2) -> u2.age.compareTo(u1.age));
users.forEach((u) -> {
System.out.println(u.age);
});
复制代码
输出:
2.3 Consumer
Consumer接口是一种有参无返回值的消费型接口。
源码
@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); };
}
}
复制代码
Consume接口可以理解为一种通用消费型接口(可以接收参数,但不能返回),通过lambda表达式定义好要执行的动作,通过调用accept方法执行。
简单示例
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello world1!");
复制代码
拿其与Runable接口比较更好理解,Runnable接口也可以直接执行run方法(就没有线程特性了),但是无法接受传入的参数。比较
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello world1!");
Runnable runnable = () -> {
System.out.println("Hello world2!");
};
runnable.run();
复制代码
回调示例
个人理解,Consumer接口最常见的用途是作为回调。
如下所示,function1接收参数list、以及回调函数consumer,function1本身不关心如何处理、直接执行consumer.accept(),具体的处理由传入的consumer变量(lambda表达式、回调函数)决定,可以通过修改传入的consumer变量来修改函数的实现。
public static void function1(List<Integer> list, Consumer<List<Integer>> consumer) {
consumer.accept(list);
}
public static void main(String[] args) {
// 存储lambda表达式
Consumer<List<Integer>> consumer1 = list -> {
list.sort((x1, x2) -> x2.compareTo(x1));
};
Consumer<List<Integer>> consumer2 = list -> {
list.sort((x1, x2) -> x1.compareTo(x2));
};
List<Integer> numbers = Lists.newArrayList(1, 2, 3);
// 传递lambda表达式
function1(numbers, consumer1);
numbers.forEach((x) -> {
System.out.println(x);
});
function1(numbers, consumer2);
numbers.forEach((x) -> {
System.out.println(x);
});
}
复制代码
BiConsumer
BiConsumer接口则是Consumer接口升级版,可以接受两个参数:
BiConsumer<String, Integer> biConsumer = (str, num) -> {
System.out.println(str + num);
};
biConsumer.accept("Hello, ", 123);
复制代码
还有其他消费型接口,比如IntConsumer。
2.4 Supplier接口
Supplier
接口是一种无参有返回值的供给型接口。可以理解为一个容器,可以生成、存储对象,供其它方法调用其中get()
方法获取对象。
源码
@FunctionalInterface
public interface Supplier<T> {
T get();
}
复制代码
简单示例
Supplier<User> supplier = () -> {
return new User(123);
};
User user = supplier.get();
System.out.println(user.age);
复制代码
回调示例
如下所示,getId方法用于返回一个int型整数,但不同的地方可能使用不同的生成策略,所以使用Supplier接口作为参数,将id生成策略回调出去。
public static Integer getId(Supplier<Integer> supplier) {
return supplier.get();
}
public static void main(String[] args) {
Supplier<Integer> supplier1 = () -> (new Random()).nextInt(10);
Supplier<Integer> supplier2 = () -> (new Random()).nextInt(100);
System.out.println(getId(supplier1));
System.out.println(getId(supplier2));
}
复制代码
但是个人理解上Supplier接口意义很低,不能接受参数、直接return,即便作为回调也难以有太多实际使用场景。
2.5 Predicate接口
Predicate接口一种有参数返回值类型为布尔型的断言型接口。
源码
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
复制代码
观察源码可以发现,需要实现test()方法,并且还支持and/or联合判断条件。
使用示例
Predicate<Integer> predicate = (x) -> x < 100;
Predicate<Integer> predicate1 = (x) -> x > 0;
System.out.println(predicate.test(1));
System.out.println(predicate.and(predicate1).test(-1));
复制代码
输出结果:
2.6 Function接口
源码
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
复制代码
观察源码得到需要实现其中apply()
方法,而lambda表达式的参数<T, R>,其中T代表函数的参数类型,R代表返回类型(第一个是参数,第二个是返回值)。
Function
接口使用的场景较多Consumer/Predicate/Supplier接口能实现的功能用Function接口也都可以实现。
简单示例
Function<Integer, String> function = (x) -> {
if (x < 0) {
return "小于0";
} else {
return "大于等于0";
}
};
System.out.println(function.apply(3));
复制代码
BiFunction
Function
接口同样也有用来接收两个参数的BiFunction
接口。
BiFunction<Integer, Integer, String> function = (x1, x2) -> {
return new StringBuilder("Receive x1=").append(x1).append(", x2=").append(x2).toString();
};
复制代码
3. 流
Java8新增了一个接口java.util.Stream,可以将Collection、List、Set、Map等使用流进行处理,编码更加方便快捷。Stream接口依赖于Lambda表达式。
详见:。。。。。。。。。