Lambda表达式

为了支持函数式编程,Java 8引入了Lambda表达式,那么在Java 8中到底是如何实现Lambda表达式的呢? 为什么使用Lambda表达式呢?Lambda表达式经过编译之后,到底会生成什么东西呢?

定义

Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方
法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码。

Lambda表达式语法

Lambda表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称 为 Lambda 操作符或剪头操作符。它将 Lambda 分为 两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。

(1)语法格式一:无参,无返回值,Lambda 体只需一条语句
示例:Runnable r1 = () -> System.out.println("Hello Lambda!");
(2)语法格式二:Lambda 需要一个参数
示例:Consumer<String> con = (x) -> System.out.println(x);
(3)语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略
示例:Consumer<String> con = x -> System.out.println(x);
(4)语法格式四:Lambda 需要两个参数,并且有返回值
示例:

 Comparator<Integer> com = (x, y) -> {
   System.out.println("函数式接口");
   return Integer.compare(x, y);
  };

(5)语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略
示例:Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
(6)Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
示例:

Comparator<Integer> com = (Integer x,Integer y) -> {  //Integer 类型可以省略
   System.out.println("函数式接口");
   return Integer.compare(x, y);
  };

类型推断:Lambda 表达式中的参数类型都是由编译器推断 得出的。 Lambda 表达式中无需指定类型,程序依然可 以编译,这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型。 Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”

**

什么情况下使用Lambda表达式和如何使用 Lambda?

函数式接口: 函数式接口就是只定义一个抽象方法的接口
下面三个接口都是函数式接口:

@FunctionalInterface
public interface Comparator<T> {
	int compare(T o1, T o2);
}

@FunctionalInterface
public interface Callable<V>{
	V call();
}

public interface PrivilegedAction<T> {
	T run();
}

用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体的实例)。

案例:

public static void main(String[] args)
    {
        Integer[] datas= {1,4,3,2};
        //匿名内部类写法
        Comparator<Integer> comp1 = new Comparator<Integer>()
        {
            @Override
            public int compare(Integer o1, Integer o2)
            {
                return o1 - o2;
            }
        };
        //lambda表达式写法
        Comparator<Integer> comp2 = (o1,o2) -> o1 - o2;
        //方法引用的写法
        Comparator<Integer> comp3 = Integer::compareTo;
        //排序操作
        Arrays.sort(datas,comp1);//comp1,comp2,comp3参数都可以
        System.out.println(Arrays.toString(datas));//[1, 2, 3, 4]
    }

分析上面的案例:
comp1对象是没有Lambda表达式之前的时候创建一个Comparator对象的方式,然后把comp1对象传递给Arrays.sort()方法,很显然的创建;
comp2对象是用了Lambda表达式来创建对象,参数传递的Lambda表达式(用Lambda前提是参数是一个函数式接口哦,详细参考java8------函数式接口),其实内部是调用了该类内部私有的静态方法,后面会分析;
comp3对象是利用的方法引用来实现,详细参考java8-----方法引用

Lambda表达式的存在在简化了匿名内部类的写法(功能是相同的,并且性能比匿名内部类的方式更好),前提是该参数类是函数式接口,有了Lambda表达式的存在代码书写更加简洁,可读性好。而方法引用存在更加方便了lambda表达式的书写。

lambda表达式的原理分析:

参考:https://blog.csdn.net/f641385712/article/details/81486280

为了支持函数式编程,Java 8引入了Lambda表达式,那么在Java 8中到底是如何实现Lambda表达式的呢? Lambda表达式经过编译之后,到底会生成什么东西呢?

Java 8中每一个Lambda表达式必须有一个函数式接口与之对应。

或许我们可以推测:Lambda表达式是不是转化成与之对应的函数式接口的一个实现类呢,然后通过多态的方式调用子类的实现呢,如下面代码是一个Lambda表达式的样例:

@FunctionalInterface
interface Print<T> {
    public void print(T x);
}

public class Lambda {   
    //待测试的静态方法
    public static void printString(String s, Print<String> print) {
        print.print(s);
    }
    public static void main(String[] args) {
        PrintString("test", (x) -> System.out.println(x));
    }
}

如果根据我们的推测,Java编译器编译后,肯定会出现一个类似Lambda$$0这样的类或者内部类,从而肯定存在一个.class文件。因为假如符合我们的预想,其实结果可以达到一模一样?
但是Java8中的lambda真是这么实现的吗?

为了探究Lambda表达式是如何实现的,就得需要研究Lambda表过式最终转化成的字节码文件,这就需要jdk的bin目录下的一个字节码查看工具及反编译工具

javap -p Lambda.class (命令中的-p表示输出所有类及成员)

运行上面的命令后,得的结果如下所示:

public class Lambda {

  public Lambda();
  
  public static void printString(java.lang.String, Print<java.lang.String>);
  public static void main(java.lang.String[]);
  
  //显然,这个方法是编译后多出的
  private static void lambda$0(java.lang.String);
}

由上面的代码可以看出编译器会根据Lambda表达式生成一个私有的静态方法(这点非常重要,就是为什么lambda表达式比匿名内部类效率高的直接原因),注意,在这里说的是生成,而不是等价

private static void lambda$0(java.lang.String);

验证一下
为了验证上面的转化是否正确? 我们在代码中定义一个lambda$0这个方法:

 public static void PrintString(String s, Print<String> print) {
     print.print(s);
 }
 
 private static void lambda$0(String s) {
 }
 
 public static void main(String[] args) {
     PrintString("test", (x) -> System.out.println(x));
 }

如上,我们代码在编译期间不抱错。但是,但是在运行期间就报错了。

Exception in thread "main" java.lang.ClassFormatError: Duplicate method name&signature in class file Lambda
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

很明显,报错的额原因是因为存在两个lambda$0函数。

通过javap对上述错误代码进行反编译,反编译之后输出的类的成员如下所示:

public class Lambda {
  public Lambda();
  public static void PrintString(java.lang.String, Print<java.lang.String>);
  private static void lambda$0(java.lang.String);
  public static void main(java.lang.String[]);
  private static void lambda$0(java.lang.String);
}

竟然发现lambda$0出现了两次,那么在代码运行的时候,就不知道去调用哪个,因此就会抛错。
提一句:
java底层处理lambda,其实非常的强大和巧妙。增加了不少类来处理lambda表达式。这里最重要的一个是LambdaMetafactory
里面有个方法:

public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName,
                               	MethodType invokedType, MethodType samMethodType,
                               	MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException {
                               
    AbstractValidatingLambdaMetafactory mf;
    mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType,
                                         implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
    mf.validateMetafactoryArgs();
    return mf.buildCallSite();
}

所有的lambda在运行时会进入这个函数。所以我们在这个方法里面打断点,来获取更多的信息。

结论
Lambda表达式在Java 8中首先会生成一个私有的静态函数,这个私有的静态函数干的就是Lambda表达式里面的内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值