本文最后更新于 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 表达式,主要作用就是代替上述的匿名内部类的繁琐语法。可以将其视为对匿名内部类的一个替换就行。由它们生成的接口对象,都可以直接调用该接口的默认方法。

但是也存在一些区别:

  1. 匿名内部类中可以实现有多个抽象方法的接口创建实例,而lambda表达式只能实现一个抽象方法的。

  2. 匿名内部类也可以为抽象类/普通类创建实例,而lambda表达式只能为函数式接口创建实例。

  3. 匿名内部类的实现的抽象方法中,可以调用该接口中的默认方法,但是lambda表达式不可以调用。

lambda表达式的主要组成:

  1. 形参列表 (用括号括起来,如果只有一个形参,可以省略圆括号。并且形参列表运行省略形参的类型)

    比如 (int a) 和 ( a ) 是等效的。

  2. ->

  3. 代码块 (用花括号括起来。如果只有一条语句,则可以省略花括号。如果只有一条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]
     }
     
 }