|
| 1 | +JDK8--Lambda表达式 |
| 2 | +=== |
| 3 | +## 1.什么是Lambda表达式 |
| 4 | +**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递** |
| 5 | + |
| 6 | +## 2.Lambda表达式语法 |
| 7 | +```(参数列表) ->{执行代码块}``` |
| 8 | +<br>参数列表可以为空```()->{}``` |
| 9 | +<br>可以加类型声明比如```(String para1, int para2) ->{return para1 + para2}```我们可以看到,lambda同样可以有返回值. |
| 10 | +<br>在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) ->{return para1 + para2}``` |
| 11 | +<br>(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言) |
| 12 | + |
| 13 | +## 3.函数式接口 |
| 14 | +在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```<br> |
| 15 | +**函数式接口指的是有且只有一个抽象(abstract)方法的接口**<br> |
| 16 | +当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子: |
| 17 | +<br> |
| 18 | +```java |
| 19 | +Thread thread =newThread(() ->{ |
| 20 | +System.out.println("This is JDK8's Lambda!"); |
| 21 | + }); |
| 22 | +``` |
| 23 | +这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的? |
| 24 | +```java |
| 25 | +public Thread(Runnable target){...} |
| 26 | + |
| 27 | +/** |
| 28 | + * Runnable Interface |
| 29 | +*/ |
| 30 | +@FunctionalInterface |
| 31 | +publicinterfaceRunnable{ |
| 32 | +publicabstractvoidrun(); |
| 33 | +} |
| 34 | +``` |
| 35 | +到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于 |
| 36 | +```java |
| 37 | +Thread thread =newThread(newRunnable(){ |
| 38 | +@Override |
| 39 | +publicvoidrun(){ |
| 40 | +System.out.println("Anonymous class"); |
| 41 | + } |
| 42 | + }); |
| 43 | +``` |
| 44 | +也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都 |
| 45 | +由编译器帮我们推断,因此可以减少很多代码量)。 |
| 46 | +<br>Lambda也可以这样用 : |
| 47 | +```java |
| 48 | +Runnable runnable = () ->{...}; |
| 49 | +``` |
| 50 | +其实这和上面的用法没有什么本质上的区别。 |
| 51 | +<br>至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范, |
| 52 | +目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了, |
| 53 | +因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]() |
| 54 | +<br> |
| 55 | +除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口 |
| 56 | +比如(当然不止这些): |
| 57 | +-```java.util.concurrent.Callable``` |
| 58 | +-```java.util.Comparator``` |
| 59 | +-```java.io.FileFilter``` |
| 60 | +<br>**关于JDK中的预定义的函数式接口** |
| 61 | + |
| 62 | +- JDK在```java.util.function```下预定义了很多函数式接口 |
| 63 | +-```Function<T, R>{R apply(T t)}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。 |
| 64 | +-```Consumer<T>{void accept(T t)}``` 消费者 接受一个T对象,没有返回值。 |
| 65 | +-```Predicate<T>{boolean test(T t)}``` 判断,接受一个T对象,返回一个布尔值。 |
| 66 | +-```Supplier<T>{T get()} 提供者(工厂)``` 返回一个T对象。 |
| 67 | +- 其他的跟上面的相似,大家可以看一下function包下的具体接口。 |
| 68 | +## 4.变量作用域 |
| 69 | +```java |
| 70 | +publicclassVaraibleHide{ |
| 71 | +@FunctionalInterface |
| 72 | +interfaceIInner{ |
| 73 | +voidprintInt(intx); |
| 74 | + } |
| 75 | +publicstaticvoidmain(String[] args){ |
| 76 | +int x =20; |
| 77 | +IInner inner =newIInner(){ |
| 78 | +int x =10; |
| 79 | +@Override |
| 80 | +publicvoidprintInt(intx){ |
| 81 | +System.out.println(x); |
| 82 | + } |
| 83 | + }; |
| 84 | + inner.printInt(30); |
| 85 | + |
| 86 | + inner = (s) ->{ |
| 87 | +//Variable used in lambda expression should be final or effectively final |
| 88 | +//!int x = 10; |
| 89 | +//!x= 50; error |
| 90 | +System.out.print(x); |
| 91 | + }; |
| 92 | + inner.printInt(30); |
| 93 | + } |
| 94 | +} |
| 95 | +输出 : |
| 96 | +30 |
| 97 | +20 |
| 98 | +``` |
| 99 | +对于lambda表达式```java inner = (s) ->{System.out.print(x)};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。 |
| 100 | +<br>lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值, |
| 101 | +同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该 |
| 102 | +不难理解。 |
| 103 | +## 5.方法引用 |
| 104 | +**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。** |
| 105 | +<br>下面的例子会打印list中的每个元素: |
| 106 | +```java |
| 107 | +List<Integer> list =newArrayList<>(); |
| 108 | +for (int i =0; i <10; ++i){ |
| 109 | + list.add(i); |
| 110 | + } |
| 111 | + list.forEach(System.out::println); |
| 112 | +``` |
| 113 | +其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para)}``` |
| 114 | +<br>我们看一下List#forEach方法 ```default void forEach(Consumer<? super T> action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口 |
| 115 | +```java |
| 116 | +@FunctionalInterface |
| 117 | +publicinterfaceConsumer<T>{ |
| 118 | +voidaccept(Tt); |
| 119 | +``` |
| 120 | +大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。 |
| 121 | +<br>我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用 |
| 122 | +```java |
| 123 | +publicclassMethodReference{ |
| 124 | +publicstaticvoidmain(String[] args){ |
| 125 | +List<Integer> list =newArrayList<>(); |
| 126 | +for (int i =0; i <10; ++i){ |
| 127 | + list.add(i); |
| 128 | + } |
| 129 | + list.forEach(MethodReference::myPrint); |
| 130 | + } |
| 131 | + |
| 132 | +staticvoidmyPrint(inti){ |
| 133 | +System.out.print(i +", "); |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +输出:0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
| 138 | +``` |
| 139 | +可以看到,我们自己定义的方法也可以当做方法引用。 |
| 140 | +<br>到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。 |
| 141 | +- 方法引用 |
| 142 | +- 类名::静态方法名 |
| 143 | +- 类名::实例方法名 |
| 144 | +- 类名::new (构造方法引用) |
| 145 | +- 实例名::实例方法名 |
| 146 | +可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种 |
| 147 | +方法引用的使用方法。<br> |
| 148 | +**类名::实例方法名**<br> |
| 149 | +先来看一段代码 |
| 150 | +```java |
| 151 | +String[] strings =newString[10]; |
| 152 | + Arrays.sort(strings, String::compareToIgnoreCase); |
| 153 | +``` |
| 154 | +**上面的String::compareToIgnoreCase等价于(x, y) ->{return x.compareToIgnoreCase(y)}**<br> |
| 155 | +我们看一下`Arrays#sort`方法`public static <T> voidsort(T[] a, Comparator<? super T>c)`, |
| 156 | +可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`intcompare(To1, To2);`,再看一下 |
| 157 | +`String#compareToIgnoreCase`方法,`public intcompareToIgnoreCase(Stringstr)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它 |
| 158 | +的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样? |
| 159 | +<br>是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是 |
| 160 | +instance.method(x)中的instance, |
| 161 | +它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(To1, To2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的 |
| 162 | +参数。我们再举一个自己实现的例子: |
| 163 | +```java |
| 164 | +public class MethodReference{ |
| 165 | +staticRandom random =newRandom(47); |
| 166 | +publicstaticvoid main(String[] args){ |
| 167 | +MethodReference[] methodReferences =newMethodReference[10]; |
| 168 | +Arrays.sort(methodReferences, MethodReference::myCompare); |
| 169 | + } |
| 170 | +int myCompare(MethodReference o){ |
| 171 | +return random.nextInt(2) -1; |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | +上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。 |
| 176 | +<br>**构造器引用**<br> |
| 177 | +构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口 |
| 178 | +```java |
| 179 | +//compile error : String is not a functional interface |
| 180 | +String str =String::new; |
| 181 | +``` |
| 182 | +下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。 |
| 183 | +```java |
| 184 | +interfaceIFunctional<T>{ |
| 185 | +Tfunc(); |
| 186 | +} |
| 187 | + |
| 188 | +publicclassConstructorReference{ |
| 189 | + |
| 190 | +publicConstructorReference(){ |
| 191 | + } |
| 192 | + |
| 193 | +publicstaticvoidmain(String[] args){ |
| 194 | +Supplier<ConstructorReference> supplier0 = () ->newConstructorReference(); |
| 195 | +Supplier<ConstructorReference> supplier1 =ConstructorReference::new; |
| 196 | +IFunctional<ConstructorReference> functional = () ->newConstructorReference(); |
| 197 | +IFunctional<ConstructorReference> functional1 =ConstructorReference::new; |
| 198 | + } |
| 199 | +} |
| 200 | +``` |
| 201 | +下面是一个JDK官方的例子 |
| 202 | +```java |
| 203 | +publicstatic<T, SOURCE extends Collection<T>, DEST extends Collection<T>> |
| 204 | +DEST transferElements( |
| 205 | +SOURCE sourceCollection, |
| 206 | +Supplier<DEST> collectionFactory){ |
| 207 | + |
| 208 | +DEST result = collectionFactory.get(); |
| 209 | +for (T t : sourceCollection){ |
| 210 | + result.add(t); |
| 211 | + } |
| 212 | +return result; |
| 213 | + } |
| 214 | + |
| 215 | +... |
| 216 | + |
| 217 | +Set<Person> rosterSet = transferElements( |
| 218 | + roster, HashSet::new); |
| 219 | +``` |
| 220 | + |
| 221 | +**实例::实例方法** |
| 222 | +<br> |
| 223 | +其实开始那个例子就是一个实例::实例方法的引用 |
| 224 | +```java |
| 225 | +List<Integer> list =newArrayList<>(); |
| 226 | +for (int i =0; i <10; ++i){ |
| 227 | + list.add(i); |
| 228 | + } |
| 229 | + list.forEach(System.out::println); |
| 230 | +``` |
| 231 | +其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。 |
| 232 | +## 总结 |
| 233 | +Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量, |
| 234 | +但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码, |
| 235 | +所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。 |
0 commit comments