Java8-Lambda
Java 8 的最大变化是引入了 Lambda 表达式——一种紧凑的、传递行为的方式
引出Lambda表达式
例子:
使用匿名内部类将行为和按钮单击进行关联
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("button clicked");
}
});
我们创建了一个新对象,它实现了 ActionListener 接口。这个接口只有一个方法 actionPerformed ,当用户点击屏幕上的按钮时, button 就会调用这个方法。匿名内部类实现了该方法
这实际上是一个代码即数据的例子——我们给按钮传递了一个代表某种行为的对象。
设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部类还是不够简便。为了调用一行重要的逻辑代码,不得不加上 4 行冗繁的样板代码
使用 Lambda 表达式将行为和按钮单击进行关联
button.addActionListener(event -> System.out.println("button clicked"));
和传入一个实现某接口的对象不同,我们传入了一段代码块——一个没有名字的函数
event 是参数名,和上面匿名内部类示例中的是同一个参数。 -> 将参数和 Lambda 表达式的主体分开,而主体是用户点击按钮时会运行的一些代码
和使用匿名内部类的另一处不同在于声明 event 参数的方式。使用匿名内部类时需要显式地声明参数类型 ActionEvent event ,而在 Lambda 表达式中无需指定类型,程序依然以
编译.这是因为 javac 根据程序的上下文( addActionListener 方法的签名)在后台推断出了参数 event 的类型。这意味着如果参数类型不言而明,则无需显式指定
看几种 Lambda 表达式的不同形式:
- 1:
Runnable runnable =
() -> System.out.println("hello world");
- 2:
ActionListener actionListener =
event -> System.out.println("button clicked");
- 3:
Runnable runnable1 =
() ->
{
System.out.println("hello");
System.out.println("world");
};
- 4:
BinaryOperator<Long> add =
(x,y) -> x+y;
- 5:
BinaryOperator<Long> addExplicit =
(Long x,Long y) -> x + y;
1中所示的 Lambda 表达式不包含参数,使用空括号 () 表示没有参数。该 Lambda 表达式实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为 void
2中所示的 Lambda 表达式包含且只包含一个参数,可省略参数的括号
如3所示Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号( {} )将代码块括起来,该代码块和普通方法遵循的规则别无二致,可以用返回或抛出异常来退出。只有一行代码的 Lambda 表达式也可使用大括号,用以明确 Lambda表达式从何处开始、到哪里结束
Lambda 表达式也可以表示包含多个参数的方法,如4所示。这时就有必要思考怎样去阅读该 Lambda 表达式。这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。变量 add 的类型是 BinaryOperator<Long> ,它不是两个数字的和,而是将两个数字相加的那行代码
所有 Lambda 表达式中的参数类型都是由编译器推断得出的。这当然不错,但有时最好也可以显式声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此。如5所示
目标类型是指 Lambda 表达式所在上下文环境的类型。比如,将 Lambda 表达式赋值给一个局部变量,或传递给一个方法作为参数,局部变量或方法参数的类型就是 Lambda 表达式的目标类型
Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。目标类型也不是一个全新的概念
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值
简单测试:
public class M2 {
interface MathOperation{
int operation(int a,int b);
}
interface GreetingService{
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
public static void main(String[] args) {
M2 m2 = new M2();
// 类型声明
MathOperation addition = (int a,int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
System.out.println("10 + 5 = " + m2.operate(10, 5, addition));
System.out.println("10 - 5 = " + m2.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + m2.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + m2.operate(10, 5, division));
}
}
引用值,而不是变量
使用匿名内部类,需要引用它所在方法里的变量。这时,需要将变量声明为 final
将变量声明为 final ,意味着不能为其重复赋值。同时也意味着在使用 final 变量时,实际上是在使用赋给该变量的一个特定的值
匿名内部类中使用 final 局部变量
final String name = getUserName();
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("hi " + name);
}
});
Java 8 虽然放松了这一限制,可以引用非 final 变量,但是该变量在既成事实上必须是final 。虽然无需将变量声明为 final ,但在 Lambda 表达式中,也无法用作非终态变量。如果坚持用作非终态变量,编译器就会报错
既成事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,
而不是变量
Lambda 表达式中引用既成事实上的 final 变量
String name = getUserName();
button.addActionListener(event -> System.out.println("hi " + name));
如果你试图给该变量多次赋值,然后在 Lambda 表达式中引用它,编译器就会报错
未使用既成事实上的 final 变量,导致无法通过编译
String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi " + name));
例子:
public class M3 {
final static String STRING = "hello";
interface Greet{
void say(String Msg);
}
public static void main(String[] args) {
Greet greet = Msg ->
{
System.out.println(STRING + Msg);
};
greet.say("lolo");
}
}
可以直接在 lambda 表达式中访问外层的局部变量
public class M4 {
interface Converter<T1,T2>{
void convert(int i);
}
public static void main(String[] args) {
final int num = 1;
Converter<Integer,String> stringConverter =
(param) ->
{
System.out.println(String.valueOf(param + num));
};
stringConverter.convert(6);
}
}
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//报错信息:Local variable num defined in an enclosing scope must be final or effectively
final
Lambda 表达式本身的类型:函数接口
函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。
使用只有一个方法的接口来表示某特定方法并反复使用,是很早就有的习惯。使用 Swing编写过用户界面的人对这种方式都不陌生,这里无需再标新立异,Lambda 表达式也使用同样的技巧,并将这种接口称为函数接口
ActionListener 接口:接受 ActionEvent 类型的参数,返回空
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent event);
}
ActionListener 只有一个抽象方法: actionPerformed ,被用来表示行为:接受一个参数,返回空。记住,由于 actionPerformed 定义在一个接口里,因此 abstract 关键字不是必需的。该接口也继承自一个不具有任何方法的父接口: EventListener
这就是函数接口,接口中单一方法的命名并不重要,只要方法签名和 Lambda 表达式的类型匹配即可。可在函数接口中为参数起一个有意义的名字,增加代码易读性,便于更透彻
地理解参数的用途
如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。
这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了@FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错,可以拥有若干个默认方法
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现
类型推断
Lambda 表达式中的类型推断,实际上是 Java 7 就引入的目标类型推断的扩展。比如Java 7 中的菱形操作符,它可使 javac 推断出泛型参数的类型
使用菱形操作符,根据变量类型做推断
Map<String, Integer> oldWordCounts = new HashMap<String, Integer>(); //1
Map<String, Integer> diamondWordCounts = new HashMap<>(); //2
为变量 oldWordCounts 1明确指定了泛型的类型,而变量 diamondWordCounts 2则使用了菱形操作符。不用明确声明泛型类型,编译器就可以自己推断出来
使用菱形操作符,根据方法签名做推断
private void useHashmap(Map<String, String> values);
useHashmap(new HashMap<>());
Java 7 中程序员可省略构造函数的泛型类型,Java 8 更进一步,程序员可省略 Lambda 表达式中的所有参数类型。再强调一次,这并不是魔法, javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断
Java8中重要的函数接口
Function<T, R>
Fcuntion接口是对接受一个T类型参数,返回R类型的结果的方法的抽象,通过调用apply方法执行内容。
接口方法
// 将此参数应用到函数中
R apply(T t)
//返回一个组合函数,该函数结果应用到after函数中
Function<T, R> andThen(Function<? super R,? extends V> after)
//返回一个组合函数,首先将入参应用到before函数,再将before函数结果应用到该函数中
Function<T, R> compose(Function<? super V,? extends T> before)
例子:
public class M1 {
public static final int addOne(int a){
return a+1;
}
public static int operation(int a,Function<Integer,Integer> function){
return function.apply(a);
}
public static void main(String[] args) {
int x = 1;
int y = operation(x,x1 -> addOne(x1));
System.out.printf("x= %d, y = %d\n", x, y);
System.out.println("=====================");
// 当然你也可以使用lambda表达式来表示这段行为,只要保证一个参数,一个返回值就能匹配
int z = operation(x,h->h+10);
System.out.println(z);
}
}
计算给定字符串的长度:
public class M2 {
public static int computeLen(String source, Function<String,Integer> function){
int len = function.apply(source);
return len;
}
public static void main(String[] args) {
// 使用lambda
System.out.println(computeLen("jdjsajd",(string)->string.length()));
//使用了方法引用
System.out.println(computeLen("helo",String::length));
}
}
Function<T, R>还有的两个方法:
- compose方法接收一个Function参数before,该方法说明是返回一个组合的函数,首先会应用before,然后应用当前对象,换句话说就是先执行before对象的apply,再执行当前对象的apply,将两个执行逻辑串起来。
- andThen方法接收一个Function参数after,与compose方法相反,它是先执行当前对象的apply方法,再执行after对象的方法
public class M3 {
public static int compute_1(int i,
Function<Integer,Integer> after,
Function<Integer,Integer> before){
return after.compose(before).apply(i);
}
public static int compute_2(int i,
Function<Integer,Integer> before,
Function<Integer,Integer> after){
return before.andThen(after).apply(i);
}
public static void main(String[] args) {
// 当调用compute1(5,i -> i 2,i -> i i)时,先平方再乘以2所以结果是50。而compute2方法对两个Function的调用正好相反,所以结果是100
System.out.println(compute_1(
5,
i -> i * 2,
i -> i * i
));
System.out.println(compute_2(
5,
i -> i * 2,
i -> i * i
));
}
}
Consumer<T>
Consumer 接口翻译过来就是消费者,顾名思义,该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数'消费掉了',一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句
接口方法
//对给定的参数执行操作
void accept(T t)
//返回一个组合函数,after将会在该函数执行之后应用
default Consumer andThen(Consumer<? super T> after)
几个应用例子:
public class M1 {
public static void main(String[] args) {
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("jfjfj");
}
}
public class M2 {
public static void consumer_1(double num){
method(num,
(m) ->
System.out.println("花了 " + m + " RMB"));
}
public static void method(double number, Consumer<Double> consumer){
consumer.accept(number);
}
public static void main(String[] args) {
consumer_1(555);
}
}
public class M3 {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("hello");
Consumer<StringBuilder> consumer = (s) ->
s.append(" world");
consumer.accept(builder);
System.out.println(builder.toString());
}
}
public class M4 {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("hello ");
Consumer<StringBuilder> consumer_1 =
(s) ->
s.append("LOL");
Consumer<StringBuilder> consumer_2 =
(s) ->
s.append("Dota");
consumer_1.andThen(consumer_2).accept(builder);
System.out.println(builder.toString());
}
}
Supplier<T>
Supplier 接口翻译过来就是提供者,和上面的消费者相反,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值
接口方法
//获取结果值
T get()
几个例子:
public class M1 {
public static void main(String[] args) {
Supplier<String> stringSupplier = () ->
"djjdjdjd";
System.out.println(stringSupplier.get());
}
}
public class M2 {
static Random random = new Random(55);
public static List<Integer> getNumList(Supplier<Integer> supplier){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(supplier.get());
}
return list;
}
public static void supplier(){
List<Integer> list = getNumList(() ->
random.nextInt(555));
list.forEach(System.out::println);
}
public static void main(String[] args) {
supplier();
}
}
Predicate<T>
predicate<T> 谓语接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<T,R>,但是为了便于区分语义,还是单独的划了一个接口
接口方法
// 根据给定的参数进行判断
boolean test(T t)
//返回一个组合判断,将other以短路与的方式加入到函数的判断中
Predicate and(Predicate<? super T> other)
//返回一个组合判断,将other以短路或的方式加入到函数的判断中
Predicate or(Predicate<? super T> other)
//将函数的判断取反
Predicate negate()
使用:
public class M1 {
public static void main(String[] args) {
Predicate<Integer> predicate = n ->
n != 0;
System.out.println(predicate.test(22));
System.out.println("====================");
Predicate<Integer> predicate_1 = n ->
n != 0;
predicate_1 = predicate_1.and(n ->
n >= 100);
System.out.println(predicate_1.test(555));
System.out.println("====================");
Predicate<Integer> predicate_2 = n ->
n != 0;
predicate_2 = predicate_2.or( n ->
n < 10);
System.out.println(predicate_2.test(0));
System.out.println("====================");
Predicate<Integer> predicate_3 = number -> number != 0;
predicate_3 = predicate_3.negate();
System.out.println(predicate_3.test(10));
}
}
public class M2 {
public static List<String> filterStr(List<String> list,
Predicate<String> predicate){
List<String> strings = new ArrayList<>();
list.forEach(str ->
{
if (predicate.test(str)){
strings.add(str);
}
});
return strings;
}
public static void predicate(){
List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
List<String> res = filterStr(list,
s -> s.length() > 3
);
res.forEach(System.out::println);
}
public static void main(String[] args) {
predicate();
}
}
BiFunction<T, U, R>
使用前面Function以后,发现Function只能接收一个参数,如果我要传递两个参数呢,这一点Java8也替我们考虑到了,就是我们截下来要讲到的 BiFunction
方法:
//将此函数应用于给定的参数。
R apply(T t, U u);
//返回一个组合函数,首先将此函数应用于其输入,然后将 after函数应用于结果。
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after)
使用例子:
public class M1 {
public static int compute(int a, int b, BiFunction<Integer,Integer,Integer>
function){
return function.apply(a,b);
}
public static int compute_1(int a, int b,
BiFunction<Integer,Integer,Integer> function_1,
Function<Integer,Integer> function_2){
return function_1.andThen(function_2).apply(a,b);
}
public static void main(String[] args) {
System.out.println(compute(2,5,
(x,y) ->
x + y));
System.out.println(compute(2,5,
(x,y) ->
x - y));
System.out.println(compute(2,5,
(x,y) ->
x * y));
System.out.println("===============================");
// 首先执行(v1, v2) -> v1 + v2,然后执行 v1 -> v1 * v1。
System.out.println(compute_1(100,200,
(x,y) -> (x + y),x->x*x));
}
}
BinaryOperator<T>
public interface BinaryOperator<T> extends BiFunction<T,T,T>
BiFunction的一个特殊例子,接收两个参数,产生一个结果,只是它的三个参数都是同一个数据类型
接口方法
//根据给定参数执行函数
T apply(T t, T u)
//返回一个组合函数,after应用于该函数之后
BiFunction<T,T,T> andThen(Function<? super T,? extends T> after)
//返回二元操作本身,通过特殊比较器返回最大的元素
BinaryOperator maxBy(Comparator<? super T> comparator)
//返回二元操作本身,通过特殊比较器返回最小的元素
BinaryOperator minBy(Comparator<? super T> comparator)
使用:
public class M2 {
public static void test_1(Integer integer1,Integer integer2,
BinaryOperator<Integer> binaryOperator){
System.out.println(binaryOperator.apply(integer1,integer2));
}
// * 返回两者里面较小的一个
public static void test_2(String s1, String s2, Comparator<String> comparator){
System.out.println(BinaryOperator.minBy(comparator).apply(s1,s2));
}
// * 返回两者里面较大的一个
public static void test_3(String s1,String s2,Comparator<String> comparator){
System.out.println(BinaryOperator.maxBy(comparator).apply(s1,s2));
}
public static void main(String[] args) {
test_1(1,2,(x,y) -> x + y);
System.out.println("==============================");
test_1(1,55,(x,y) -> x - y);
System.out.println("==============================");
test_2("hello","wonders",(str1,str2)->str1.length()-str2.length());
}
}
BinaryOperator<T>中有两个静态方法,是用于比较两个数字或字符串的大小。
//获取更小的值
static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator)
//获取更大的值
static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator)
public static void main(String[] args) {
BinaryOperator<Integer> binaryOperator = BinaryOperator.minBy(
Comparator.naturalOrder()
);
System.out.println(binaryOperator.apply(100,200));
}
interface DoubleUnaryOperator
接口方法
// 根据给定参数执行函数
double applyAsDouble(double operand);
// 返回一个组合函数,after应用于该函数之后
DoubleUnaryOperator andThen(DoubleUnaryOperator after)
//返回一个组合函数,before应用于该函数之前
DoubleUnaryOperator compose(DoubleUnaryOperator before)
使用:
public class M4 {
public static void main(String[] args) {
DoubleUnaryOperator doubleUnaryOperator = d -> d + 2.50;
System.out.println(doubleUnaryOperator.applyAsDouble(2.3));
System.out.println("=================================================");
DoubleUnaryOperator doubleUnaryOperator1 = doub -> doub + 2.5;
DoubleUnaryOperator doubleUnaryOperator2 = doub -> doub * 3;
double result = doubleUnaryOperator1.andThen(doubleUnaryOperator2)
.applyAsDouble(10);
System.out.println(result);
System.out.println("=================================================");
DoubleUnaryOperator doubleUnaryOperator_3 = doub -> doub + 2.5;
DoubleUnaryOperator doubleUnaryOperator_4 = doub -> doub * 3;
double result_1 = doubleUnaryOperator_3.compose(doubleUnaryOperator_4)
.applyAsDouble(10);
System.out.println(result_1);
}
}
UnaryOperator<T>
public interface UnaryOperator<T> extends Function<T, T>
接口方法
//将给定参数应用到函数中
T apply(T t)
//返回一个组合函数,该函数结果应用到after函数中
Function<T, R> andThen(Function<? super R,? extends V> after)
//返回一个组合函数,首先将入参应用到before函数,再将before函数结果应用到该函数中
Function<T, R> compose(Function<? super V,? extends T> before)
使用:
public class M5 {
public static void main(String[] args) {
UnaryOperator<String> unaryOperator = greet -> greet + "hello ";
System.out.println(unaryOperator.apply("world"));
System.out.println(UnaryOperator.identity());
}
}
interface DoubleToIntFunction
接口方法
//根据给定的参数执行函数
int applyAsInt(double value)
使用:
public class M6 {
public static void main(String[] args) {
DoubleToIntFunction doubleToIntFunction = d -> Double.valueOf(d).intValue();
System.out.println(doubleToIntFunction.applyAsInt(1.2));
}
}
BiConsumer<T,U>
接口方法
//对给定的参数执行操作
void accept(T t, U u)
//返回一个组合函数,after将会在该函数执行之后应用
default BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after)
public class M8 {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
BiConsumer<String,String> biConsumer =
(a,b) ->
{
builder.append(a);
builder.append(b);
};
biConsumer.accept("hello "," world");
System.out.println(builder.toString());
}
}
public class M9 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
BiConsumer<String, String> biConsumer = (a, b) -> {
sb.append(a);
sb.append(b);
};
BiConsumer<String, String> biConsumer1 = (a, b) -> {
System.out.println(a + b);
};
biConsumer.andThen(biConsumer1).accept("Hello", " Jack!");
}
}