# Lambda
译者注
(虽然看着很先进,其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。本人建议不要乱用,因为这就和某些很高级的黑客写的代码一样,简洁,难懂,难以调试,维护人员想骂娘.)
可以把Lambda表达式理解为简洁的表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表。
lambda表达式允许你通过表达式来代替功能接口。
lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。
Stream&Function
流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。 在本文中,我们将从简单到复杂的示例中见认识lambda表达式和stream的强悍。
# Lambda表达式的语法
# 基本语法
(parameters) -> expression
(parameters) ->{ statements; }
2
java中,引入了一个新的操作符“->”,该操作符在很多资料中,称为箭头操作符,或者lambda操作符;箭头操作符将lambda分成了两个部分:
- 左侧:lambda表达式的参数列表
- 右侧:lambda表达式中所需要执行的功能,即lambda函数体
- lambda表达式语法格式;
- (1).无参数,无返回值的用法 :
() -> System.out.println("hello lambda");
- (2).有一个参数,无返回值的用法:
(x) -> System.out.println(x); // 或者
x -> System.out.println(x); // 一个参数,可以省略参数的小括号
2
下面是Java lambda表达式的简单例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 - x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
2
3
4
5
6
7
8
9
10
# Lambda的域以及访问限制
# 1、访问局部变量
在Lambda表达式外部的局部变量会被JVM隐式的编译成final类型,因此只能访问外而不能修改。
# 2、访问静态变量和成员变量
在Lambda表达式内部,对静态变量和成员变量可读可写。
# 基本的Lambda例子
@Test
public void testLambda(){
String[] atp = {"Rafael Nadal","Novak Djokovic","Stanislas Wawrinka",
"David Ferrer","Roger Federer","Andy Murray",
"Tomas Berdych","Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
players.forEach((palyer) -> System.out.println(palyer+"。"));
}
@Test
public void testJava8(){
String[] atp = {"Rafael Nadal","Novak Djokovic","Stanislas Wawrinka",
"David Ferrer","Roger Federer","Andy Murray",
"Tomas Berdych","Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
players.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IDE警告
lambda can be replaced with method……
这种警告可以通过修改代码如下:
@Test
public void testConsumer(){
Consumer<String> con = System.out::println;
con.accept("你是哪个");
}
2
3
4
5
双冒号::
双冒号(::)运算符在Java 8中被用作方法引用(method reference),方法引用是与lambda表达式相关的一个重要特性。它提供了一种不执行方法的方法。为此,方法引用需要由兼容的函数接口组成的目标类型上下文。
# Runnable接口
下面是使用lambdas 来实现 Runnable接口 的示例:
@Test
public void testThread(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello HT");
}
}).start();
new Thread(()-> System.out.println("Hello HT")).start();
Runnable run1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello CLM");
}
};
Runnable run2 = ()-> System.out.println("Hello CLM");
run1.run();
run2.run();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (3).有两个参数,有返回值的:(x, y) -> x + y
BinaryOperator<Integer> addOp = (a,b)->a+b;
System.out.println(addOp.apply(3,5));
2
- (4).剩下的多个参数
就不在继续介绍了,按照上面的推测,就可以了, 上面的例子,函数体中,都是一行语句,最后一个语法,介绍下,lambda表达式中,多行语句,分别在无返回值和有返回值的抽象类中的用法
函数式接口支持
lamabd表达式中,需要有函数式接口的支持;
函数式接口定义:接口中只有一个抽象方法的接口,称为函数式接口;
可以使用@FunctionalInterface注解修饰,对该接口做检查;如果接口里,有多个抽象类,使用该注解,会有语法错误
在java8中,lambda表达式所用的接口,必须是函数式接口;
在java8中的函数式接口,有很多,比说,
无参无返回值的(Runnable接口),有一个参数,无返回值的(Consumer),有多个参数有返回值的(BiFunction)……等等;
根据不用的作用,java8中,内置了4个核心接口
# JAVA8-Consumer接口
Consumer
Consumer的语义是消费的意思,了解一些消息队列的同学,肯定对这个单词,有一定的理解,我们先看接口的定义
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
/**传入一个Consumer类型的参数,
*他的泛型类型,
*跟本接口是一致的T,先做本接口的accept操作,
*然后在做传入的Consumer类型的参数的accept操作
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
2
3
4
5
6
7
8
9
10
11
12
13
这个接口,接收一个泛型的参数T,然后调用accept,对这个参数做一系列的操作,没有返回值;看到这里,是不是很懵,下面用一个简单的小例子
# JAVA8-Supplier接口
@FunctionalInterface
public interface Supplier<T> {
/**
- Gets a result.
*
- @return a result
*/
T get();
}
2
3
4
5
6
7
8
9
10
Supplier
看语义,可以看到,这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T
Supplier<AlarmBo> supS = AlarmBo::new;
AlarmBo alarmBo = supS.get();
alarmBo.setAlarmDevName("sss");
System.out.println(alarmBo.getAlarmDevName());
2
3
4
可以看到,这个接口,只是为我们提供了一个创建好的对象,这也符号接口的语义的定义,提供者,提供一个对象, 直接理解成一个创建对象的工厂,就可以了;
# JAVA8-Function接口以及同类型的特化的接口
Funtion
Funtion接口,定义了一个apply的抽象类,接收一个泛型T对象,并且返回泛型R对象,看到这里,是不是对这个接口的描述,还是一头雾水呢,
Function<Integer,String> fis = x->x*2+"aaa";
System.out.println(fis.apply(6));
2
传入的泛型T是Integer的,返回的泛型R是String的,里面的做的操作是传入的参数x 乘2,然后连接一个dd的字符串,然后的就是泛型R的字符串;
源码:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
/**
- 先做传入的Function类型的参数的apply操作,再做当前这个接口的apply操作
- V表示这个Function类型的参数的传入参数类型,也就是本接口的T类型
- @param before
- @return
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
- 先做本接口的apply操作,再做传入的Function类型的参数的apply操作
- @param after
- @return
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
- 静态方法表示,这个传入的泛型参数T的本身
- @return
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Function扩展
下面我们看下Funtion这个接口的“扩展”的原始类型特化的一些函数接口
IntFunction<R>,IntToDoubleFunction,
IntToLongFunction,LongFunction<R>,
LongToDoubleFunction,LongToIntFunction,
DoubleFunction<R>,ToIntFunction<T>,
ToDoubleFunction<T>,ToLongFunction<T>
# JAVA8-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);
}
}
Predicate<String> ps = s->s.startsWith("N");
System.out.println(ps.test("Nasdfsd"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 其他lambda接口
| 接口 | 说明 |
|---|---|
BiConsumer<T,U> | 代表了一个接受两个输入参数的操作,并且不返回任何结果 |
BiFunction<T,U,R> | 代表了一个接受两个输入参数的方法,并且返回一个结果 |
BinaryOperator<T> | 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
BiPredicate<T,U> | 代表了一个两个参数的boolean值方法 |
BooleanSupplier | 代表了boolean值结果的提供方 |
Consumer<T> | 代表了接受一个输入参数并且无返回的操作 |
DoubleBinaryOperator | 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
DoubleConsumer | 代表一个接受double值参数的操作,并且不返回结果。 |
DoubleFunction<R> | 代表接受一个double值参数的方法,并且返回结果 |
DoublePredicate | 代表一个拥有double值参数的boolean值方法 |
DoubleSupplier | 代表一个double值结构的提供方 |
DoubleToIntFunction | 接受一个double类型输入,返回一个int类型结果。 |
DoubleToLongFunction | 接受一个double类型输入,返回一个long类型结果 |
DoubleUnaryOperator | 接受一个参数同为类型double,返回值类型也为double 。 |
Function<T,R> | 接受一个输入参数,返回一个结果。 |
IntBinaryOperator | 接受两个参数同为类型int,返回值类型也为int 。 |
IntConsumer | 接受一个int类型的输入参数,无返回值 。 |
IntFunction<R> | 接受一个int类型输入参数,返回一个结果 。 |
IntPredicate | 接受一个int输入参数,返回一个布尔值的结果。 |
IntSupplier | 无参数,返回一个int类型结果。 |
IntToDoubleFunction | 接受一个int类型输入,返回一个double类型结果 。 |
IntToLongFunction | 接受一个int类型输入,返回一个long类型结果。 |
IntUnaryOperator | 接受一个参数同为类型int,返回值类型也为int 。 |
LongBinaryOperator | 接受两个参数同为类型long,返回值类型也为long。 |
LongConsumer | 接受一个long类型的输入参数,无返回值。 |
LongFunction<R> | 接受一个long类型输入参数,返回一个结果。 |
LongPredicate | R接受一个long输入参数,返回一个布尔值类型结果。 |
LongSupplier | 无参数,返回一个结果long类型的值。 |
LongToDoubleFunction | 接受一个long类型输入,返回一个double类型结果。 |
LongToIntFunction | 接受一个long类型输入,返回一个int类型结果。 |
LongUnaryOperator | 接受一个参数同为类型long,返回值类型也为long。 |
ObjDoubleConsumer<T> | 接受一个object类型和一个double类型的输入参数,无返回值。 |
ObjIntConsumer<T> | 接受一个object类型和一个int类型的输入参数,无返回值。 |
ObjLongConsumer<T> | 接受一个object类型和一个long类型的输入参数,无返回值。 |
Predicate<T> | 接受一个输入参数,返回一个布尔值结果。 |
Supplier<T> | 无参数,返回一个结果。 |
ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个double类型结果 |
ToDoubleFunction<T> | 接受一个输入参数,返回一个double类型结果 |
ToIntBiFunction<T,U> | 接受两个输入参数,返回一个int类型结果。 |
ToIntFunction<T> | 接受一个输入参数,返回一个int类型结果。 |
ToLongBiFunction<T,U> | 接受两个输入参数,返回一个long类型结果。 |
ToLongFunction<T> | 接受一个输入参数,返回一个long类型结果。 |
UnaryOperator<T> | 接受一个参数为类型T,返回值类型也为T。 |
- JDK 1.8之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
// 接受两个泛型参数
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
/**
- Returns a unary operator that always returns its input argument.
*
- @param <T> the type of the input and output of the operator
- @return a unary operator that always returns its input argument
*/
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# java8-lambda表达式,方法的引用以及构造器的引用
在lambda表达式中,使用方法的引用,来传递方法方法的行为参数化; 方法的引用让你可以重复使用现有的方法定义,并像lambda一样传递他们,在一些情况下,比起使用lambda表达式,它们似乎更易读,感觉也更自然;
方法的引用的语法,主要有三类
- 指向静态方法的方法引用,例如Integer的parseInt方法 ,可以写成Integer::parseInt
- 类::静态方法名
- 指向任意类型实例方法的方法引用,例如String的length方法,写成String::length;
- 类::实例方法名
- 指向现有对象的实例方法的方法引用
- 对象::实例方法名
构造器的引用:对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用ClassName::new;
最后,特别要注意:
- ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
- ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName