1. 程式人生 > 其它 >Java8新特性-Lambda表示式-Stream API

Java8新特性-Lambda表示式-Stream API

技術標籤:Java學習筆記javastreamlambda

演變過程 & 初體驗

當前有一個List 裡面包含公司的員工資訊,
需求一:求 當前公司中年齡大於35歲的員工
需求二:求 當前公司中工資大於5000的員工

List<Employee> list = Arrays.asList(
				// 姓名   工資    年齡
	new Employee("張三", 3888.95, 25), 
	new Employee("李四", 7000.00, 45),
	new Employee("王五", 6988.95, 40
), new Employee("趙六", 2888.85, 25), new Employee("Wang", 5888.95, 35));

普通做法 :

需求一: 求 當前公司中年齡大於35歲的員工

	public static void test3() {
		List<Employee> filterEmployee = filterEmployee(list);
		for (Employee item : filterEmployee) {
			System.out.println(item);
		}
	}
	public
static List<Employee> filterEmployee(List<Employee> list) { ArrayList<Employee> arrayList = new ArrayList<Employee>(); for (Employee item : list) { if (item.getAge() > 35) { arrayList.add(item); } } return arrayList; }

在這裡插入圖片描述

需求二:求 當前公司中工資大於5000的員工

	public static
void test3() { List<Employee> filterEmployee = filterEmployee2(list); for (Employee item : filterEmployee) { System.out.println(item); } } public static List<Employee> filterEmployee2(List<Employee> list) { ArrayList<Employee> arrayList = new ArrayList<Employee>(); for (Employee item : list) { if (item.getSalary() > 5000) { arrayList.add(item); } } return arrayList; }

在這裡插入圖片描述

這種做法寫的程式碼。太過於冗餘。並且複用性低。為此我們使用 策略設計模式 優化一下

優化(一) 需求一:

新建一個介面 :

public interface FilterEmployee<T> {
	boolean filterTest(Employee emp);
}

根據需求一對其實現類:

public class FilterEmployeeByAge implements FilterEmployee<Employee> {

	@Override
	public boolean filterTest(Employee emp) {
		return emp.getAge() > 35;
	}

}

根據需求二對其實現類:

public class FilterEmployeeBySalary implements FilterEmployee<Employee> {

	@Override
	public boolean filterTest(Employee emp) {
		return emp.getSalary() > 5000;
	}

}



定義一個 這兩個需求的公共方法

// 優化方式一 策略設計模式:
	public static List<Employee> filterEmployee(List<Employee> list, FilterEmployee<Employee> emp) {
		List<Employee> arrayList = new ArrayList<>();
		for (Employee item : list) {
			if (emp.filterTest(item)) {
				arrayList.add(item);
			}
		}
		return arrayList;
	}
	public static void test3() {
	// 優化一需求一:
		List<Employee> filterEmployeeByage = filterEmployee(list, new FilterEmployeeByAge()); 
		System.out.println(filterEmployeeByage);
	// 優化一需求二:
		List<Employee> filterEmployeeByage = filterEmployee(list, new FilterEmployeeBySalary());
		System.out.println(filterEmployeeByage);
	}

通過這種方式 ,我們對其的進行了策略化,我們發現,這種方法,對我們來說依然是非常冗餘的,因為每一種需求 ,就要增加一個實現類,於是我們進行了第二次優化。

介面依然要使用,但是這次我們不需要使用實現類了,我們把介面的實現方式直接放在引數中。這也是匿名內部類的演化過程。

		public static void test3() {
		// 優化二需求一:
		List<Employee> filterEmployeeByage = filterEmployee(list, new FilterEmployee<Employee>() {
			@Override
			public boolean filterTest(Employee emp) {
				return emp.getAge() > 35;
			}
		});

		// 優化二需求二:
		List<Employee> filterEmployeeBySalary = filterEmployee(list, new FilterEmployee<Employee>() {
			@Override
			public boolean filterTest(Employee emp) {
				return emp.getSalary() > 5000;
			}
		});

		System.out.println(filterEmployeeByage);
		System.out.println(filterEmployeeBySalary);
	}

這次優化之後,你以為就完了嗎,不,我們使用Lambda表示式對其進行優化

		// 優化三:
		// Lambda表示式
		List<Employee> filterEmployeeByAge2 = filterEmployee(list, (e) -> e.getAge() > 35);
		System.out.println(filterEmployeeByAge2);

		List<Employee> filterEmployeeByWage2 = filterEmployee(list, (e) -> e.getSalary() > 5000);
		System.out.println(filterEmployeeByWage2);

感覺很秒吧,上述兩個需求,四行程式碼就實現了。 其實還有更妙的。那就是Stream Api + Lambda表示式

		// 優化方式四:Stream API
		list.stream().filter((e) -> e.getAge() > 35).forEach(System.out::println);
		list.stream().filter((e) -> e.getSalary() > 5000).forEach(System.out::println);

到這裡 ,有沒有提起你想學習的興趣?

什麼是Lambda 表示式?

Lambda 表示式的基礎語法: Java8中引入了一個新的操作符 ->,該操作符稱為箭頭操作符或Lambda操作符 ,箭頭操作符將Lambda拆分為兩部分,
左側: Lambda 表示式的引數列表 也就是抽象方法所需要的引數
右側: Lambda 表示式所需執行的功能 即Lambda體 是對抽象方法的實現

注意

  • Lambda 表示式的引數列表資料型別可以省略不寫,因為JVM編譯器通過上下文推斷出資料型別,即'型別推斷'
  • 只有在函式式介面中才可以使用Lambda表示式。

函式式介面: 介面中只有一個方法的介面,稱為函式式介面。可以使用註解@FunctionalInterface 修飾 可以檢查是否是函式式介面

語法格式一:

// 語法格式一 : 無參 無返回值 () -> System.out.printLn(“hello”);

	public static void test1() {

		// 匿名內部類
		Runnable r = new Runnable() {
			@Override
			public void run() {
				System.out.println("Hello");
			}
		};
		r.run();

		System.out.println("------------------------------------");

		// Lambda
		Runnable r1 = () -> System.out.println("Hello Lambda!");
		r1.run();
	}

語法格式二

// 語法格式二: 有一個引數並且無返回值 (num) -> System.out.printLn(num); 當引數只有一個時,小括號可省略不寫

	public void test2() {
		// Consumer<String> con = (x) -> System.out.println(x);
		Consumer<String> con = x -> System.out.println(x);
		con.accept("Hello Lambda!");
	}

語法格式三

語法格式三: 有兩個或以上引數 ,有返回值, 並且Lambda體中有多條語句 (a,b) -> {};

	public void test3() {
		Comparator<Integer> com = (x, y) -> {
			System.out.println("函式式介面");
			return Integer.compare(x, y);
		};
	}

語法格式四

// 語法格式四: 有兩個或以上引數 ,有返回值, 並且Lambda體中只有一條語句 return 以及 {} 可以省略;

	public void test4() {
		Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
	}

下面是對其簡單的使用。
新建一個介面 並加上 @FunctionalInterface 註解

@FunctionalInterface
public interface MyFun {
	public Integer getValue(Integer value);
}

曾加一個對其提供支援的方法。

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

使用:

	public static void test5() {
		Integer operation = operation(100, (x) -> x * 2);
		System.out.println(operation); // 200
	}

我們發現 ,需要使用這個表示式,就必須要有介面對它的支援。當然 ,Java8 也內建了。

四大核心函式式介面

	 * Consumer<T> : 消費型介面 - void accept(T t); 一個引數 無返回值
	 * 
	 * Supplier<T> : 供給型介面 -T get(); 無引數 有返回值
	 * 
	 * Function<T,R> : 函式型介面- R apply(T t);
	 * 
	 * Predicate<T> : 斷言型介面- boolean test(T t);

Consumer :

	// Consumer 消費型介面
	public static void happy(double money, Consumer<Double> co) {
		co.accept(money);
	}
	public static void test1() {
		happy(100, (m) -> System.out.println("消費了" + m + "元"));
	}

Supplier:

// Supplier<T> : 供給型介面
	// 當前需求:產生指定個數的整數。並放入集合中
	public static List<Integer> getNumList(int num, Supplier<Integer> sup) {
		List<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < num; i++) {
			int n = sup.get();
			list.add(n);
		}
		return list;
	}
	
	public static void test2() {
		System.out.println(getNumList(1000, () -> (int) (Math.random() * 100)));
	}

Function:

	// 當前需求 傳入str 返回 str 長度
	public static Integer strHandler(String str, Function<String, Integer> fun) {
		return fun.apply(str);
	}
	public static void test3() {
		System.out.println(strHandler("Hello Lambda", (str) -> str.length()));
	}	

Predicate :

// Predicate<T> : 斷言型介面
	public static List<String> filterStr(List<String> list, Predicate<String> pre) {
		ArrayList<String> arrayList = new ArrayList<String>();
		for (String item : list) {
			if (pre.test(item)) {
				arrayList.add(item);
			}
		}
		return arrayList;
	}
	public static void test4() {
		List<String> strList = Arrays.asList("S575751111tr1", "str57575555fds", "fjsdlaf", "fdsla575##");
		List<String> filterStr = filterStr(strList, (str) -> str.length() > 10);
		System.out.println(filterStr);
	}

