「Java基础」Lambda表达式就是这么简单!

Lambda表达式是Java8中新增的特性,lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。在以前定义的方法中,只能将基本类型或者引用类型的变量作为方法参数,在Java 8以后可以将一个代码片段作为方法参数。

Lambda表达式入门

在集合中Java为开发者提供了遍历集合的简洁方式,如下例所示:

package cn.bytecollege;

import java.util.List;
import java.util.ArrayList;
public class LambdaDemo {
	public static void main(String[] args) {
		List list = new ArrayList<>();
		
		list.add("张三");
		list.add("李四");
		list.add("王五");
		
		list.forEach(e->System.out.println(e));
	}
}

在上面的示例中,调用了list对象的foreach方法,从程序可以看出,传入foreach的并不是一个变量,而是一段代码,这就是Lambda表达式。从上面的语法可以看出,Lambda表达式的主要作用就是代替匿名内部类的烦琐语法。

Lambda由3部分组成:

  1. 形参列表:形参列表允许省略形参的数据类型,如果形参列表中有且只有1个参数,可以省略形参列表的括号
  2. 箭头函数:->必须有横线和大于号组成
  3. 代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号。

下面,通过示例来学习Lambda的写法:

//表达式只有1个参数
(a)->{
    System.out.print(a);
}
//表达式可以简写为
a->{
    System.out.print(a);
}
//如果代码块中只有1条语句,可以省略大括号
a->System.out.print(a)

函数式接口

Lambda表达式的目标类型必须是函数式接口,所谓函数式接口代表只包含一个抽象方法的接口,函数式接口可以包含多个默认方法、类方法,但是只能声明一个抽象方法。

如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下可采用Lambda表达式来创建对象。

注意:Java8 专门为函数式接口提供了@FunctionalInterface注解,该注解通常放在接口定义前,该注解对程序功能没有任何作用,它的作用是用于告诉编译器执行更严格的检查,检查该接口必须是函数式接口,否则编译器出错。

Lambda表达式的结果就是被作为对象,程序中晚期可以使用Lambda表达式进行赋值,例如在多线程Thread类的构造器中可以传入Runnable接口的子类对象。查看Runnable接口发现,该接口也被声明为一个函数式接口:

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

所以,就可以使用Lambda表达式来创建线程:

package cn.bytecollege;
public class ThreadLambdaDemo {
	public static void main(String[] args) {
		Thread thread = new Thread(()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(i);
			}
		});
		thread.start();
	}
}

Lambda 表达式实现的是匿名方法——因此它只能实现特定函数式接口中的唯一方法。这意味着 Lambda 表达式有如下两个限制。


下面定义一个函数式接口深入学习Lambda表达式

package cn.bytecollege;
//函数式接口只能有一个抽象方法,并且要使用@FunctionalInterface声明
@FunctionalInterface
public interface Consumer {
	int add(int a,int b);
}

定义一个方法,方法参数是Consumer接口:

package cn.bytecollege;

public class MyTest {
	public static int test(Consumer consumer) {
		int a = 5;
		int b = 4;
		return consumer.add(a, b);
		
	}
	public static void main(String[] args) {
		int k = test((a,b)->{
			return a+b;
		});
		System.out.println(k);
	}
}

在上例中定义了一个函数式接口,在测试类的test方法传入了接口并调用了Consumer接口的add方法,需要注意的是,此时add方法并没有方法实现,在main方法中调用了test,并将一段代码(即add方法的实现)也就是lambda表达式当做参数传入了test方法。换句话说在上例中使用了lambda表达替代了烦琐的匿名内部类。对比下面的代码就可以看出Lambda表达式的独到之处。

package cn.bytecollege;

public class MyTest {
	public static int test(Consumer consumer) {
		int a = 5;
		int b = 4;
		return consumer.add(a, b);
		
	}
	public static void main(String[] args) {
		int k = test(new Consumer() {
			@Override
			public int add(int a, int b) {
				return a+b;
			}
		});
		System.out.println(k);
	}
}

