java8特性探讨-Lambda表达式
前言:java8非常重要的版本升级,引入了一些非常有趣的特性,下面我将一点一点的抽丝剥茧进行探讨,先从基本的Lambda表达式开始
Lambda表达式
Lambda表达式本身在代码中是非常简单的,所以关于Lambda表达式的探讨其实本质就是以Lambda表达式为开始,进行后续的探讨。
- Lambda语法 ;
- 思想的改变;
- Lambda表达式的使用
- 深入理解Lambda表达式和匿名内部类的区别;
- 面向函数式编程
Lambda表达式的语法
Lambda表达式的语法是非常简单的:
1.基本语法:函数接口引用=(形参列表)->{执行的代码块或者表达式}
main:
public static void main(String[] args) {
grammar();
}
method:
//内部接口
@FunctionalInterface
private interface Ttest{
void functiontest();
}
@FunctionalInterface
private interface Test2{
String function(String str);
}
public static void grammar() {
/**lambda表达式简洁版,省略形参列表和代码块 */
Ttest test=()->System.out.println("简洁版");
test.functiontest();
/** 含有返回值的和参数列表的Lambda表达式*/
Test2 test2=(str)->str+=" doing";//没有{}也就没有return
Test2 test22=(str)->{return str;};
Test2 test23=(String str)->{str=str+" doing"; return str;};//指定参数并且有执行代码块-完整的Lambda表达式
System.out.println(test2.function("test2")+"; "+test22.function("test22")+"; "+test23.function("test23"));
/**表达中不加{}的Lambda表达式,->右边只能是Expression,同时返回可以省略return关键字;
* 加了{}就可以是执行代码块了,无法省略return关键字
* 对于形参列表省略参数类型,是因为java有类型推断机制*/
}
running:
简洁版
test2 doing; test22; test23 doing
思想的改变
(以下都是个人观点,欢迎改正)
个人认为想要理解Lambda表达式,首先就是要知道java引入Lambda表达式的作用是什么。
1.引入函数式编程思想: java的编程思想是面向对象编程,而引入Lambda表达式其实是引入了面向函数式编程的思想,所谓的面向函数式编程其实就是让函数式代替实例对象,然后使用函数接口引用进行引用。我们只需创建,或者引用“函数”来代替函数接口对象就可以了。简单地说:就是让函数成为形参。
- 什么叫“函数接口”:其实函数接口也很简单,就是在接口中只有一个抽象方法的接口就是函数接口。函数接口可以使用@FunctionalInterface注解进行注解,不使用@FunctionalInterface进行注解为隐式的函数接口。
@FunctionalInterface
private interface Ttest{
void functiontest();
}
private interface Test2{
String function(String str);
}
2.让代码看起来更加简洁,提生性能
Lambda表达式的使用
在很多博客里都能看到“Lambda表达式的作用是取代一些匿名内部类的”,其实我认为这仅仅是lambda的部分作用,其实更主要的是引入面向函数式编程,让函数成为形参,下面让我们在代码中去看Lambda表达式的使用
- 取代匿名内部类—创建编写函数式
public static void main(String[] args) {
LanbdaUse();
}
public static void LanbdaUse() {
/** 使用Lanbda表达式代替匿名内部类和让函数式成为形参-------创建编写函数式**/
ArrayList<Integer>list=new ArrayList<>();
Random random=new Random();
for(int i=0;i<20;i++) {
list.add(random.nextInt(666));
};
/** 匿名内部类形式*/
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
System.out.println("匿名内部类: "+list);
/** Lambda表达式形式*/
list.sort((Integer a,Integer b)->{return b-a;});
System.out.println("Lambda表达式: "+list);
}
匿名内部类: [15, 16, 27, 32, 72, 285, 290, 339, 365, 374, 438, 482, 556, 558, 583, 593, 606, 628, 649, 661]
Lambda表达式: [661, 649, 628, 606, 593, 583, 558, 556, 482, 438, 374, 365, 339, 290, 285, 72, 32, 27, 16, 15]
注意:Comparator接口为函数接口,所以才能使用lambda表达式来代替匿名内部类,如果所要表达的对象不是函数接口,就无法使
用lambda表达式来表示
-
引用函数式
资源类 private static class MySort implements Comparator<Integer>{ @Override public int compare(Integer o1, Integer o2) { return o1-o2;//正序 } public static int staCompare(Integer o1,Integer o2) { return o2-o1;//反序 } }
1.引用对象实例函数 public static void LanbdaUse() { ArrayList<Integer>alist=new ArrayList<>(); for(int i=0;i<20;i++) { alist.add(random.nextInt(666)); }; MySort mySort=new MySort(); alist.sort(mySort::compare); System.out.println("对象实例变量: "+alist); } out:对象实例变量: [66, 97, 120, 147, 180, 274, 286, 353, 397, 401, 408, 416, 486, 500, 517, 561, 576, 619, 658, 659]
2.引用类静态函数 public static void LanbdaUse() { ArrayList<Integer>alist=new ArrayList<>(); for(int i=0;i<20;i++) { alist.add(random.nextInt(666)); }; alist.sort(MySort::staCompare); System.out.println("对象静态方法: "+alist); } out:对象静态方法: [639, 621, 560, 542, 525, 472, 431, 378, 341, 334, 224, 203, 188, 165, 126, 124, 107, 99, 57, 4]
3.引用类的构造器 private interface GenertorMySort{ MySort get(); } public static void LanbdaUse() { GenertorMySort gMySort=MySort::new;//引用构造器 MySort mySort=new MySort();//创建对象 System.out.println(mySort);//原来对象 mySort=gMySort.get(); System.out.println(mySort);//新建对象 } out:oldObject: CSDN.LambdaFirst$MySort@4c873330 newObect:CSDN.LambdaFirst$MySort@119d7047//对象改变
4.引用类的普通方法 private interface ClassTestI{ public void interfaceTest(ClassTest classTest,int a,int b); } private static class ClassTest{ private int id; public ClassTest(int id) { this.id=id; } public ClassTest() { } public void test(int a,int b) { System.out.println(a+b); //隐藏域this的探讨 System.out.println(this.getClass().getName()+" id "+this.id); } } public static void LanbdaUse() { ClassTestI classTestI=ClassTest::test;//注意这里方法形参是不一样的 ClassTest classTest=new ClassTest(1); classTestI.interfaceTest(classTest, 3, 5); } out:8 CSDN.LambdaFirst$ClassTest id 1 在类的普通方法中,接口函数引用必须为ClassTest 类的2个参数的方法。
看到这个this隐藏域你是否有思维的火花呢?在编写创建函数式中是否有隐藏的this域呢,如果有那this指谁?如果没有那java仅仅是创立了一个没有this域的函数式对象。在引用实例方法里this是否指的是我们传递进去的对象?和反射方法的调用一样?不妨自己去探索探索,加深理解。
-
函数引用探讨
前面的方法引用中基本上我恶魔你都保持函数接口和引用方法的形参列表相同,但是在引用类的普通方法我们并没有保持形参列表的相同,反而是像反射中方法调用一样,传入实例对象。try { ClassTest classTest=new ClassTest(); Method method=ClassTest.class.getDeclaredMethod("test", int.class,int.class); method.invoke(classTest, 3,5);//这样形参列表又相同了 } catch (Exception e) { e.printStackTrace(); } out:8 CSDN.LambdaFirst$ClassTest id 1
那是否函数式对象必须和函数接口(引用)保持相同的形参列表呢?我认为应该是相同的。
深入理解Lambda表达式和匿名内部类的区别
前面我们可以看到Lambda表达式确实是可以代替匿名内部类的,看起来也比匿名内部类更加简洁,那么除了代码更加简洁以外是否还有其它优点呢?
- 从文件和字节码的角度来看:使用内部类其实其实是会生成.class文件的。他是和我们创立Main类属于同一级别(关于更多内部类的知识可以看看javathink里的内部类章节),而使用Lambda表达式是不会创建.class文件的,他是直接创建对象的,第一次运行时动态创建相应的函数对象。
1.内部类生成的.class文件
2.内部类和Lanbda表示是的字节码,一个是调用构造器创建对象,一个是使用invokedynamic动态创建
- 从java虚拟机和classloader角度来说
一个calss文件被加载到jvm中是要经过几个步骤的,首先是classloader的双亲委托机制,然后是class文件加载过程等等(感兴趣的同学可以看看深入理解java虚拟机这本书,这里就点一下),既然内部类需要创建.class文件并且.class文件需要进行加载,而Lambda表示式直接创建对象,比匿名内部类更加快捷,所以当可以使用Lambda表示代替内部类时,还是尽量的使用Lambda表示式会好点。
面向函数式编程
当你看到这里时,也就是Lambda的解析结尾了,不过这篇文章是正餐前的开胃小菜,由Lambda表示式引导出面向函数式编程的思想,下一篇文章将探讨Stream API的使用及其流水线实现原理和源码的介绍。