方法引用

一:方法引用: 若Lambda 體中的內容有方法已經實現了,我們可以使用“方法引用” (可以理解為方法引用是Lambda 表示式的另一種表現形式)

主要有三種語法格式:

  • 物件 :: 例項方法名
  • :: 例項方法名
  • :: 靜態方法名

*1. Lambda 體中呼叫方法的引數列表與返回值型別,要與函式式介面中的抽象方法的引數列表以及返回值一致
*
* 2.若 Lambda 引數列表的第一個引數是 例項方法的呼叫者 ,而第二個引數是例項方法的引數時,可以使用 Class Name :: method

	// 物件 :: 例項方法名
	public static void test1() {
		// Consumer<String> con = (str) -> System.out.println(str);
		// 抽象介面方法的引數與返回值要與 方法引用中的一致
		Consumer<String> con = System.out::println;
		con.accept("Hello");
	}
	public static void test2() {
		Employee emp = new Employee();
		// Supplier<Integer> sup = () -> emp.getAge();

		Supplier<Integer> sup = emp::getAge;
		sup.get();
	}
	// 類 :: 靜態方法名
	public static void test3() {
		// Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
		Comparator<Integer> com = Integer::compare;
	}
	// 類 :: 例項方法名
	public static void test4() {
		// BiPredicate<String, String> bp = (x, y) -> x.equals(y);
		BiPredicate<String, String> bp = String::equals;
	}

構造器引用:

需要呼叫的構造器的引數列表要與函式式介面中抽象方法的引數列表保持一致
* 格式:
* ClassName :: new


	public static void test5() {
		Supplier<Employee> sup = () -> new Employee();
		// 構造器引用方式
		Supplier<Employee> sup2 = Employee::new;
		Employee emp = sup2.get();
	}
	public static void test6() {
		// Function<Integer, Employee> fun = (x) -> new Employee(x);
		// Employee employee = fun.apply(10);

		// 呼叫哪個構造器 取決於函式式介面的引數列表
		Function<Integer, Employee> fun = Employee::new;
		Employee employee = fun.apply(100); 
		System.out.println(employee);
		
	}

陣列引用:

Type :: new ;

	// 陣列引用
	public static void test7() {
		// Function<Integer, String[]> fun = (x) -> new String[x];
		// String[] apply = fun.apply(10);

		Function<Integer, String[]> fun = String[]::new;
		String[] apply = fun.apply(20);

		System.out.println(apply.length);
	}

流(Stream) 是什麼?

是資料渠道,用於操作資料來源(集合、陣列等)所生成的元素序列
“集合講的是資料,流講的是計算”

一. 建立流的方式

  • 可以通過Collection 系列集合提供的Stream() 或 parallelStream();;;Stream 是序列的 ,parallelStream是並行
		List<String> list = new ArrayList<String>();
		Stream<String> stream1 = list.stream();
  • 可以通過Arrays中的靜態方法 stream() 獲取陣列流
		Employee[] emp = new Employee[10];
		Stream<Employee> stream2 = Arrays.stream(emp);
  • 通過Stream類中的靜態方法of()
		Stream<String> stream3 = Stream.of("aa", "bb", "cc");
  • 建立無限流
		// 迭代 ,從零開始 一直返回加2的數 2 4 6 ... 直到終止操作
		Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
		stream4.forEach(System.out::println);

		// 生成
		Stream<Double> stream5 = Stream.generate(() -> Math.random());
		stream5.forEach(System.out::println);

2.1-中間操作-篩選與切片

static List<Employee> employee = Arrays.asList(new Employee("張三", 2888.8, 40), new Employee("張三", 2888.6, 40),
			new Employee("李四", 7815.9, 25), new Employee("王五", 6545.0, 45), new Employee("趙六", 5671.8, 46),
			new Employee("田七", 5784.8, 29));

filter - 接收Lambda ,從流中排除某寫元素

	// filter
	public static void test1() {
		employee.stream().filter((em) -> em.getAge() > 30).forEach(System.out::println);
	}

在這裡插入圖片描述

limit - 截斷流 ,使元素不超過給定數量

	// limit
	public static void test2() {
		employee.stream().limit(3).forEach(System.out::println);
	}

在這裡插入圖片描述

  • skip(n) 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空 流。
	// skip
	public static void test3() {
		employee.stream().skip(3).forEach(System.out::println);
	}

