java中lambda表达式的概念,写法及应用
本文最后更新于 2024-10-24,文章距离上次更新已超30天,内容可能有些老了——
本篇是自己在学习java,留存的一些整理x最近看了lambda表达式,所以写了一个记录。如有错误,欢迎指正..!
要理解lambda表达式,需要先清楚接口,匿名内部类的概念。
接口
[public/#] interface Name extends fathter_interface1,father_interface2
{
常量定义,
抽象方法定义,
内部类,接口,枚举定义
私有方法,默认方法,类定义方法
}
//与类不同,支持多个继承。但接口只能继承接口,而不能继承类。
接口不能用于创建实例(这点类似于抽象类),但可以用来声明引用类型的变量,该变量必须引用到该接口实现类的对象。
interface output
{
int MAX_CACHE_LINE = 50; //成员变量只能是常量。
void out(); //普通方法只能是public的抽象方法。
void getData(String msg);
default void print(String... msgs) //接口中定义默认方法,需要default修饰
{
for (var msg: msgs)
{
System.out.println(msg);
}
}
//如果有一个接口实现类,这个类创建的实例都可以直接使用这些默认方法
//而这接口实现类,必须将那些抽象方法都进行覆写。
}
接口和抽象类,都是位于最顶端的,不能用它们来生成一个实例
且继承了它们的子类,必须要实现所有的抽象方法。
但接口制定的应该是各个模块都需要遵循的标准,而抽象类是多个子类的共同父类(模板)
在用法上,接口中不能有普通方法,而抽象类可以。(抽象类中,以abstract来区分是否为抽象方法,而接口中所有普通方法都是抽象的,除非标注default,定义为默认方法)
接口中不能包含成员变量,只能有静态常量。并且接口没有构造器(抽象类中的构造器是给子类用于完成抽象父类的初始化的)
接口中也没有初始化块,而抽象类可以有。
一个类只能有一个直接父类,而一个类可以实现多个接口。(弥补单继承的问题)
匿名内部类
格式:
new 实现接口() | 父类构造器(实参列表)
{
//类体部分
}
interface Get //假定有一个接口。
{
int get();
}
public class Test //这个文件中最主要的类。
{
public void test(Get g) //定义这个类中的一个方法,要求传入的参数类型为刚刚的接口
{
System.out.println(g.get());
}
public static void main(String[] args)
{
var t = new Test();
t.test(new Get() //相当于,用这个接口生成了一个接口实现类
{
public int get(){ //然后需要再这个接口实现类中,再进行覆写。 最后就直接传入了这个类的对象。
return 1; //中途省略了对子类/接口实现类的命名。
}
})
}
}
lambda表达式
上述t.test()中的内容,可以改写为
t.test(() -> 1);
lambda 表达式,主要作用就是代替上述的匿名内部类的繁琐语法。可以将其视为对匿名内部类的一个替换就行。由它们生成的接口对象,都可以直接调用该接口的默认方法。
但是也存在一些区别:
匿名内部类中可以实现有多个抽象方法的接口创建实例,而lambda表达式只能实现一个抽象方法的。
匿名内部类也可以为抽象类/普通类创建实例,而lambda表达式只能为函数式接口创建实例。
匿名内部类的实现的抽象方法中,可以调用该接口中的默认方法,但是lambda表达式不可以调用。
lambda表达式的主要组成:
形参列表 (用括号括起来,如果只有一个形参,可以省略圆括号。并且形参列表运行省略形参的类型)
比如 (int a) 和 ( a ) 是等效的。
->
代码块 (用花括号括起来。如果只有一条语句,则可以省略花括号。如果只有一条return语句,可以省略return语句,lambda表达式会自动返回它的值)
lambda表达式的类型(target type) 必须是函数式接口( Functional interface) - - 只包含一个抽象方法的接口(但可以包含多个默认方法和类方法)
对比匿名内部类,可以发现lambda表达式返回的,就是这个类的实例(一个对象),所以可以写someInterface i = (xxx)->{xxx};
来表示这个接口对应的对象。返回类型必须是接口,否则得进行强制类型转化。
object o = (someInterface)(xxx)->{xxx};
同一句lambda表达式,可以被当成不同的类型(但这些类型都是接口!),只需要满足,lambda表达式中的形参列表,与这个接口中唯一的那个抽象方法的形参列表相同。
一些特殊情况:
//如果使用var 来定义变量 ->必须显示表示lambda表达式的返回类型
var r = (Get)()->{};
//如果要对形参加上注解,则需要写明形参的类型(可以利用var)
someInterface i = (@注解 var 参数) -> {};
方法引用和构造器引用
如果lambda表达式代码块中只有一条代码,不仅可以省略花括号,还可以用方法引用/构造器引用来简化。
@FunctionalInterface // 注解,下面的定义是一个函数式接口
interface Converter
{
Integer convert(String from); //接口中唯一一个抽象方法
}
Converter converter1 = from -> Integer.valueOf(from); //利用lambda表达式,构造一个实例对象。
//因为只有一条语句,所以这个语句同时会被作为return值返回。
//所以这个方法最后实现的效果就是,将字符串转化为整数
Integer val = converter1.convert("1"); //此时val的值就为1
//上述实例化一个对象,可以用引用类方法的方式,效果一样。 即,类名 实例名 = 抽象方法返回值的类名::该类的类方法;
Converter converter2 = Integer::valueOf;
//类似的。还有:
//2.引用特定对象的实例方法 :类名 实例名 = 特定对象::实例方法;
//举例:Converter converter3 = "fkit"::indexOf;
//实现在"fkit"这个字符串中,查询传入的from字符串位置。
//3.引用某类对象的实例方法: 类名 实例名 = 传入参数中的第一个参数的类名::该类对象的实例方法;
//举例: Test t1 = String::substring; // 假定有一个Test接口,唯一方法的返回类型是String,这里表明传入3个参数,第一个参数为String类,后面两个参数为substring这个方法的参数。
//4.引用构造器: 类名 实例名 = 类名::new
//最后一个自己初看的时候,感觉有点难以理解,所以这里在套用(lambda表达式是简化的匿名内部类)这个概念进行理解:
@FunctionalInterface
interface Test2
{
YourClass getnew(String s); //假定有一个接口,返回类型是某一个类,形参为字符串
}
Test2 t2 = a -> new YourClass(a); //lambda表达式的返回类型是一个接口。
YourClass y = t2.getnew("some String");
//这样,这个类就会以"some String" 作为其构造器的参数,生成一个新的类实例
//这句与上述倒数第二句效果相同
Test2 t2 = YourClass::new;
//上述代码中,Test2 t2这句,可以化为匿名内部类进行理解
Test2 t2 = new Test2(){
YourClass getnew(String a){
return new YourClass(a);
}
}
//即,这个接口中,唯一抽象方法它的返回类型是一个类名,那就可以利用它,来生成该类的构造器。
吐槽:不过个人认为,最后一种其实和第一种引用类方法相似,就是类名::类方法(如果把new当做一种特殊的类方法的话)
lambda表达式的应用
import java.util.Arrays;
public class LambdaArrays {
public static void main(String[] args) {
var arr1 = new String[] { "Java", "Python", "C++" };
Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
System.out.println(Arrays.toString(arr1));
//left是前一个元素,right是当前元素(第一个元素的left是1)
var arr2 = new int[] { 3, -4, 25, 16, 30, 18 };
Arrays.parallelPrefix(arr2, (left, right) -> left * right);
System.out.println(Arrays.toString(arr2));
var arr3 = new long[5];
//operand是数组元素的索引
Arrays.parallelSetAll(arr3, operand -> operand * 5);
System.out.println(Arrays.toString(arr3));
//arr1输出为[c++, Java, Python]
//arr2输出为[3, -12, -300, -4800, -144000, -2592000]
//arr3输出为[0, 5, 10, 15, 20]
}
}