lambda表达式支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口(函数式接口)的实例。
lambda表达式入门
匿名内部类
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
//处理数组,具体处理行为取决于匿名内部类
pa.process(target , new Command()
{
public void process(int[] target) {
int sum = 0;
for (int tmp : target) {
sum += tmp;
}
System.out.println("数组元素的总和是:"+sum);
}
});
}
}
lambda改写后的代码:
public class CommandTest2 {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
//处理数组,具体处理行为取决于匿名内部类
pa.process(target , (int[] target)-> {
int sum = 0;
for (int tmp : target) {
sum += tmp;
}
System.out.println("数组元素的总和是:"+sum);
});
}
}
lambda表达式的主要作用就是替代匿名内部类的繁琐语法。他由三部分组成:
- 形参列表。允许省略形参类型。如果形参列表只有一个参数,甚至可以省略形参列表的圆括号。
- 箭头:->。
- 代码块。如果代码块只包含一个语句,允许省略代码块的花括号。只有一条return语句,甚至可以省略return关键字,此时会自动返回这条语句的值。
lq.eat(()->System.out.println("Great"));//不带形参的匿名方法,只有一条语句,可以省了花括号;
lq.eat(apple->{
System.out.println("Great");
System.out.println("Great");
});//只带一个形参的匿名方法,省略了圆括号;
lq.eat((a,b)->a+b);//只有一行语句,这行语句的返回值将作为改代码块的返回值。
lambda表达式实际上会被当做任意一种类型的对象,到底需要当成何种类型的对象,取决于运行环境的需要。
lambda表达式与函数式接口
Lambda表达式的类型,也被称为目标类型。lambda表达式的目标类型必须是明确的函数式接口。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。
java8中专门为函数式接口提供了一个@FunctionalInterface注解,该注解通常方法接口定义前面,这个注解没有任何作用,只是告诉编译器执行更加严格,检查该接口必须是函数式接口,否则编译器就会报错。
我们在使用过程中最好将一个lambda表达式理解成一个函数,而不是一个对象,并记住它可以被转换成一个函数式接口。为了保证lambda表达式的目标类型是一个明确的函数式接口,一般情况下lambda有下面3种常用的使用方式:
- 将lambda表达式赋值给函数式接口类型的变量。
public class Test
{
public static void main(String[] args)
{
A a = () -> System.out.println("这里使用A的函数式接口来赋值给类型是A的变量a");
}
}
@FunctionalInterface
interface A
{
void test();
}
- 将lambda表达式作为函数式接口类型的参数传递给某个方法。
public class Test
{
public static void main(String[] args)
{
new Thread(() -> System.out.println("使用Runnable函数式接口来传递给Thread构造器一个参数")).start();
}
}
3.使用函数式接口对lambda表达式进行强制类型转换。
public class Test
{
public static void main(String[] args)
{
Object a = (A) () -> System.out.println("这里使用A的函数式接口来赋值给类型是A的变量a");
}
}
@FunctionalInterface
interface A
{
void test();
}
需要说明的一点:同样的lambda表达式的目标类型完全有可能是变化的,唯一的要求就是,lambda表达式实现的匿名方法与目标类型中唯一的抽象方法有着相同的形参列表。
public class Test
{
public static void main(String[] args)
{
Object a = (A) () -> System.out.println("这里使用A的函数式接口来赋值给类型是A的变量a");
Object b = (B) () -> System.out.println("lambda表达式一样,但是目标类型不同");
}
}
@FunctionalInterface
interface A
{
void test();
}
@FunctionalInterface
interface B
{
void test();
}
请注意处理检查期异常。
public class Test
{
public static void main(String[] args)
{
new Thread(() ->
{
System.out.println("下面的编译时异常必须要捕获,不然就要报错");
Thread.sleep(100);
}).start();
}
}
可以在编译时异常try,catch,或者我们直接在声明函数式接口的时候抛出异常。
public class Test
{
public static void main(String[] args)
{
A a = () ->
{
Thread.sleep(1000);
};
B b = () ->
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
};
}
}
@FunctionalInterface
interface A
{
void test() throws Exception;
}
@FunctionalInterface
interface B
{
void test();
}
在java8中的java.util.function包下预定义了大量函数式接口,典型的包含如下4类接口:
1),XxxFuncion:这类接口通常包含一个apply()抽象方法,该方法对参数进行处理,转换,然后返回一个新的值。具体的apply()方法的处理逻辑由lambda表达式来实现。该函数式接口通常用于对指定数据进行转换处理
2),XxxConsumer:这类接口通常包含一个accept()抽象方法,这个方法与上面的XxxFunction接口中的apply()方法基本相似,也是负责对参数进行处理,只是该方法不会返回处理结果。
3),XxxPredicate:这类接口通常包含一个test()抽象方法,该方法通常用来对参数进行某种判断,然后返回一个boolean值。具体的test()方法的判断逻辑由lambda表达式来实现,这个接口用于判断参数是否满足特定条件,经常用于过滤数据。
4),XxxSupplier:这类接口通常包含一个getAsXxx()抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法来返回一个数据。
在大多数函数式编程语言中,函数类型都是结构化的。为了指定将2个字符串映射到一个整数的函数,我们需要使用一个类似于Function2<String,String,Integer>
或者(String,String)->int
的类型。不过在java中,我们直接使用Comparator<String>
这样的函数式接口来声明函数就OK。在许多情况下,我们希望接受任意的函数,而不是某种特定语义的函数,java8已经给我们提供了许多的函数类型,我们要尽可能的使用它们。现在这里做一个整理:
Runnable:public abstract void run():执行一个没有参数和返回值的操作
Supplier<T>:T get():提供一个T类型的值
Comsumer<T>:void accept(T t):处理一个T类型的值
BiComsumer<T,U>:void accept(T t, U u):处理T类型和U类型的值
Function<T,R>:R apply(T t):处理一个参数类型是T的函数,返回R
BiFunction<T,U,R>:R apply(T t, U u):处理参数类型是T,U的函数,返回R
UnaryOperator<T>:T apply(T t):对类型T进行一元操作,这个接口extends Function<T, T>
BinaryOperator<T>:T apply(T t,T t):对类型T进行二元操作,这个接口extends Function<T, T, T>
Predicate<T>:boolean test(T t):对类型T进行计算,返回boolean值
BiPredicate<T, U>:boolean test(T t, U u):对类型T和U进行计算boolean值
public class Test
{
public void test(BiPredicate<String, String> huhu)
{
System.out.println("定义了一个方法,需要传入A函数式接口。。。");
}
public static void main(String[] args)
{
Test test = new Test();
test.test((str, str1) -> str.equals(str1));
}
}
方法和构造器引用
如果lambda表达式的代码块只有一行代码,可以省略lambda表达式中代码块的花括号。不仅如此,如果lambda表达式的代码块只有一行代码,还可以在代码中使用方法引用和构造器引用。
方法引用和构造器引用可以让lambda表达式编码更加简洁,具体语法就是使用2个英文冒号,然后加上方法名字,注意没有括号的。一共有4种情况,类引用类方法,对象引用实例方法,类引用构造器都很好理解,注意的是这里还有一个类引用实例方法。
种类 | 示例 | 说明 | 对应的lambda表达式 |
---|---|---|---|
引用类方法 | 类名::类方法 | 函数式接口中被实现方法的全部参数传给该类方法作为参数 | (a,b,…)->类名.类方法(a,b,…) |
引用特定对象的实例方法 | 特定对象::实例方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b,…)->特定对象.实例方法(a,b,…) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部参数传给该类方法作为参数 | (a,b,…)->a.类方法(b,…) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,…)->new 类名(a,b,…) |
引用类方法
public class Test
{
public static void main(String[] args)
{
//原始的方法
//Converter converter = str -> Integer.parseInt(str);
//使用类方法引用
Converter converter = Integer::parseInt;
System.out.println(converter.convert("25"));
}
}
@FunctionalInterface
interface
{
//定义一个将字符串串成integer的方法
Integer convert(String str);
}
引用特定对象的实例方法
public class Test
{
public static void main(String[] args)
{
//原始的lambda表达式
//Converter converter = str -> "LinkinPark".indexOf(str);
//使用类方法引用
Converter converter = "LinkinPark"::indexOf;
System.out.println(converter.convert("kin"));
}
}
@FunctionalInterface
interface Converter
{
//定义一个将获取字符串下表的方法
Integer convert(String str);
}
引用类的实例方法
public class Test
{
public static void main(String[] args)
{
//原始的lambda表达式
// Converter converter = (str, index, end) -> str.substring(index, end);
//使用方法引用
Converter converter = String::substring;
System.out.println(converter.convert("LinkinPark", 0, 6));
}
}
@FunctionalInterface
interface Converter
{
/**
* @param str 原始的字符串
* @param index 开始截图的下标
* @param end 终止截图的下标
* @return 截取后的字符串
* @Description: 定义一个截取字符串的方法
*/
String convert(String str, int index, int end);
}
//通过使用super,可以引用方法的父类版本,语法如下:super::方法名字
构造器引用
public class Test
{
public static void main(String[] args)
{
//原始的lambda表达式
// Converter converter = str -> new String(str);
//使用构造器引用
Converter converter = String::new;
System.out.println(converter.convert("LinkinPark"));
}
}
@FunctionalInterface
interface Converter
{
//定义一个方法,处理一个字符串然后在返回一个字符串
String convert(String str);
}
lambda表达式与匿名内部类的联系与区别
相同点:
- 都可以直接访问”effectively final”的局部变量,以及外部类的成员变量。
- 创建的对象都可以直接调用从接口中继承的默认方法。
区别:
- lambda表达式只能为函数式接口创建实例。
- lambda表达式的代码块不允许直接调用接口中定义的默认方法,但是匿名内部类可以。
使用lambda表达式调用Arrays的类方法
import java.util.Arrays;
public class LambdaArrays {
public static void main(String[] args) {
String[] arr1 = {"java" , "lkjhj" , "uhnj" , "ios" , "android"};
Arrays.parallelSort(arr1, (o1, o2)->o1.length()-o2.length());
System.out.println(Arrays.toString(arr1));
int[] arr2 = new int[]{3,-4 , 25, 16, 30, 18};
//left代表数组中前一个索引处的元素,计算第一个元素时,left为1
//right代表数组中当前索引处的元素
Arrays.parallelPrefix(arr2, (left,right)->left*right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
//operand代表正在计算的元素索引
Arrays.parallelSetAll(arr3, operand -> operand * 5);
System.out.println(Arrays.toString(arr3));
}
}