在這裡插入圖片描述

  • distinct - 去重,通過流所生成元素的hashcode() 和 equals() 去除重複元素
	public static void test4() {
		employee.stream().distinct().forEach(System.out::println);
	}

在這裡插入圖片描述

2.2-中間操作-對映

map - 接受Lambda ,將元素轉換成其他形式或提取資訊。接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素。
flatMap - 接收一個函式作為函式,將流中的每個值都換成新的一個流,然後把所有流連線成一個流

	// map-flatMap
	public static void test5() {
		List<String> asList = Arrays.asList("aaa", "bbb", "ccc", "ddd");
		asList.stream().map((str) -> str.toUpperCase()).forEach(System.out::println);

		System.out.println("----------------------");
		employee.stream().map(Employee::getName).forEach(System.out::println);

		System.out.println("-------------");
		// 需求:把asList中的字串一個一個提取出來 用map實現
		 // {{a,a,a},{b,b,b},{c,c,c}...}
		Stream<Stream<Character>> stream = asList.stream().map(TestStream2::filterCharacter);
		stream.forEach((sm) -> {
			sm.forEach(System.out::println);
		});

		System.out.println("=============================");
		// flatMap實現 接收一個函式作為函式,將流中的每個值都換成新的一個流,然後把所有流連線成一個流
		// {a,a,a,b,b,b,c,c,c...}
		Stream<Character> sm = asList.stream().flatMap(TestStream2::filterCharacter); 
		sm.forEach(System.out::println);
	}

	public static Stream<Character> filterCharacter(String str) {
		List<Character> charList = new ArrayList<>();
		for (Character item : str.toCharArray()) {
			charList.add(item);
		}
		return charList.stream();
	}

map 與 flatMap 的區別

	public static void test6() {
		// map 與 flatMap 的區別 就類似於 集合中的add 與 addAll的關係,
		// add 把整個物件新增到當前物件中,
		// addAll把物件作為元素,合併到當前物件中
		List<String> list = Arrays.asList("aaa", "bbb");
		List list2 = new ArrayList();

		list2.add(1);
		list2.add(2);

		list2.add(list); // [1, 2, [aaa, bbb]]

		// list2.addAll(list); // [1, 2, aaa, bbb]

		System.out.println(list2);

	}

2.3-中間操作-排序

sorted() - 自然排序(Comparable)
*
* sorted(Comparator com) - 定製排序

	// sorted()
	public static void test7() {
		List<String> list = Arrays.asList("zzz", "ddd", "aaa", "bbb", "ccc");
		list.stream().sorted().forEach(System.out::println);

		System.out.println("=================");
		// 先按年齡排,年齡一樣 按姓名排
		employee.stream().sorted((e1, e2) -> {
			if (e1.getAge().equals(e2.getAge())) {
				return e1.getName().compareTo(e2.getName());
			}
			return e2.getSalary().compareTo(e1.getSalary());
		}).forEach(System.out::println);
	}

3.終止操作 - 查詢與匹配

  • allMatch - 檢查是否匹配所有元素
    *
    * anyMatch - 檢查是否至少匹配一個元素
    *
    * noneMatch - 檢查元素是否沒有匹配元素
    *
    * findFirst - 返回第一個元素
    *
    * findAny - 返回當前流中的任意元素
    *
    * count - 返回流中元素的總個數
    *
    * max - 返回流中的最大值
    *
    * min - 返回流中最小值
	static List<Employee> employee = Arrays.asList(new Employee("張三", 2888.8, 40, Status.UNWANTED),
			new Employee("張三", 2888.6, 40, Status.BUSYED), new Employee("李四", 7815.9, 25, Status.UNWANTED),
			new Employee("王五", 6545.0, 45, Status.RESTED), new Employee("趙六", 5671.8, 46, Status.BUSYED),
			new Employee("田七", 5784.8, 29, Status.RESTED));

	public static void test1() {
		// allMatch - 檢查是否匹配所有元素
		// 返回布林值 檢查是否所有元素都是xx
		boolean allMatch = employee.stream().allMatch((em) -> Status.BUSYED.equals(em.getStatus()));
		System.out.println(allMatch);

		// anyMatch - 檢查是否至少匹配一個元素
		// 返回布林值 檢查是否存在至少一個xx元素
		boolean anyMatch = employee.stream().anyMatch((em) -> Status.RESTED.equals(em.getStatus()));
		System.out.println(anyMatch);

		// noneMatch - 檢查元素是否沒有匹配元素
		boolean noneMatch = employee.stream().noneMatch((em) -> Status.BUSYED.equals(em.getStatus()));
		System.out.println(noneMatch);

		// findFirst - 返回第一個元素
		// 按工資排序 返回第一個
		Optional<Employee> findFirst = employee.stream().sorted((e1, e2) -> -e1.getSalary().compareTo(e2.getSalary()))
				.findFirst();
		System.out.println(findFirst.get());

		// findAny - 返回當前流中的任意元素
		Optional<Employee> findAny = employee.stream().filter((em) -> Status.BUSYED.equals(em.getStatus())).findAny();
		System.out.println(findAny.get());

		// count - 返回流中元素的總個數
		long count = employee.stream().filter((em) -> em.getAge() > 30).count();
		System.out.println(count);

		// max 返回流中的最大值
		Employee employee2 = employee.stream().max((e1, e2) -> e1.getSalary().compareTo(e2.getSalary())).get();
		System.out.println(employee2.getSalary());

		// min 返回流中的最小值
		Integer min = employee.stream().map(Employee::getAge).min(Integer::compare).get();
		System.out.println(min);
	}

