Java 的 Lambda 表达式的使用方法示例

Java8 - Lambda 表达式

Lambda 表达式是java8新增的主要特性之一,lambda表达式又称闭包或匿名函数,主要优点在于简化代码、增强代码可读性、并行操作集合等。

基本定义

 (parameters) -> expression
 or
 (parameters) ->{ statements; }

lambda表达式又称闭包或匿名函数其特性表现为

  1. 可选类型声明,即无需声明参数类型,编译器即可自动识别
  2. 可选的参数圆括号,即仅有一个参数时圆括号可以省略
  3. 可选的大括号,主体只包含一个语句时可省略大括号
  4. 可选的返回关键字,主体只包含一个表达式返回值并省略大括号时,编译器会自动return返回值;有大括号时,需要显式指定表达式return了一个数值。
    比如:
() -> 1
() -> System.out.print("Java8 lambda.");
x ->  ++x
(int x, int y) -> x - y

当然你要是直接在代码中这样写,编译都通不过的,每一个lambda表达式都需要做一些特殊的处理。下面介绍一个通过函数式接口实现上面的语法。

package test;

public class MainTest {

	public static void main(String[] args) {
		MyLambdaInteerface myLambdaInteerface = (x) -> ++x;
		System.out.println(myLambdaInteerface.doSameThing(5));
		
		// 打印结果 :6
	}
}

/** 
 * 定义一个接口类,并在里面创建一个唯一的抽象方法
 * 注意 @FunctionalInterface注解,表示将其定义为一个函数式接口
 * */
@FunctionalInterface
interface MyLambdaInteerface{
	//接口中所有的方法,都是抽象方法,所以也可以说,接口就是一个特殊的抽象类
	int doSameThing(int x);
}

个人建议,从程序的严谨性角度出发,尽量指明函数的参数类型,避免出错!!!

通过 MyLambdaInteerface myLambdaInteerface = (x) -> ++x;的方式,我实际就重写了接口中的方法,没有按照传统的方式,先创建类继承接口然后再重写抽象方法的方式。再来一个例子体会一下 Lambda 的作用和执行顺序:

package test;

public class MainTest {

	public static void main(String[] args) {
		new MyLambda(() -> System.out.println("使用 Lambda 的 设计的  doSameThing() 方法")).doSameThing();
		
		/*
		 	使用 MyLambda 的 doSameThing() 方法
			使用 Lambda 的 设计的  doSameThing() 方法
		 * */
	}
}

/** 
 * 定义一个接口类,并在里面创建一个唯一的抽象方法
 * 注意 @FunctionalInterface注解,表示将其定义为一个函数式接口
 * */
@FunctionalInterface
interface MyLambdaInteerface{
	//接口中所有的方法,都是抽象方法,所以也可以说,接口就是一个特殊的抽象类
	void doSameThing();
}

/** 
 * 继承刚刚定义一个接口类
 * */
class MyLambda implements MyLambdaInteerface{
	private MyLambdaInteerface target;
	
	@Override
	public void doSameThing() {
		System.out.println("使用 MyLambda 的 doSameThing() 方法");
		target.doSameThing();
	}
	
	public MyLambda(MyLambdaInteerface target) {
		this.target = target;
	}
}

Java Runnable 接口的 lambda 实现

用 lambda 代替匿名类是java8中lambda的常用形式,上面的例子也是这里的简化

public static void main(String[] args) {
	new Thread(new Runnable() {
		@Override
		public void run() {
			System.out.println("Java Runnable 接口的 lambda 实现");
		}
	}).start();
}

简化后

public static void main(String[] args) {
	new Thread(() -> System.out.println("Use lambda")).start();
}

此处简要提下,用lambda表达式代替匿名类的关键在于,匿名类实现的接口使用了java.lang.FunctionalInterface注解,且只有一个待实现的抽象接口方法,如Runnable接口:

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

Java List 迭代的 lambda 实现

在 Java8 之前,我们做循环迭代只能用 for 或者 iterator 迭代器处理,在 Java8 中,集合类的顶层接口 java.lang.Iterable 定义了一个 forEach 方法:

/*
 * @param action The action to be performed for each element
 * 
 * @throws NullPointerException if the specified action is null
 * 
 * @since 1.8
 */
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

forEach 方法可以迭代集合的所有对象,其参数为Consumer对象,Consumer类位于java.util.function包下,我们看下其定义:

@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);
		};
	}
}

上面那些东西我也搞不太明白,总之,java8之后 集合的循环就多了一种方式,直接上测试用例:

public class MainTest {

	public static void main(String[] args) {
		List<Integer> intList = Arrays.asList(1,2,3,4,5,6);
		// 最普通的方式
		for (Integer integer : intList) {
			System.out.println(integer);
		}
		// lambda 表达式方式
		intList.forEach(n -> System.out.println(n));
		// lambda 表达式的语法糖,函数式接口 变量名 = 类实例::方法名
		intList.forEach(System.out::println);
	}
}
函数式接口

函数式接口(Functional Interface) :任何接口,如果只包含唯一 一个抽象方法,那么它就是一个FI。(之前它们被称为 SAM类型,即 单抽象方法类型(Single Abstract Method))。接口中的方法默认就是public abstract的。
Java 的 Lambda 表达式的使用方法示例_第1张图片

​接口可能继承了一个 Object 已经提供的方法,比如 toString(),equals( )…这些都不属于函数式接口方法的范畴, 所以函数式接口中所说的方法不包括这些。例如下面FI接口也是一个函数式接口。

总的来说,Lambda 表达式可以用在任何需要使用匿名方法,或是代理的地方。编译器会将Lambda表达式编译为标准的匿名方法(可以使用ildasm.exe or reflector.exe得到确认)。

注意事项

lambda表达式可以使用方法引用,当且仅当主体中不修改lambda表达式提供的参数,如第三章提到的一种写法

intList.forEach(System.out::println);

而如果对参数有任何修改时不能使用方法引用,如:

intList.forEach(n -> System.out.println(n + 1));

是错误的。

Lambda与匿名类的联系和区别

联系:

  1. 都可以访问final或effectively final局部变量。
  2. 生成的对象都可以调用实现的接口方法。

区别:

  1. this指针的指向不同。我们知道匿名类的this指针指向匿名类,而lambda表达式的this指针指向的是包围lambda表达式的类。
  2. 编译方式不同。lambda在编译器内部被翻译为私有方法,并使用了Java 7的 invokedynamic 字节码指令来动态绑定这个方法
  3. 实现的接口限制有区别。匿名类可以为任意接口创建实例,只要实现接口所有的抽象方法即可;而lambda表达式只能实现函数式接口(只有一个必须实现的抽象方法)。
  4. 接口默认方法的调用权限不同。匿名类实现的抽象方法允许调用接口中的默认方法,而lambda表达式不能调用接口中的默认方法。

你可能感兴趣的:(java,java)