java lambda表達式和函數式接口使用示例


import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;

import org.junit.Test;

/**
 * @author gm
 * Lambda  表達式的基礎語法:java8中引入一個新的操作符 "->" ,該操作符稱爲箭頭操作符或lambda操作符
 *      箭頭操作符將lambda拆分成兩部分:
 *      左側:lambda表達式的參數列表
 *      右側:lambda表達式中所需執行的功能,即lambda體
 *  語法格式一:無參數,無返回值
 *      () -> System.out.println("xxxxxx");
 *  語法格式二:有一個參數,無返回值
 *      (x) -> System.out.println(x);
 *  語法格式三:若只有一個參數,小括號可以省略不寫
 *      x -> System.out.println(x);
 *  語法格式四:有兩個以上的參數,有返回值,並且lambda體中有多條語句    test4
 *      Comparator<Integer> comparator = (x,y) -> {
 *            System.out.println("函數式接口");
 *            return Integer.compare(x, y);
 *        };
 *    語法格式五:若lambda體中只有一條語句,則return和大括號都可以省略不寫
 *        Comparator<Integer> comparator = (x,y) -> Integer.compare(x, y);
 *    語法格式六:lambda表達式的參數列表的數據類型可以省略不寫,因爲jvm編譯器可以根據上下文推斷出數據類型,即“類型推斷”
 *        (Integer x,Integer y) -> Integer.compare(x, y);  == (x,y) -> Integer.compare(x, y);
 *    
 *    左右遇一括號省(左邊是一個參數或者右邊只有一條語句), 左側推斷類型省(左邊不需要顯示指定類型)
 *
 *    二、lambda表達式需要函數式接口的支持
 *        函數式接口:接口中只有一個抽象方法的接口(這樣才知道要動態替換哪個方法),可以使用 @FunctionalInterface 檢查一下
 *        反而言之:jdk接口上有@FunctionalInterface註解的都是函數式接口
 */
public class TestLambda {
    
	@Test
	public void test1() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("hello lambda1");
			}
		}).start();

		// 相當於實現接口Runable無參方法run的匿名實現類.這裏的實現同上面的匿名類效果一樣
		Runnable r1 = () -> System.out.println("hello lambda2");
		new Thread(r1).start();
	}
	
	// Consumer接口 accept該函數式接口的唯一的抽象方法,接收一個參數,沒有返回值
	@Test
	public void test2() {
		// 這裏因爲是一個參數,所以左邊的括號省略,右邊是一個表達式,所以右邊的大括號省略
		// Consumer是一個消費者型的函數式接口,其accept方法可以對接收到的數據進行處理,無返回值。這裏相當於實現其抽象方法accept
		Consumer<String> consumer = x -> System.out.println(x);
		consumer.accept("我很帥");
	}
	
	// Consumer接口andThen方法,在執行完調用者方法後再執行傳入參數的方法
	@Test
	public void test3() {
		// 這裏因爲是一個參數,所以左邊的括號省略,右邊是一個表達式,所以右邊的大括號省略
		// Consumer是一個消費者型的函數式接口,其andThen方法在執行完調用者方法後再執行傳入參數的方法
		Consumer<String> consumer = x -> System.out.println(x);
		Consumer<String> consumer2 = x -> System.out.println("是真的,大家都是這麼覺得!");
		consumer.andThen(consumer2).accept("我很帥是嗎?");
	}

	@Test
	public void test4() {
		// 這裏左邊是兩個參數,所以使用括號,右邊是兩條語句,使用大括號。這裏是實現了Comparator接口的compare方法,用於collection排序操作
		Comparator<Integer> comparator = (x, y) -> {
			System.out.println("函數式接口");
			return Integer.compare(x, y);
		};
		// 這裏是簡寫,效果是倒序
		Comparator<Integer> comparator2 = (x, y) -> Integer.compare(y, x);

		Integer[] array = { 1, 9, 6, 7, 3, 2, 8 };
		List<Integer> list = Arrays.asList(array);
		list.sort(comparator);
		System.out.println(list);
		list.sort(comparator2);
		System.out.println(list);
	}

	// 通過這裏的兩個lambda實現,可以發現函數式接口的方法是動態改變的,而且不用繼續接口,不用匿名類,實現起來方便快捷
	@Test
	public void test5() {
		// (x) -> (x + 1)是一個lambda表達式,功能是自增。
		// 其相當於一個入參和返回值類型相同的函數,這裏將其傳給MyFun<Integer>,可以作爲函數式接口MyFun內方法getValue的實現。
		// 可以理解爲MyFun內方法getValue的實現變成了整數值自增然後返回
		Integer result = operation(100, (x) -> (x + 1));
		// 這裏輸出101
		System.out.println(result);
		// 這裏同理,只是getValue的實現變成了自減,所以輸出結果爲99
		System.out.println(operation(100, (x) -> (x - 1)));
	}

	public Integer operation(Integer num, MyFun<Integer> mf) {
		return mf.getValue(num);
	}

	List<User> users = Arrays.asList(new User("zhangsan", 24, 7500), new User("lisi", 25, 13000), new User("wangwu", 26, 20000));

	@Test
	public void test6() {
		// 這裏第二個參數是lambda表達式,其實現了函數表達式式Comparator的compare方法
		Collections.sort(users, (u1, u2) -> {
			return u1.getAge() - u2.getAge();
		});
		System.out.println(users);
	}

	@Test
	// 對兩個long型進行處理
	public void test7() {
		// 參數3是lambda,用於實現函數式接口。相當於MyFun2的getValue(a,b)功能變成了a+b
		op(100L, 200L, (x, y) -> x + y);
	}

	public void op(Long t1, Long t2, MyFun2<Long, Long> mf2) {
		System.out.println(mf2.getValue(t1, t2));
	}

	@Test
	public void test8() {
		// 這裏是stream配合lambda表達式一起使用。stream這裏簡單理解爲遍歷list
		// (e) -> e.getSalary() >= 10000是函數式接口Predicate內方法test的實現,其功能是判斷是否正確
		// 下面這裏就是判斷list中的元素的salary是否大於10000,大於的繼續往下處理
		// forEach就是遍歷打印。這裏總體的功能就是遍歷list,打印salary大於10000的User
		users.stream().filter((e) -> e.getSalary() >= 10000).forEach(System.out::println);
		users.stream().map((e) -> e.getName()).forEach(System.out::println);
	}
}

