lambda表达式

1. 什么是lambda表达式

        lambda表达式本质上是一个匿名方法。匿名类的一个问题是,如果匿名类的实现非常简单,例如只包含一个方法的接口,那么匿名类的语法可能看起来不实用且不清楚。在这些情况下,您通常会尝试将功能作为参数传递给另一个方法,例如当有人单击按钮时应采取的操作。Lambda表达式使您可以执行此操作,将功能视为方法参数,或将代码视为数据。
        lambda表达式不能直接使用,一般都是先赋给一个接口,然后对这个接口操作,或者非要直接用必须强转,如:

System.out.println( () -> {} ); //错误! 目标类型不明
System.out.println( (Runnable)() -> {} ); // 正确

2. lambda表达式语法

2.1 lambda表达式的一般语法

(type_1 para_1, type_2 para_2,···,type_n, para_n) -> {
	statement1;
	statement2;
	…………
	statementn;
	return ……
}

2.2 lambda表达式的单参数语法

如果方法只有一个参数,而且这个参数的类型可以推导出,那么可以省略小括号

	para1 -> {
	statement1;
		statement2;
		…………
		statementn;
		return ……
	}

【例】将列表中的字符串转换为全小写

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("Ni");
		list.add("Hao");
		list.add("Lambda");
		
		List<String> lowerList = list.stream().map(
			name -> { return name.toLowerCase();} //单参数lambda表达式
		).collect(Collectors.toList());
	}
}

2.3 lambda表达式的单语句写法

当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号

param1 -> statment

【例】将列表中的字符串转换为全小写

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("Ni");
		list.add("Hao");
		list.add("Lambda");

		List<String> lowerList = list.stream().map(
				name -> name.toLowerCase() //单语句lambda表达式
		).collect(Collectors.toList());
	}
}

2.4 lambda表达式的无参数写法

即使lambda表达式没有参数,仍然要提供空括号,就像无参方法一样

public class Test {
	public static void main(String[] args) {
		Thread t = new Thread(
			()->{for(int i =10;i>0;i--)System.out.println(i);}
		);
		t.start();
	}
}

2.5 lambda表达式方法引用写法

类似C/C++作用域绑定符(后面会提到)

class :: method
instance :: method

【例】将列表中的字符串转换为全小写

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("Ni");
		list.add("Hao");
		list.add("Lambda");

		List<String> lowerList = list.stream().map(
				String::toLowerCase //方法引用写法
		).collect(Collectors.toList());
	}
}

3. lambda表达式可使用的变量

先举例

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
	public static void main(String[] args) {
		//将为列表中的字符串添加前缀字符串
		String waibu = "lambda :";
		List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
		List<String>execStrs = proStrs.stream().map(
			chuandi -> {
				Long zidingyi = System.currentTimeMillis();
				return waibu + chuandi + " -----:" + zidingyi;
			}
		).collect(Collectors.toList());
		execStrs.forEach(System.out::println);
	}
}

在这里插入图片描述
变量waibu —— 外部变量
变量chuandi —— 传递变量,即lambda表达式的参数
变量zidingyi —— 内部自定义变量

lambda表达式可以访问给它传递的变量,访问自己内部定义的变量,同时也能访问它外部的变量。

不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)

当在表达式内部修改waibu = waibu + " ";时,IDE就会提示你:
Local variable waibu defined in an enclosing scope must be final or effectively final

编译时会报错。因为变量waibu被lambda表达式引用,所以编译器会隐式的把其当成final来处理。

以前Java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。现在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理。

4. lambda表达式中的this概念

this指的是定义该表达式时所在类的对象,而不是lambda表达式所产生的对象

package java_lambda;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestThis {

     public void whatThis(){
           //转全小写
           List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
           List<String> execStrs = proStrs.stream().map(str -> {
                 System.out.println(this.getClass().getName());
                 return str.toLowerCase();
           }).collect(Collectors.toList());
           execStrs.forEach(System.out::println);
     }

     public static void main(String[] args) {
           TestThis wt = new TestThis();
           wt.whatThis();
     }
}

在这里插入图片描述

5. 函数式接口

        对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口(functional interface)
        最好把lambda表达式看作是一个函数,而不是一个对象,另外要接受lambda表达式可以传递到函数式接口
        实际上,在Java中,对lambda表达式所能做的也只是能转换为函数式接口。在其他编程语言像Groovy,是可以用变量保存lambda表达式的,称为闭包,不过Java设计者还是决定保持我们熟悉的接口概念,没有为Java语言增加新的函数类型。
        注解是@FunctionalInterface,可加可不加

6.方法引用

        有时,可能已经有现成的方法可以完成你想要传递到其他代码的某个动作。例如,假设你希望只要出现一个定时器事件就打印这个事件。当然,为此可以调用:

Timer t = new Timer(1000, event -> System.out.println(event));

但是,如果直接把println方法传递到Timer构造器就更好了。具体做法如下:

Timer t = new Timer(1000, System.out::println);

表达式System.out::println是一个方法引用,它等价于lambda表达式:

	x->System.out.println(x)

不难看出,要用::操作符分隔方法名与对象或类名,主要有3种情况:

  • object :: instanceMethod
  • Class :: staticMethod
  • Class :: instanceMethod

        前2种情况中,方法引用等价于提供方法参数的lambda表达式。如:
                System.out::println 等价于 x->System.out.println(x)
                Math :: pow 等价于 (x,y) -> Math.pow(x,y)
        对于第3种情况,第一个参数会成为方法的目标。例如:
                 String :: compareToIgnoreCase 等同于 (x,y)->x.compareToIgnoreCase(y)

        可以再方法引用中使用this参数。例如:this :; equals 等同于 x -> this.equals(x) ,使用super也是合法的,如 super::instanceMethod

7. 构造器引用

        构造器引用和方法引用很类似,只不过方法名为new。例如,Person :: new 是Person构造器的一个引用。哪一个构造器呢?这取决于上下文,假设你有一个字符串列表,可以把它转换为一个Person对象数组,为此要在各个字符串上调用构造器,调用如下:

	ArrayList<String> names = ...;
	Stream<Person> stream = names.stream().map(Person::new);
	List<Person> people = stream.collect(Collectors.toList());

        如果有多个Person构造器,编译器会选择一个有String参数的构造器,因为它从上下文推导出这是在对一个字符串调用构造器

        可以用数组类型建立构造器引用,如: int[]::new 是一个构造器引用,它有一个参数:即数组的长度。这等价于lambda表达式 x -> new int[x]

        Java有一个限制,不能构造泛型T的数组。数组构造器对于克服这个很有用。假如:我们需要一个Person对象数组,Stream有一个toArray方法可以返回Object数组:

	Object people = stream.toArray();

        不过这并不令人满意。用户希望得到一个Person引用数组,而不是Object数组。流库利用构造器引用解决了这个问题。可以把Person[]::new 传入toArray方法:

	Person[] people = stream.toArray(Person[]::new);

toArray方法调用这个构造器来得到一个正确类型的数组。然后填充这个数组并返回

        简单来说可以理解为:

	类名::new
	类型名[]::new
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值