从前面的程序可以看出Lambda表达式的使用离不开函数式接口,通常函数式接口中有且只能有1个抽象方法,这样使用Lambda表达式时也就明确了是哪个抽象方法的实现,如果接口中出现了多个抽象方法,那么就不能在接口上使用@FunctionInterface注解,会编译出错。因此,Java 8在java.util.function包中预定义了大量函数式接口,通常情况下这些接口完全可以满足开发需要:

下面在程序中示范上述接口的使用:

package cn.bytecollege.lambda;

import java.util.function.Function;

/**
 * 数据转换
 * @author MR.W
 *
 */
public class CastUtil {
	/**
	 * 定义方法将Object类型转换为String类型
	 * @param function
	 * @param o
	 * @return
	 */
	public static String castToString(Function function, Integer a) {
		return function.apply(a);
	}
}

在上面的CastUtil类中定义了castToString,在该方法中第一个参数是一个Java 8 预定义的函数式接口,在方法内调用了Function接口的apply()方法,作用是将任意类型转换成String。但是此时这个方法并没有方法的实现,需要在调用此方法时传入方法的实现。

package cn.bytecollege.lambda;
import java.util.function.Function;
public class Test {
	public static void main(String[] args) {
		Integer a = 10010;
		//使用Lambda表达式,此时castToString方法的第一个参数
		//就是Function函数式接口apply()的实现
		String s = CastUtil.castToString((o)->{
			return String.valueOf(o);
		}, a);
		System.out.println(s);
	}
}

在测试类中,调用了CastUtil的castToString()方法,并传入了Lambda表达式,以此Lambda表达式作为apply()方法的实现,在表达式中使用了String.valueOf()方法将对象转换成String类型。

方法引用与构造器引用

前面已经介绍过,如果Lambda 表达式的代码块只有一条代码,程序就可以省略 Lambda 表达式中代码块的花括号。不仅如此,如果Lambda 表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。方法引用和构造器引用可以让 Lambda表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号。

Lambda 表达式支持如下表所示的几种引用方式。

引用类方法

下面的示例将演示类方法的引用,首先定义一个函数式接口,接口中定义抽象方法castToString(),该方法的作用是将一个对象转换成String对象。

package cn.bytecollege.lambda;
@FunctionalInterface
public interface Function {
	R castToString(T t);
}

在String的学习中可以知道,String类有提供了类方法valueOf(Object o),该方法可以将任意对象转换成String类型,因此可以使用该方法作为Lambda表达式的实现代码:

package cn.bytecollege.lambda;

public class RefTest {
	public static void main(String[] args) {
		Function function = a->{
			return String.valueOf(a);
		};
		System.out.println(function.castToString("张三"));
	}
}

在上面的代码中,创建了Lambda表达式作为了Function接口中castToString()方法的实现。在Lambda表达式中调用了String.valueOf()方法来进行对象到字符串的转换,在代码第8行调用了function接口的castToString()方法,实际上调用了就是代码第5行创建的Lambda表达式。

上面的Lambda表达式的代码块只有一行调用类方法的代码,因此可以使用如下方法引用进行替换。代码如下:

package cn.bytecollege.lambda;

public class RefTest {
	public static void main(String[] args) {
		Function function = String::valueOf;
		System.out.println(function.castToString("张三"));
	}
}

对于上面的类方法的引用,也就是调用了String类的valueOf()方法来实现Function函数式接口中唯一抽象方法。当调用castToString()方法时,调用参数将会传给String类的valueOf()类方法。

引用对象的实例方法

下面演示第二种方法引用,引用对象的实例方法,首先使用Lambda表达式创建一个Function接口的子类对象:

Function function = o->o.toString();

上面的Lambda表达式只有一条语句,因此省略了该代码的花括号。

接下来程序调用function对象的castToString()方法:

package cn.bytecollege.lambda;
public class RefTest {
	public static void main(String[] args) {
		Function function = o->o.toString();
		System.out.println(function.castToString(100));
	}
}

上面的程序调用了function对象的castToString()方法时,由于function对象是Lambda表达式创建,castToString()方法的执行体就是Lambda表达式的代码部分,因此上面的程序输出了100.