3.1.終止操作 - 歸約

  • *歸約
    * reduce - 可以將流中元素反覆結合起來 ,得到一個值
	// reduce
	public static void test2() {
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
		// 起始值為 0
		// 第一次 傳入1 x = 0 , y = 1; 返回 1
		// 第二次 傳入2 x = 1 , y = 2; 返回3
		// 第三次 傳入3 x = 3 , y = 3; 返回6
		// x 等於上一次返回的值
		Integer total = list.stream().reduce(0, (x, y) -> x + y);
		System.out.println(total);

		System.out.println("--------------");
		// 例如: 計算所有員工工資的和
		Optional<Double> reduce = employee.stream().map(Employee::getSalary).reduce(Double::sum);
		System.out.println(reduce.get());
	}

3.2.終止操作 - 收集

收集
collect - 將流轉換為其他形式。接收一個Collector 介面的實現。用於給Stream中元素中彙總的方法

容器


	// collect
	// 需求 ,提取所有員工姓名 放到容器中
	public static void test3() {

		// 放到list中
		List<String> listName = employee.stream().map(Employee::getName).collect(Collectors.toList());
		listName.forEach(System.out::println);

		System.out.println("----------------------------");

		// 放到Set中
		Set<String> setName = employee.stream().map(Employee::getName).collect(Collectors.toSet());
		setName.forEach(System.out::println);

		System.out.println("----------------------------");

		// 放到HashSet中
		HashSet<String> hashSetName = employee.stream().map(Employee::getName)
				.collect(Collectors.toCollection(HashSet::new));
		hashSetName.forEach(System.out::println);

	}

求值。Stream 求值的方式及其多,下面是常用的

	public static void tes4() {
		// 總數
		Long count = employee.stream().collect(Collectors.counting());

		// 工資平均值
		Double avg = employee.stream().collect(Collectors.averagingDouble(Employee::getSalary));

		// 總和
		Double sum = employee.stream().collect(Collectors.summingDouble(Employee::getSalary));

		// 最大值 工資最高的員工
		Optional<Employee> max = employee.stream()
				.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
		Employee employee2 = max.get();
		// 最小值 公司工資最小值
		Optional<Double> min = employee.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compare));
		System.out.println(min.get());

		// ....

		System.out.println("===================");

		DoubleSummaryStatistics dss = employee.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
		System.out.println(dss.getAverage());
		System.out.println(dss.getCount());
		System.out.println(dss.getMax());
		System.out.println(dss.getSum());
		// .....

	}

分組

	// 分組
	public static void test5() {
		Map<Status, List<Employee>> groupBy = employee.stream().collect(Collectors.groupingBy(Employee::getStatus));
	}

多級分組

	// 多級分組
	public static void test6() {
	// 先按狀態分 ,再按年齡段分
		Map<Status, Map<String, List<Employee>>> groupBys = employee.stream()
				.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
					if (((Employee) e).getAge() <= 25) {
						return "青年";
					} else if (e.getAge() <= 45) {
						return "中年";
					} else {
						return "老年";
					}
				})));
	}

分割槽

	// 分割槽 ,true為一個區,false 為一個區
	public static void test7() {
		Map<Boolean, List<Employee>> collect = employee.stream()
				.collect(Collectors.partitioningBy((e) -> e.getSalary() > 5000));
	}

連線

	// 連線
	public static void test8() {
		String collect = employee.stream().map(Employee::getName).collect(Collectors.joining("-"));
	}