@FunctionalInterface
public interface MyFun<T> {
	public T getValue(T value);
}
@FunctionalInterface
public interface MyFun2<T, R> {

	public R getValue(T t1, T t2);
}

public class User {
	private String name;
	private int age;
	private int salary;

	public User() {
	}

	public User(String name, int age, int salary) {
		super();
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getSalary() {
		return salary;
	}

	public void setSalary(int salary) {
		this.salary = salary;
	}

}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.junit.Test;

/**
 * @author gm
 * java8 內置的四大核心函數式接口
 * 
 * Consumer<T>: 消費型接口,接收數據並處理
 *         void accept(T t);
 * Supplier<T>: 供給型接口,對外提供數據
 *         T get()
 * Function<T, R>: 函數型接口,接收參數,返回結果
 *         R apply(T t);
 * Predicate<T>: 斷言型接口,檢測入參是否符合條件(符合則返回true)
 *         boolean test(T t);
 *
 */
public class TestLambdaFunc {

	@Test
	// Consumer<T> 消費型接口
	public void testConsumer() {
		// Consumer是消費型接口,其可定義對接收到的數據進行不同的處理。這裏的處理方式就是打印詳細信息。
		// m -> System.out.println("工資:" + m + " 元") 可以理解爲Consumer中accept(T t)方法的實現
		// m -> System.out.println("這" + m + " 元真,是辛苦所得") 執行完調用者方法後再執行傳入參數的方法
		happy(10000L, m -> System.out.println("工資:" + m + " 元"), m -> System.out.println("這" + m + " 元真,是辛苦所得"));
	}

	public void happy(double money, Consumer<Double> con, Consumer<Double> con2) {
		con.andThen(con2).accept(money);
	}
    
	// Supplier<T> 供給型接口
	@Test
	public void testSupplier() {
		// Supplier是供給型接口。其內部定義對外輸出的數據。而且不需要入參
		// () -> (int)(Math.random() * 100) 爲這裏的供給行爲,即返回一個隨機數
		List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
		System.out.println(numList);
	}
    
	public List<Integer> getNumList(int num, Supplier<Integer> sup) {
		List<Integer> list = new ArrayList<>();

		for (int i = 0; i < num; i++) {
			Integer n = sup.get();
			list.add(n);
		}
		return list;
	}
    