上面的Lambda表达式代码只有一行,且调用了对象的o的toString()实例方法。因此代码可以进行如下替换:

package cn.bytecollege.lambda;
public class RefTest {
	public static void main(String[] args) {
		Function function = Object::toString;
		System.out.println(function.castToString(100));
	}
}

上面的Lambda表达式的代码只有一条语句,因此省略了代码块的花括号;而且由于表达式实现的castToString方法需要返回值,因此Lambda表达会将这行代码的值作为返回值。此时就可以使用方法引用进行替换,直接引用Object的toString()方法作为Lambda表达式的代码块。其中Function接口的castToString方法有个参数,当执行Lambda表达式代码块时,会自动调用传入参数的toString()方法。

引用构造器

下面的实例将演示如何引用构造器,首先定义函数式接口:

package cn.bytecollege.lambda;
@FunctionalInterface
public interface MyInterface {
	StringBuilder get(String s);
}

该函数式接口包含了一个get()抽象方法,该方法的作用是使用String对象生成一个StringBuilder对象,接着使用Lambda表达式创建一个MyInterface的对象:

package cn.bytecollege.lambda;
public class RefTest3 {
	public static void main(String[] args) {
		MyInterface myInterface = (s)-> new StringBuilder(s);
		StringBuilder sb = myInterface.get("张三");
	}
}

上面的代码调用了myInterface对象的get()方法时,由于该对象是Lambda表达式创建的,因此get()方法执行体就是Lambda表达式的代码块部分,即执行体就是执行new StringBuilder(a)语句,并将这条语句的值作为方法的返回值。因此上面代码中Lambda表达式的代码可以进行如下替换:

package cn.bytecollege.lambda;

public class RefTest3 {
	public static void main(String[] args) {
		MyInterface myInterface = StringBuilder::new;
		StringBuilder sb = myInterface.get("张三");
	}
}

对于上面的构造器引用,也就是调用StringBuilder类的构造方法来实现MyInteface函数式接口中唯一的抽象方法,当调用MyInterface接口的get()方法时,调用参数会传给StringBuilder构造器,从上面的程序中可以看出,调用myInterface对象的get()方法时,实际只传入了一个String类型的参数,这个String类型的参数会被传给StringBuilder的构造器。

Lambda表达式和匿名内部类的联系和区别

从前面介绍可以看出,Lambda 表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用,Lambda 表达式与匿名内部类存在如下相同点。

首先创建函数式接口:

package cn.bytecollege.ano;
@FunctionalInterface
public interface Display {
	int add(int a,int b);
	
	default void print() {
		System.out.println("Hello!");
	}
}
package cn.bytecollege.ano;

public class LambdaTest {
	private int age = 18;
	private static String name = "Byte科技";
	
	public void test() {
		String book = "Java编程思想";
		Display display = (a,b)->{
			//访问外部类的实例变量
			System.out.println(age);
			//访问外部类的类变量
			System.out.println(name);
			//访问局部变量
			System.out.println(book);
			return a+b;
		};
		//调用display对象从接口继承的默认方法
		display.print();
//		book = "Java核心技术卷";
		System.out.println(display.add(1, 2));
	}
}

创建测试类:

package cn.bytecollege.ano;

public class Test {
	public static void main(String[] args) {
		LambdaTest test = new LambdaTest();
		test.test();
	}
}

上面的程序使用Lambda表达式创建了一个Display接口的对象,Lambda表达式分别访问了外部类的实例变量,类变量从这些来看Lambda表达式的代码块和匿名内部类的方法体是相同的。

和匿名内部类相似,由于Lambda表达式访问了了book局部变量,因此该局部变量相当于有一个隐式的final修饰,不允许对book局部变量重新赋值。

当程序使用 Lambda 表达式创建了 Display 的对象之后,该对象不仅可调用接口中唯一的抽象方法,也可调用接口中的默认方法。

Lambda表达式与匿名内部类主要存在如下区别:

展开阅读全文

页面更新:2024-05-01

标签:表达式   部类   变量   抽象   函数   接口   对象   参数   代码   基础   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top