初步接觸Java中的Lambda表達式

參考:
https://blog.csdn.net/w1764662543/article/details/89154408
https://blog.csdn.net/qq_31807385/article/details/82670505
https://www.cnblogs.com/nnxud/p/9827704.html
Java核心技術

第一次認真學習Lambda表達式,有錯誤還望指正。

Lambda表達式簡介

可以將Lambda表達式理解爲一個匿名函數;Lambda表達式允許將一個函數作爲另外一個函數的參數; 我們可以把 Lambda 表達式理解爲是一段可以傳遞的代碼(將代碼作爲實參),也可以理解爲函數式編程,將一個函數作爲參數進行傳遞。
Lambda表達式只能用來簡化僅包含一個抽象方法的接口的創建。
(1)只能是接口
否則報:Target type of a lambda conversion must be an interface
(2)只能是含有一個抽象方法的接口
否則報:Multiple non-overriding abstract methods found xxx

替代匿名內部類

lambda表達式用得最多的場合就是替代匿名內部類,而實現Runnable接口是匿名內部類的經典例子。

// 1.使用匿名類
Thread th1 = new Thread(new Runnable() {
	@Override
    public void run() {
		System.out.println("2.匿名類的Runnable...");
	}
});
th2.start();
// 2.使用Lambda表達式
Runnable runnable = ()->System.out.println("線程啓動了");	
Thread th2 = new Thread(runnable);
// 調用th2.start()時會執行Lambda表達式的主體
th2.start();
// 3.更簡潔的寫法(但這種形式不如形式2方便人理解)
new Thread(()-> System.out.println("3.lambda表達式實現Runnable...")).start();

Lambda表達式的形式

([Lambda參數列表,即形參列表]) -> {Lambda體,即方法體}
使用 "->"將參數和實現邏輯分離;( ) 中的部分是需要傳入Lambda體中的參數;{ } 中的部分,接收形參列表中的參數,完成一定的功能。->右邊就相當於實現了接口中的抽象方法。此時Lambda表達式就是一個可用的接口實現類了。
若Lambda體中只有一條語句,return和大括號都可以省略不寫。一定要注意,如果Lambda體中寫了return,則必須加上{}。除此之外,Lambda表達式的形參列表的數據類型也可以省略不寫,因爲編譯器可以通過上下文推斷出數據類型。

package learnlambda;
// 自定義一個函數式接口
interface MyInterface{
	int add(int a,int b);
}
public class LambdaTest05 {
	public static void main(String[] args) {
		MyInterface mInstance1 = (x,y)->  x+y;
		System.out.println(mInstance1.add(2, 3));
		
		MyInterface mInstance2 = (x,y)->  { return x+y; };
		System.out.println(mInstance2.add(2, 3));
	}
}

方法引用

接口中要被實現的抽象方法的形參列表和返回值類型,必須與方法引用的方法的形參列表和返回值類型保持一致,否則不能使用方法引用。其實就是使用已存在的方法作爲函數式接口中抽象方法的實現,因此已存在的方法的返回值類型與形參列表同接口中的抽象方法一致。
在方法引用中,使用操作符 “::”將類(或對象)與方法名分隔開來。值得注意的是,在使用方法引用給接口變量賦值的時候,並不需要給方法提供形式參數,而僅在調用的時候提供實參即可,這與先前的lambda表達式並無不同。
以下是方法引用的3種方式。

  1. •object::instanceMethod
  2. •Class::staticMethod
  3. •Class::instanceMethod
    注意,使用方法引用和靜態方法的概念不要弄混淆。
		 // 2. 比較器的示例
		 // 1.以最簡單的lambda表達式實現
        Comparator<Integer> com1 = (a,b)->{
        	if(a>b){
        		return 1;
        	}else if(a<b){
        		return -1;
        	}else{
        		return 0;
        	}
        };
        // 2.使用Lambda表達式(這種形式的Lambda表達式必須指明形式參數)
        Comparator<Integer> com2 = (a,b)-> Integer.compare(a,b);
        // 3.使用方法引用(雖然Integer中的compare方法不是靜態的)
        // 注意,賦值給接口變量,但並未指明形式參數
        Comparator<Integer> com3 = Integer::compare;
        // 調用方法
        int res1 = com1.compare(2, 3);
        int res2 = com2.compare(4, 5);
        int res3 = com3.compare(7,6);
        System.out.println("res1 = "+res1);
        System.out.println("res2 = "+res2);
        System.out.println("res3 = "+res3);

構造器引用

構造器引用的方法名爲new,如Person::new 就表示Person構造器的一個引用。

Supplier<Student> supp = () -> new Student("zhao",23);
// 1.使用方法引用
Supplier<Student> supp2 = Student::new;

4種形式的Lambda表達式示例

Lambda表達式中,存在1)無參無返回值、2)有參無返回值、3)無參有返回值、4)有參有返回值,4種情況。

package learnlambda;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
import java.util.stream.Stream;

public class LambdaTest01 {
	