	// Function<T,R> 函數型接口
	@Test
	public void testFunction() {
		// 函數型接口功能相對強大,可以對接收到的數據進行進一步處理,返回類型可以和入參類型不一致(泛型接口,二元組)
		// (x) -> (x + ", 哈哈哈") 這裏作爲Function接口中apply的實現
		String str = strHandler("我最帥", (x) -> (x + ", 哈哈哈"));
		System.out.println(str);
	}

	private String strHandler(String str, Function<String, String> fun) {
		return fun.apply(str);
	}
    
	// Predicate<T> 斷言型接口
	@Test
	public void testPredicate() {
		List<String> list = Arrays.asList("Hello", "World", "www.baidu.com");
		// 函數式接口Predicate主要用於判斷,x -> (x.length() > 5) 這裏是判斷入參的長度是否大於5
		List<String> filterStr = filterStr(list, x -> (x.length() > 5));
		System.out.println(filterStr);
	}

	public List<String> filterStr(List<String> list, Predicate<String> pre) {
		List<String> strList = new ArrayList<>();

		for (String str : list) {
			if (pre.test(str)) {
				strList.add(str);
			}
		}
		return strList;
	}
}

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.junit.Test;

/**
 * @author gm
 * 方法引用:若lambda體中的內容有方法已經實現了,我們可以使用“方法引用”
 *         (可以理解爲方法引用是lambda表達式的另外一種表達形式)
 * 主要有三種語法格式:
 *         對象::實例方法名
 *         類::靜態方法名
 *         類::實例方法名
 * 注意:
 *     1.lambda體中調用方法的參數列表與返回值類型,要與函數式接口中抽象方法的參數列表和返回值類型一致
 *  2.若lambda參數列表 中的第一個參數是實例方法的調用者,而第二個參數是實例方法的參數時,可以使用className::method
 *  
 *  二、構造器引用
 *  格式:
 *      ClassName::new
 */
public class TestMethodRef {

	// 對象::實例方法名
	@Test
	public void test() {
		Consumer<String> consuemr = (x) -> System.out.println(x);
		consuemr.accept("aaaa");

		PrintStream ps = System.out;
		Consumer<String> consumer2 = ps::println;
		consumer2.accept("bbbb");
	}
    
	@Test
	public void test2() {
		User user = new User("gm", 30, 200000);
		// 這裏是無參,所以左邊使用了()
		Supplier<String> sup = () -> user.getName();
		System.out.println(sup.get());

		// 這裏對lambda表達式進行了省略。() -> user.getAge() == user::getAge
		Supplier<Integer> sup2 = user::getAge;
		System.out.println(sup2.get());
	}
    
	// 類::靜態方法名
	@Test
	public void test3() {
		Comparator<Integer> com = (x, y) -> Integer.compare(y, x);

		// 這裏因爲入參和lambda實現方法要調用的入參一樣。所以兩邊都省略了
		Comparator<Integer> com1 = Integer::compare;

		Integer[] array = { 1, 9, 6, 7, 3, 2, 8 };
		List<Integer> list = Arrays.asList(array);
		list.sort(com);
		System.out.println(list);

		list.sort(com1);
		System.out.println(list);
	}
    
	// 類::實例方法名
	@Test
	public void test4() {
		BiPredicate<String, String> bp = (x, y) -> x.equals(y);

		// 這裏是兩個入參,而且滿足第一個參數是新方法調用者,第二個參數是入參的情況
		// 理論而言都用上面的表達式即可,看起來比較簡單,但是不能避免別人不會使用簡寫方式,看不懂豈不是很尷尬
		BiPredicate<String, String> bp2 = String::equals;
		System.out.println(bp.test("1", "1"));
		System.out.println(bp2.test("Hello", "World"));
	}

	@Test
	public void test5() {
		// 函數式接口生產數據的方式是new User();
		// 創建Supplier容器,聲明爲User類型,此時並不會調用對象的構造方法,即不會創建對象
		Supplier<User> sup = () -> new User();
		// 這裏功能同上,簡寫方式
		Supplier<User> sup2 = User::new;
		// 調用get()方法,此時會調用對象的構造方法,即獲得到真正對象
		System.out.println(sup.get());
		// 每次get都會調用構造方法,即獲取的對象不同
		System.out.println(sup2.get());
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章