Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口叫函数式接口)的实例
public interface Command
{// 接口里定义的process()方法用于封装“处理行为”void process(int[] origin);
}
public class ProcessArray
{public void process(int[] target, Command cmd){cmd.process(target);}
}
public class CommandTest
{public static void main(String[] args){var pa = new ProcessArray();int[] target = {3, -4, 6, 4};// 处理数组,具体处理行为取决于匿名内部类pa.process(target, new Command(){*public void process(int[] origin){int sum = 0;for (int tmp : origin){sum += tmp;}System.out.println("数组元素的总和是:" + sum);}*});}
}
ProcessArray类的process()方法处理数组时,希望传入一段代码作为具体的处理行为,从代码中可以看到,用于封装处理行为的关键就是代码中的粗体部分,但为了向process()方法传入这段粗体代码段,程序使用了匿名内部类的语法来创建对象
当使用Lambda表达式代替匿名内部类创建对象时,Lambda表达式的代码块将会代替实现抽象方法的方法体,Lambda表达式就相当于一个匿名方法,并且可以简化创建匿名内部类对象。
public class CommandTest2
{public static void main(String[] args){var pa = new ProcessArray();int[] array = {3, -4, 6, 4};// 处理数组,具体处理行为取决于匿名内部类pa.process(array, (int[] target)->{int sum = 0;for (int tmp : target){sum += tmp;}System.out.println("数组元素的平方是:" + sum);});}
}
public interface Command
{// 接口里定义的process()方法用于封装“处理行为”void process(int element);
}
public class ProcessArray
{public void process(int[] target, Command cmd){for (var t : target){cmd.process(t);}}
}
public class CommandTest
{public static void main(String[] args){var pa = new ProcessArray();int[] target = {3, -4, 6, 4};// 处理数组,具体处理行为取决于匿名内部类pa.process(target, new Command(){public void process(int element){System.out.println("数组元素的平方是:" + element * element);}});}
}
public class CommandTest2
{public static void main(String[] args){var pa = new ProcessArray();int[] array = {3, -4, 6, 4};// 处理数组,具体处理行为取决于匿名内部类pa.process(array, (int element)->{System.out.println("数组元素的平方是:" + element * element);});}
}
比较两段代码的不同,第二种实现不需要new Xxx(){}这种繁琐的代码,不需要指出重写的方法名字,不需要给出重写的方法的返回值类型,只要给出重写的方法括号以及括号里的形参列表即可
interface Eatable
{void taste();
}
interface Flyable
{void fly(String weather);
}
interface Addable
{int add(int a, int b);
}
public class LambdaQs
{// 调用该方法需要Eatable对象public void eat(Eatable e){System.out.println(e);e.taste();}// 调用该方法需要Flyable对象public void drive(Flyable f){System.out.println("我正在驾驶:" + f);f.fly("我醉欲眠卿且去");}// 调用该方法需要Addable对象public void test(Addable add){System.out.println("5与3的和为:" + add.add(5, 3));}public static void main(String[] args){var lq = new LambdaQs();// Lambda表达式的代码块只有一条语句,可以省略花括号。lq.eat(() -> System.out.println("苹果的味道不错!"));// Lambda表达式的形参列表只有一个形参,省略圆括号lq.drive(weather -> {System.out.println("今天天气是:" + weather);System.out.println("直升机飞行平稳");});// Lambda表达式的代码块只有一条语句,省略花括号// 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字。lq.test((a, b) -> a + b);}
}
代码lq.eat(*()** -> System.out.println("苹果的味道不错!"**)*);
调用eat()方法,调用该方法需要一个Eatable类型的参数,但实际传入的是Lambda表达式
代码lq.drive(*weather -> { System.out.println("今天天气是:" + weather); System.out.println("直升机飞行平稳"); **}*);
调用drive()方法,调用该方法需要一个Flyable类型的参数,但实际传入的是Lambda表达式
代码
这说明Lambda表达式实际上将会被当成“任意类型”的对象,到底当成什么类型的对象,则取决于运行环境的需要
Lambda表达式的“类型”,也被称为“目标类型(target type)”,Lambda表达式的目标类型必须是“函数式接口”,函数式接口代表只包含一个抽象方法的接口,它可以包含多个默认方法、类方法但只能声明一个抽象方法。
如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用Lambda表达式来创建对象,该表达式创建出来的对象的目标类型就是这个函数式接口。
Java8专门提供了函数式接口的注解@FunctionalInterface
,该注解方法接口定义前面,虽然它对程序功能没有任何作用,但可以告诉编译器严格检查它标注的接口必须是函数式接口,否则报异常
由于Lambda表达式的结果就是被当成对象,因此程序中完全可以使用Lambda表达式进行赋值
@FunctionalInterface
interface FkTest
{void run();
}public class LambdaTest
{public static void main(String[] args){// Runnable接口是Java本身提供的一个函数式接口,它只包含一个无参数的方法// Lambda表达式代表的匿名方法实现了Runnable接口中唯一的、无参数的方法// 因此下面的Lambda表达式创建了一个Runnable对象Runnable r = () -> {for (var i = 0; i < 100; i++){System.out.println(i);}};/* 下面代码报错: 不兼容的类型: Object不是函数接口Object obj = () -> {for (var i = 0; i < 100; i++){System.out.println(i);}};*/// 对Lambda表达式执行强制类型转换,这样可以确定该表达式的目标类型为Runnable函数式接口Object obj1 = (Runnable)() -> {for (var i = 0; i < 100; i++){System.out.println(i);}};// 同样的Lambda表达式可以被当成不同的目标类型,唯一的要求是:// Lambda表达式的形参列表与函数式接口中唯一的抽象方法的形参列表相同// 前面强制转型为Runnable的Lambda表达式也可强转为FkTest类型,因为FkTest接口中的// 唯一抽象方法是不带参数的,而该Lambda表达式也是不带参数的Object obj2 = (FkTest)() -> {for (var i = 0; i < 100; i++){System.out.println();}};}
}
为了保证Lambda表达式的目标类型是一个明确的函数式接口,有三种方式:
Java8在java.util.function包下预定义了大量函数式接口,例如XxxFunction、XxxConsumer、XxxxPredicate、XxxSupplier等等
如果Lambda表达式的代码块只有一条代码,程序就可以省略Lambda表达式中代码块的花括号,不仅如此,如果Lambda表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用,共支持4中引用
种类 | 示例 | 说明 | 对应的lambda表达式 |
---|---|---|---|
引用类方法 | 类名 ::类方法 | 函数式接口中被实现方法的全部参数传给该类方法作为参数 | (a,b…) -> 类名.类方法(a,b,…) |
引用特定对象的实例方法 | 特定对象::实例方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b…) -> 特定对象.实例方法(a,b,…) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,c,…) -> a.实例方法(b,c,…) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,…) -> new 类名(a,b,…) |
@FunctionalInterface
interface Converter{Integer convert(String from);
}
public class MethodRefer
{public static void main(String[] args){// 下面代码使用Lambda表达式创建Converter对象// Lambda表达式的代码块只有一条语句,因此省略了花括号// 由于表达式所实现的convert()方法需要返回值,因此Lambda表达式将会把这条语句的结果作为返回值Converter converter1 = from -> Integer.valueOf(from);// 方法引用代替Lambda表达式:引用类方法// 函数式接口中被实现方法的全部参数传给该类方法作为参数Converter converter1 = Integer::valueOf;Integer val = vert("99");System.out.println(val); // 输出整数99/* Lambda表达式的代码块只有一行调用类方法的代码,将其替换成了方法引用* 类方法引用,也就是调用Integer类的valueOf()类方法来实现Converter函数式接口中唯一的抽象方法* 当调用Converter接口中唯一的抽象方法时,调用参数将会传给Interger类的valueOf()类方法*/}
}
@FunctionalInterface
interface Converter{Integer convert(String from);
}
public class MethodRefer
{public static void main(String[] args){// 下面代码使用Lambda表达式创建Converter对象Converter converter2 = from -> "fkit".indexOf(from);// 方法引用代替Lambda表达式:引用特定对象的实例方法。// 函数式接口中被实现方法的全部参数传给该方法作为参数。Converter converter2 = "fkit"::indexOf;Integer value = vert("it");System.out.println(value); // 输出2}
}
@FunctionalInterface
interface MyTest
{String test(String a, int b, int c);
}public class MethodRefer
{public static void main(String[] args){// 下面代码使用Lambda表达式创建MyTest对象MyTest mt = (a, b, c) -> a.substring(b, c);// 方法引用代替Lambda表达式:引用某类对象的实例方法。// 函数式接口中被实现方法的第一个参数作为调用者,// 后面的参数全部传给该方法作为参数。MyTest mt = String::substring;String str = mt.test("Java I Love you", 2, 9);System.out.println(str); // 输出:va I Lo}
}
@FunctionalInterface
interface YourTest
{JFrame win(String title);
}
public class MethodRefer
{public static void main(String[] args){// 下面代码使用Lambda表达式创建YourTest对象YourTest yt = a -> new JFrame(a);// 构造器引用代替Lambda表达式。// 函数式接口中被实现方法的全部参数传给该构造器作为参数。YourTest yt = JFrame::new;JFrame jf = yt.win("我的窗口");System.out.println(jf);}
}
Lambda表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用,Lambda表达式与匿名内部类的共同点:
@FunctionalInterface
interface Displayable
{// 定义一个抽象方法和默认方法void display();default int add(int a, int b){return a + b;}
}
public class LambdaAndInner
{private int age = 12;private static String name = "我欲乘风归去";public void test(){var book = "又恐琼楼玉宇";Displayable dis = ()->{// 访问“effectively final”的局部变量System.out.println("book局部变量为:" + book);// 访问外部类的实例变量和类变量System.out.println("外部类的age实例变量为:" + age);System.out.println("外部类的name类变量为:" + name);// 尝试调用接口中的默认方法,编译器会报错// System.out.println(add(3, 5));};dis.display();// 调用dis对象从接口中继承的add()方法System.out.println(dis.add(3, 5)); }public static void main(String[] args){var lambda = new LambdaAndInner();st();}
}
System.out.println("book局部变量为:" + book); System.out.println("外部类的age实例变量为:" + age); System.out.println("外部类的name类变量为:" + name);
代码分别示范了访问“effectively final”的局部变量、外部类的实例变量和类变量System.out.println(dis.add(3, 5));
表明,Lambda表达式创建了Displayable的对象后,该对象不仅可以调用接口中唯一的抽象方法,也可以调用接口中的默认方法import java.util.Arrays;
public class LambdaArrays
{public static void main(String[] args){var arr1 = new String[] {"java", "fkava", "fkit", "ios", "android"};Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());System.out.String(arr1));var arr2 = new int[] {3, -4, 25, 16, 30, 18};// left代表数组中前一个所索引处的元素,计算第一个元素时,left为1// right代表数组中当前索引处的元素Arrays.parallelPrefix(arr2, (left, right)-> left * right);System.out.String(arr2));var arr3 = new long[5];// operand代表正在计算的元素索引Arrays.parallelSetAll(arr3, operand -> operand * 5);System.out.String(arr3));}
}
Lambda表达式(o1, o2) -> o1.length() - o2.length()
的目标类型是Comparator, 该函数式接口指定了判断字符串大小的标准,字符串越长,即认为该字符串越大
Lambda表达式(left, right)-> left * right
的目标类型是IntBinaryOperator,该函数式接口根据前后两个元素来计算当前元素的值
Lambda表达式operand -> operand * 5
的目标类型是IntToLongFunction,该函数式接口根据元素的索引来计算当前元素的值
执行结果如下:
[ios, java, fkit, fkava, android]
[3, -12, -300, -4800, -144000, -2592000]
[0, 5, 10, 15, 20]
本文发布于:2024-02-04 23:26:29,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170718798060724.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |