Java8中的lambda表達(dá)式入門(mén)教程
1.基本介紹
lambda表達(dá)式,即帶有參數(shù)的表達(dá)式,為了更清晰地理解lambda表達(dá)式,先上代碼:
1.1 兩種方式的對(duì)比
1.1.1 方式1-匿名內(nèi)部類
class Student{
private String name;
private Double score;
public Student(String name, Double score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public Double getScore() {
return score;
}
public void setName(String name) {
this.name = name;
}
public void setScore(Double score) {
this.score = score;
}
@Override
public String toString() {
return "{"
+ "\"name\":\"" + name + "\""
+ ", \"score\":\"" + score + "\""
+ "}";
}
}:
@Test
public void test1(){
List<Student> studentList = new ArrayList<Student>(){
{
add(new Student("stu1",100.0));
add(new Student("stu2",97.0));
add(new Student("stu3",96.0));
add(new Student("stu4",95.0));
}
};
Collections.sort(studentList, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getScore(),o2.getScore());
}
});
System.out.println(studentList);
}
代碼調(diào)用Collections.sort方法對(duì)集合進(jìn)行排序,其中第二個(gè)參數(shù)是一個(gè)匿名內(nèi)部類,sort方法調(diào)用內(nèi)部類中的compare方法對(duì)list進(jìn)行位置交換,因?yàn)閖ava中的參數(shù)類型只能是類或者基本數(shù)據(jù)類型,所以雖然傳入的是一個(gè)Comparator類,但是實(shí)際上可以理解成為了傳遞compare方法而不得不傳遞一個(gè)Comparator類 ,這種方式顯得比較笨拙,而且大量使用的話代碼嚴(yán)重冗余,這種情況在java8中通過(guò)使用lambda表達(dá)式來(lái)解決。
lambda表達(dá)式專門(mén)針對(duì)只有一個(gè)方法的接口(即函數(shù)式接口),Comparator就是一個(gè)函數(shù)式接口
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
@FunctionalInterface的作用就是標(biāo)識(shí)一個(gè)接口為函數(shù)式接口,此時(shí)Comparator里只能有一個(gè)抽象方法,由編譯器進(jìn)行判定。
使用lambda表達(dá)式之后方式1 中的代碼改造如下
1.1.2 方式2-lambda表達(dá)式
public void test1_(){
List<Student> studentList = new ArrayList<Student>(){
{
add(new Student("stu1",100.0));
add(new Student("stu2",97.0));
add(new Student("stu3",96.0));
add(new Student("stu4",95.0));
}
};
Collections.sort(studentList,(s1,s2)-> Double.compare(s1.getScore(),s2.getScore()));
System.out.println(studentList);
}
1.2 lambda語(yǔ)法
1.2.1 多參數(shù)
(1). lambda表達(dá)式的基本格式為(x1,x2)->{表達(dá)式...};
(2). 在上式中,lambda表達(dá)式帶有兩個(gè)參數(shù),此時(shí)參數(shù)類型可以省略,但兩邊的括號(hào)不能省略
(3). 如果表達(dá)式只有一行,那么表達(dá)式兩邊的花括號(hào)可以省略
1.2.2 無(wú)參數(shù)
一個(gè)常見(jiàn)的例子是新建一個(gè)線程,不使用lambda表達(dá)式的寫(xiě)法為
public void testThread(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello, i am thread!");
}
}).start();
}
其中Runnable接口也是一個(gè)函數(shù)式接口,源碼如下
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
將其轉(zhuǎn)換為lambda表達(dá)式的寫(xiě)法為
public void testThread_(){
new Thread(()-> System.out.println("hello, i am thread!")).start();
}
對(duì)于沒(méi)有參數(shù)的情況 :
(1).參數(shù)的括號(hào)不能省略,
(2).其他語(yǔ)法同多參數(shù)
1.2.3 一個(gè)參數(shù)
我們構(gòu)造一個(gè)只有一個(gè)參數(shù)的函數(shù)式接口
@FunctionalInterface
public interface MyFunctionalInterface {
public void single(String msg);
}
/**
* 需要單個(gè)參數(shù)
*/
public static void testOnePar(MyFunctionalInterface myFunctionalInterface){
myFunctionalInterface.single("msg");
}
/**
* 一個(gè)參數(shù),可以省略參數(shù)的括號(hào)
*/
@Test
public void testOneParameter(){
testOnePar(x-> System.out.println(x));
}
對(duì)于一個(gè)參數(shù)的情況:
(1).可以省略參數(shù)的括號(hào)和類型
(2).其他語(yǔ)法同多參數(shù)
1.3 jdk提供的常用函數(shù)式接口
在這里我們?yōu)榱搜菔局挥幸粋€(gè)參數(shù)的情況自己創(chuàng)建了一個(gè)函數(shù)式接口,其實(shí)java8中已經(jīng)為我們提供了很多常見(jiàn)的函數(shù)式接口,截圖如下:


常見(jiàn)的有
Function:提供任意一種類型的參數(shù),返回另外一個(gè)任意類型返回值。 R apply(T t);
Consumer:提供任意一種類型的參數(shù),返回空值。 void accept(T t);
Supplier:參數(shù)為空,得到任意一種類型的返回值。T get();
Predicate:提供任意一種類型的參數(shù),返回boolean返回值。boolean test(T t);
因此針對(duì)上面的情況,我們可以直接使用Consumer類,
/**
* 需要單個(gè)參數(shù)
*/
public static void testOnePar1(Consumer unaryOperator){
unaryOperator.accept("msg");
}
2.方法引用
lambda表達(dá)式用于替換函數(shù)式接口,方法引用也是如此,方法引用可以使代碼更加簡(jiǎn)單和便捷
2.1 小試牛刀
上代碼,根據(jù)List中字符串長(zhǎng)度排序:
public static void test1_() {
List<String> strLst = new ArrayList<String>() {
{
add("adfkjsdkfjdskjfkds");
add("asdfasdfafgfgf");
add("public static void main");
}
};
Collections.sort(strLst, String::compareToIgnoreCase);
System.out.println(strLst);
}
只要方法的參數(shù)和返回值類型與函數(shù)式接口中抽象方法的參數(shù)和返回值類型一致,就可以使用方法引用。
2.2 使用方式
方法引用主要有如下三種使用情況
(1). 類::實(shí)例方法
(2). 類::靜態(tài)方法
(3). 對(duì)象::實(shí)例方法
其中后兩種情況等同于提供方法參數(shù)的lambda表達(dá)式,
如System.out::println 等同于(x)->System.out.println(x),
Math::pow 等同于(x,y)->Math.pow(x,y).
第一種中,第一個(gè)參數(shù)會(huì)成為執(zhí)行方法的對(duì)象,String::compareToIgnoreCase)等同于(x,y)->x.compareToIgnoreCase(y)
此外,方法引用還可以使用this::methodName及super::methodName表示該對(duì)象或者其父類對(duì)象中的方法
class Father {
public void greet() {
System.out.println("Hello, i am function in father!");
}
}
class Child extends Father {
@Override
public void greet() {
Runnable runnable = super::greet;
new Thread(runnable).start();
}
}
public static void main(String[] args){
new Child().greet();
}
最后打印的結(jié)果為:Hello, i am function in father!
3.構(gòu)造器引用
構(gòu)造器引用同方法引用類似,同樣作用于函數(shù)式接口
構(gòu)造器引用的語(yǔ)法為 ClassName::new
啥也不說(shuō),線上代碼
List<String> labels = Arrays.asList("aaa","bbb","ccc","ddd");
Stream<Button> buttonStream = labels.stream().map(Button::new);
如上代碼所示,map方法內(nèi)需要一個(gè)Function對(duì)象
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
調(diào)用Button的構(gòu)造器,接收一個(gè)String類型的參數(shù),返回一個(gè)Button類型的對(duì)象
public class Button extends ButtonBase {
/**
* Creates a button with the specified text as its label.
*
* @param text A text string for its label.
*/
public Button(String text) {
super(text);
initialize();
}
}
另外一個(gè)例子如下
Button[] buttons1 = buttonStream.toArray(Button[]::new);
toArray方法的申明如下
<A> A[] toArray(IntFunction<A[]> generator);
接收一個(gè)IntFunction類型的接口R apply(int value);該接口接收一個(gè)int型參數(shù),返回指定類型
調(diào)用數(shù)組的初始化方法剛好適合。
有一個(gè)簡(jiǎn)單的構(gòu)造器引用的例子如下:
public class LambdaTest3 {
@Test
public void test1_(){
List<Integer> list = this.asList(ArrayList::new ,1,2,3,4,5);
list.forEach(System.out::println);
}
public <T> List<T> asList(MyCrator<List<T>> creator,T... a){
List<T> list = creator.create();
for (T t : a)
list.add(t);
return list;
}
}
interface MyCrator<T extends List<?>>{
T create();
}
我們?cè)陧?xiàng)目中經(jīng)常使用asList來(lái)創(chuàng)建一個(gè)ArrayList,但是也只能是ArrayList,
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
我們?nèi)绾卧赼sList中指定創(chuàng)建哪種類型的List的實(shí)例呢,使用構(gòu)造器引用使得asList方法可以指定生成的List類型。
4.自由變量的作用范圍
啥都不說(shuō),上代碼先:
public class LambdaTest4 {
public void doWork1(){
Runnable runnable = ()->{
System.out.println(this.toString());
System.out.println("lambda express run...");
};
new Thread(runnable).start();
}
public void doWork2(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(this.toString());
System.out.println("anony function run...");
}
};
new Thread(runnable).start();
}
public static void main(String[] args) {
new LambdaTest4().doWork1();
new LambdaTest4().doWork2();
}
}
代碼中doWork1和doWork2分別使用lambda表達(dá)式和匿名內(nèi)部類的方式實(shí)現(xiàn)了Runnable接口,最后打印的結(jié)果如下
com.java8.lambda.LambdaTest4@74f84cf
lambda express run...
com.java8.lambda.LambdaTest4$1@4295c176
anony function run...
可見(jiàn)使用lambda表達(dá)式的方式,表達(dá)式中的this指的是包含lambda表達(dá)式的類,而使用匿名內(nèi)部類的方式,this指的是匿名內(nèi)部類本身。
4.1 自由變量和閉包
lambda達(dá)式中的變量有幾類,1.參數(shù)內(nèi)的變量,2.lambda表達(dá)式中的內(nèi)部變量,3.自由變量,自由變量指的是在lambda表達(dá)式之外定義的變量。

包含自由變量的代碼則稱為閉包,如果理解了lambda表達(dá)式會(huì)在編譯階段被轉(zhuǎn)換為匿名內(nèi)部類,那么可以很容易理解自由變量在lambda表達(dá)式中的作用范圍,在lambda表達(dá)式中會(huì)捕獲所有的自由變量,并且將變量定義為final類型,所以不能改變lambda表達(dá)式中自由變量的值,如果改變,那么首先就無(wú)法編譯通過(guò)。
對(duì)于引用類型(如ArrayList),final指的是引用指向的類始終不變,進(jìn)行add操作是允許的,但是應(yīng)該保證變量的線程安全。
代碼如下所示:
public class Outer {
public AnnoInner getAnnoInner(int x) {
int y = 100;
return new AnnoInner() {
int z = 100;
@Override
public int add() {
return x + y + z;
}
};
}
public AnnoInner AnnoInnergetAnnoInner1(List<Integer> list1) {
List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2, 3));
return ()->{
list2.add(123);
int count = 0;
Iterator<Integer> it = list1.iterator();
while (it.hasNext()){
count+=it.next();
}
Iterator<Integer> it1 = list2.iterator();
while (it1.hasNext()){
count+=it1.next();
}
return count;
};
}
@Test
public void test(){
AnnoInner res = new Outer().AnnoInnergetAnnoInner1(new ArrayList<>(Arrays.asList(1,2,3)));
System.out.println(res.add());
}
}
interface AnnoInner {
int add();
}
最后返回135
5.接口的靜態(tài)方法和默認(rèn)方法
java8對(duì)于接口做出了種種改進(jìn),使得我們可以在接口中實(shí)現(xiàn)默認(rèn)方法和靜態(tài)方法,見(jiàn)Comparator接口完整定義
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
}
在比較器接口中定義了若干用于比較和鍵提取的靜態(tài)方法和默認(rèn)方法,默認(rèn)方法的使用使得方法引用更加方便,例如使用java.util.Objects類中的靜態(tài)方法isNull和nonNull可以在Stream中很方便的進(jìn)行null的判定(之后會(huì)有對(duì)于stream的介紹)。但是在接口中引入默認(rèn)方法設(shè)計(jì)到一個(gè)問(wèn)題,即
(1).接口中的默認(rèn)方法和父類中方法的沖突問(wèn)題
(2).接口之間引用的沖突問(wèn)題
對(duì)于第一個(gè)沖突,java8規(guī)定類中的方法優(yōu)先級(jí)要高于接口中的默認(rèn)方法,所以接口中默認(rèn)方法復(fù)寫(xiě)Object類中的方法是沒(méi)有意義的,因?yàn)樗械慕涌诙寄J(rèn)繼承自O(shè)bject類使得默認(rèn)方法一定會(huì)被覆蓋。
對(duì)于第二個(gè)沖突,java8強(qiáng)制要求子類必須復(fù)寫(xiě)接口中沖突的方法。如下所示:
public class LambdaTest5 implements myInterface1, myInterface2 {
@Override
public void getName() {
myInterface1.super.getName();
}
public static void main(String[] args) {
new LambdaTest5().getName();
}
}
interface myInterface1 {
default void getName() {
System.out.println("myInterface1 getName");
}
;
}
interface myInterface2 {
default void getName() {
System.out.println("myInterface2 getName");
}
}
強(qiáng)制使用myInterface1中的getName方法
總結(jié)
以上所述是小編給大家介紹的Java8中的lambda表達(dá)式入門(mén)教程,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
oracle+mybatis-plus+springboot實(shí)現(xiàn)分頁(yè)查詢的實(shí)例
本文主要介紹了oracle+mybatis-plus+springboot實(shí)現(xiàn)分頁(yè)查詢,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Reactor3 Map與FlatMap的區(qū)別示例詳解
這篇文章主要為大家介紹了Reactor3 Map與FlatMap的區(qū)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
創(chuàng)建網(wǎng)關(guān)項(xiàng)目(Spring Cloud Gateway)過(guò)程詳解
這篇文章主要介紹了創(chuàng)建網(wǎng)關(guān)項(xiàng)目(Spring Cloud Gateway)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
詳解Mybatis是如何把數(shù)據(jù)庫(kù)數(shù)據(jù)封裝到對(duì)象中的
這篇文章主要介紹了Mybatis是如何把數(shù)據(jù)庫(kù)數(shù)據(jù)封裝到對(duì)象中的,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Java 實(shí)戰(zhàn)項(xiàng)目之小說(shuō)在線閱讀系統(tǒng)的實(shí)現(xiàn)流程
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實(shí)現(xiàn)前臺(tái)閱讀后臺(tái)管理的小說(shuō)在線閱讀系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11
SpringBoot解決跨域請(qǐng)求攔截問(wèn)題代碼實(shí)例
這篇文章主要介紹了SpringBoot解決跨域請(qǐng)求攔截代碼實(shí)例,在微服務(wù)開(kāi)發(fā)中,一個(gè)系統(tǒng)包含多個(gè)微服務(wù),會(huì)存在跨域請(qǐng)求的場(chǎng)景。 本文講解SpringBoot解決跨域請(qǐng)求攔截的問(wèn)題。,需要的朋友可以參考下2019-06-06
RocketMQ之NameServer架構(gòu)設(shè)計(jì)及啟動(dòng)關(guān)閉流程源碼分析
這篇文章主要為大家介紹了RocketMQ之NameServer架構(gòu)設(shè)計(jì)及啟動(dòng)關(guān)閉流程源碼分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11
java中BigDecimal類型比較大小和絕對(duì)值計(jì)算方式
這篇文章主要介紹了java中BigDecimal類型比較大小和絕對(duì)值計(jì)算方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java數(shù)組轉(zhuǎn)List及Stream的基本方法使用方法
Java?的?Stream?流操作是一種簡(jiǎn)潔而強(qiáng)大的處理集合數(shù)據(jù)的方式,允許對(duì)數(shù)據(jù)進(jìn)行高效的操作,如過(guò)濾、映射、排序和聚合,這篇文章主要介紹了Java數(shù)組轉(zhuǎn)List及Stream的基本方法使用教程,需要的朋友可以參考下2024-08-08