	public static void main(String[] args) {
		// 3.使用lambda表達式實現,不加{}
		// (1)無參無返回值
//		new Thread(()-> System.out.println("3.lambda表達式實現Runnable...")).start();
		// (1)的另一種寫法(更容易理解)
		Runnable runnable = ()->System.out.println("線程啓動了");	
		Thread th1 = new Thread(runnable);
		th1.start();
		// (2)有參無返回值
		ArrayList<String> list =  new ArrayList<>();
		list.add("zhao");
		list.add("qian");
		list.add("sun");
		list.add("li");
		list.forEach( (tele)->{System.out.println(tele);} );
		
		// (3)無參有返回值(當方法體只有一個return時,可以省略return)
		Random random = new Random(); 
        Stream<Integer> stream = Stream.generate( () ->  random.nextInt(100) );
//        Stream<Integer> stream = Stream.generate(() ->{ return random.nextInt(100);});
        stream.forEach(t -> System.out.println(t));
        
        // (4)有參有返回值
        // 需求:按照字符串的長度排序
        String[] names ={"Bough","Paul","Peter","Alex"};
        // sort方法第2個參數需要傳入一個實現了Comparator接口的對象
        // 而Comparator接口中含有抽象方法int compare(T o1, T o2);
//        重寫該方法 則應當是:public int compare(String first, String second)
//                           { return first.length() - second.length();}
        // 
        String[] names2 ={"Bough","Paul","Peter","Alex"};
        Comparator<String> com = (first,second) -> first.length()- second.length();
        Arrays.sort(names2,com);
        System.out.println(Arrays.toString(names2));
        // (4)的另一種簡潔的寫法
		Arrays.sort(names, (first,second)-> first.length() - second.length());
        System.out.println(Arrays.toString(names));
	}
	
}

變量作用域

在lambda表達式中訪問外圍方法或類中的變量,由於使用lambda表達式的重點是延時執行,所以lambda表達式可能在外圍方法調用返回很久之後才運行,而那時這個變量已經不存在了,所以lambda表達式必須存儲自由變量的值。
如Java核心技術一書中的例子,在lambda表達式中訪問外圍方法或類中的變量。

public static void repeatMessage(String text,int delay){
		ActionListener listener = event ->{
			System.out.println(text);
			Toolkit.getDefaultToolkit().beep();
		};
		new Timer(delay,listener).start();
}
// 調用
repeatMessage("Hello",1000);

注意看,lambda 表達式中的變量 text,實際上是 repeatMessage 方法的一個參數變量。仔細想想,這裏好像會有問題,儘管不那麼明顯。lambda 表達式的代碼可能會在repeatMessage 調用返回很久以後才運行(個人理解:這可能主要是因爲Lambda表達式的延時執行),而那時這個參數變量已經不存在了。
這裏需要說明一下,自由變量指的是非lambda表達式的參數且不在lambda體中定義的變量。在lambda表達式中,只能引用值不會改變的變量。如果在lambda表達式中改變變量,併發執行多個動作時就會不安全。實際上,lambda表達式中捕獲的變量必須是最終變量,最終變量的意思是這個變量初始化之後就不會再爲它賦新值。

函數式接口

函數式接口的定義

如果一個接口只有一個抽象方法,那麼就可以稱該接口是函數式接口。當然,接口中既可以聲明非抽象方法(Java8中可以聲明static方法),也可以重寫Object類的方法,如toString或clone,這些方法都有可能會讓方法不再是抽象的。當需要這種接口的對象時,就可以提供一個lambda表達式。實際上,在Java中,對lambda表達式所能做的也只能是將其轉換爲函數式接口。值得注意的是,lambda表達式與函數式接口中的抽象方法的參數與返回值是一一對應的,即如果函數式接口中的抽象方法是有返回值,有參數的,那麼要求Lambda表達式也是有返回值,有參數的(以此類推)

常見的函數式接口

在這裏插入圖片描述

示例

Student類

package learnlambda;

public class Student {
	
	private String name;
	private int age;
	public Student(){}
	public Student(String name,int age){
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}
	@Override
	public String toString() {
		return "Student [ name :"+
				name+", age :"+age
				+"]";
	}
}

測試代碼

package learnlambda;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class LambdaTest03 {
	/**
	 * 4種函數式接口的使用
	 * @param args
	 */
	public static void main(String[] args) {
		// 1. 函數式接口的對象cons使用Lambda表達式相當於實現了Consumer接口的抽象方法accept
		// 所以當變量cons調用accept方法即在使用Lambda體。
		Consumer<String> cons = e->System.out.println("接收到的字符串 :"+e);
		cons.accept("woojopj");
		
		// 2.Supplier接口
		Supplier<Student> supp = () -> new Student("zhao",23);
		Student stu1 = supp.get();
		System.out.println(stu1.toString());
		
		Supplier<String> supp2 = ()-> "hello world".substring(2);
		String strSub = supp2.get();
		System.out.println("2.Supplier 的結果: "+strSub);
		// 3.Function接口
		Function<Student,String> func = (stu) ->{ 
			return stu.getName();
		};
		Student stu2 =new Student("zhao",23);
		String str =func.apply(stu2);
		System.out.println("3.Function 的結果: "+str);
		
		// 4.Predicate接口
		// 判斷學生年紀是否在20-30歲之間,否則返回錯
		Predicate<Student> pre = (stu) ->
		{
			int age = stu.getAge();
			if( age>= 20 && age<=30)
			{
				return true;
			}else
			{
				return false;
			}
		};
		System.out.println("4.Predicate 的結果 : "+pre.test(stu2));
 	}
}

@FunctionalInterface註解

JDK1.8之後,如果是函數式接口,則可以添加 @FunctionalInterface註解表示這是一個函數式接口,但這並不是表示必須要使用該註解。根據定義,任何有一個抽象方法的接口都是函數式接口。不過使用 @FunctionalInterface註解確實是一個很好的做法。這樣做有兩個優點。 (1)如果你無意中增加了另一個非抽象方法, 編譯器會產生一個錯誤消息。(2) 另外 javadoc 頁裏會指出你的接口是一個函數式接口。

@FunctionalInterface
interface MyInterface{
	int add(int a,int b);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章