1. 程式人生 > >java8新特性 lambda Stream map(函數語言程式設計)

java8新特性 lambda Stream map(函數語言程式設計)

1.介面的預設方法
	Java 8允許我們給介面新增一個非抽象的方法實現,只需要使用 default關鍵字即可,這個特徵又叫做擴充套件方法
	//Formula表示一個設計 計算公式 的介面 
	public interface Formula {
		//計算
		double calculate(int a);
		
		//開方
		default double sqrt(int a){
			return Math.sqrt(a);
		}
	}

	main:
	Formula f = new Formula() {
		@Override
		public double calculate(int a) {
			return a+1;
		}
	};
	System.out.println(f.calculate(4));
	System.out.println(f.sqrt(8));

	注意:現在介面還可以存在靜態方法,
	可以使用 介面名.靜態方法名 的形式直接呼叫


2.Lambda 表示式
	2.1 認識Lambda表示式
	例如:
	public class LambdaTest1 {
	
		public static void main(String[] args) {
			
			//假如一個list機會中的元素要排序
			List<String> list = Arrays.asList
			("hello","tom","apple","bbc");
			//之前的排序我們可以這樣寫
			Collections.sort(list, new Comparator<String>(){
				@Override
				public int compare(String o1, String o2) {
					return -o1.compareTo(o2);
				}
			});
			
			//使用Lambda表示式
			Collections.sort(list,(String s1,String s2)->{
				return s1.compareTo(s2);
			});
			
			//可以簡寫為
			//1.大括號裡面就一句程式碼
			//2.編譯器可以自動推匯出引數型別
			Collections.sort(list,(s1,s2)->s1.compareTo(s2));
			
			System.out.println(list);
			
		}
		
	}

	2.2 Functional介面
	“函式式介面”是指僅僅只包含一個抽象方法的介面,每一個該型別的lambda表示式都會被匹配到這個抽象方法。因為 預設方法 不算抽象方法,所以你也可以給你的函式式介面新增預設方法。
	我們可以將lambda表示式當作任意只包含一個抽象方法的介面型別,確保你的介面一定達到這個要求,你只需要給你的介面新增 @FunctionalInterface 註解,編譯器如果發現你標註了這個註解的介面有多於一個抽象方法的時候會報錯的。
	例如:
	public class LambdaTest2 {
	
		public static void main(String[] args) {
			
			LambdaTest2 t = new LambdaTest2();
	//		也可以先建立物件
	//		Action1 a1 = ()->System.out.println("hello");
			
			t.test1(()->System.out.println("hello"));
			
			//Action2<String,Integer> a2 = (f)->"這個數字是:"+f;
			//如果引數就一個,那麼還可以這樣簡寫 去掉小括號
			Action2<String,Integer> a2 = f->"這個數字是:"+f;
			t.test2(a2);
		}
		public void test1(Action1 a){
			a.run();
		}
		public void test2(Action2<String,Integer> a){
			System.out.println(a.run(3));
		}
		
	}
	//這個註解不加也可以,加上只是為了讓編譯器檢查
	@FunctionalInterface
	interface Action1{
		public void run();
	}
	
	//這個註解不加也可以,加上只是為了讓編譯器檢查
	@FunctionalInterface
	interface Action2<T,F>{
		public T run(F f);
	}
	

	注意:lambda表示式無法訪問介面的預設方法

	2.3 方法與建構函式引用
	Java 8 允許你使用 :: 關鍵字來傳遞方法(靜態方法和非靜態方法)
	例如:
	public class LambdaTest3 {
		public static void main(String[] args) {
			
			LambdaTest3 t = new LambdaTest3();
			//使用Lambda引用類的靜態方法
			//能引用Integer類中的靜態方法toBinaryString的原因是:
			//Action3介面中只有一個方法且方法的引數型別和返回值型別
			//與Integer類中的靜態方法toBinaryString的引數型別、返回型別是一致的
			Action3 a3 = Integer::toBinaryString;
			System.out.println(a3.run(4));
			
			//使用Lambda引用物件的非靜態方法
			//能引用物件t中的非靜態方法test的原因是和上面的描述是一致的
			Action3 aa3 = t::test;
			System.out.println(aa3.run(4));
		}
		
		public String test(int i){
			return "i="+i;
		}
	}

	@FunctionalInterface
	interface Action3{
		public String run(int Integer);
	}


	下面是一個介面中帶泛型的時候特殊例子: 可以使用  類名::非靜態方法  的形式引用方法
	public class LambdaTest6 {
	
		public static void main(String[] args) {
			
			Model m = new Model();
			
			//方法有一個引數,然後沒返回型別,這裡引數型別會自動識別
			Action<Model> a1 = (s)->System.out.println("hello");
			a1.run(m);
			
			//注意:如果這裡泛型型別不是Model 那麼就不能引用Model中的方法
			//可以引用Model類中任意方法 只要滿足一點:該方法沒有引數
			//將來run方法中就會呼叫Model型別物件m的此處引用的方法
			Action<Model> a2 = Model::test3;
			a2.run(m);
			
			//引用物件m中的test2方法
			//因為test2方法的引數和返回型別和Action介面的方法完全一致
			Action<Model> a3 = m::test2;
			a3.run(m);
		}
		
	}

	interface Action<T>{
		public void run(T t);
	}

	class Model{
		
		public void test1(){
			System.out.println("test1");
		}
		public void test2(Model a){
			System.out.println("test2");
		}
		public int test3(){
			System.out.println("test3");
			return 1;
		}
	}
	

	Java 8 允許你使用 :: 關鍵字來引用建構函式
	public class LambdaTest4 {
		
		public static void main(String[] args) {
			
			//Lambda表示式引用建構函式
			//根據構造器的引數來自動匹配使用哪一個構造器
			Action4Creater creater = Action4::new;
			Action4 a4 = creater.create("zhangsan");
			a4.say();
			
			
		}
		
	}

	class Action4{
		private String name;
		public Action4() {
			
		}
		public Action4(String name) {
			this.name = name;
		}
		public void say(){
			System.out.println("name = "+name);
		}
	}

	interface Action4Creater{
		public Action4 create(String name);
	}

	2.4 lambda表示式中的變數訪問
	public class LambdaTest5 {
		private static int j;
		private int k;
		public static void main(String[] args) {
			LambdaTest5 t = new LambdaTest5();
			t.test();
		}
		
		public void test(){
			int num = 10;
			j = 20;
			k = 30;
			
			//lambda表示式中可以訪問成員變數也可以方法區域性變數
			Action5 a5 = (i)->System.out.println("操作後:i="+(i+num+j+k));
			a5.run(1);
			
			//但是這個被訪問的變數預設變為final修飾的 不可再改變 否則編譯不通過
			//num = 60;
			j = 50;
			k = 70;
		}
		
	}

	interface Action5{
		public void run(int i);
	}
	
	
	2.5 Predicate介面和lambda表示式
	java.util.function.Predicate介面是用來支援java函數語言程式設計新增的一個介面,使用這個介面和lamb表示式就可以以更少的程式碼為API方法新增更多的動態行為。 
	public class LambdaTest6 {
		public static void main(String[] args) {
			List<String> languages = Arrays.asList("Java", "html5","JavaScript", "C++", "hibernate", "PHP");
			
			//開頭是J的語言
			filter(languages,(name)->name.startsWith("J"));
			//5結尾的
			filter(languages,(name)->name.endsWith("5"));
			//所有的語言
			filter(languages,(name)->true);
			//一個都不顯示
			filter(languages,(name)->false);
			//顯示名字長度大於4
			filter(languages,(name)->name.length()>4);
			System.out.println("-----------------------");
			//名字以J開頭並且長度大於4的
			Predicate<String> c1 = (name)->name.startsWith("J");
			Predicate<String> c2 = (name)->name.length()>4;
			filter(languages,c1.and(c2));
			
			//名字不是以J開頭
			Predicate<String> c3 = (name)->name.startsWith("J");
			filter(languages,c3.negate());
			
			//名字以J開頭或者長度小於4的
			Predicate<String> c4 = (name)->name.startsWith("J");
			Predicate<String> c5 = (name)->name.length()<4;
			filter(languages,c4.or(c5));
			
			//名字為Java的
			filter(languages,Predicate.isEqual("Java"));
			
			//判斷倆個字串是否相等
			boolean test = Predicate.isEqual("hello").test("world");
			System.out.println(test);
		}
		public static void filter(List<String> languages, Predicate<String> condition) {  
			for(String name: languages) {  
				if(condition.test(name)) {  
					System.out.println(name + " ");  
				}  
			}  
		}  
		
	}
	
	2.6 Function 介面
		 Function有一個引數並且返回一個結果,並附帶了一些可以和其他函式組合的預設方法
		 compose方法表示在某個方法之前執行
		 andThen方法表示在某個方法之後執行
		 注意:compose和andThen方法呼叫之後都會把物件自己本身返回,這可以方便鏈式程式設計
		 default <V> Function<T,V> andThen(Function<? super R,? extends V> after) 返回一個先執行當前函式物件apply方法再執行after函式物件apply方法的函式物件。

		default <V> Function<T,V> compose(Function<? super V,? extends T> before)返回一個先執行before函式物件apply方法再執行當前函式物件apply方法的函式物件。

		static <T> Function<T,T> identity() 返回一個執行了apply()方法之後只會返回輸入引數的函式物件。
	public interface Function<T, R> {

		R apply(T t);

		default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
			Objects.requireNonNull(before);
			return (V v) -> apply(before.apply(v));
		}

		default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
			Objects.requireNonNull(after);
			return (T t) -> after.apply(apply(t));
		}
		
		//注意: t->t是(t)->t的簡寫
		//t->t是作為方法identity的返回值的,也就是Function型別物件
		//類似於這樣的寫法:Function<Object, Object> f = t->t;
		//那麼f.apply("test") 返回字串"test"
		//傳入什麼則返回什麼
		static <T> Function<T, T> identity() {
			return t -> t;
		}
	}

	例如:
	public class LambdaTest7 {
		//靜態內部類
		private static class Student{
			private String name;
			public Student(String name){
				this.name = name;
			}
			public String getName() {
				return name;
			}
			
		}
		public static void main(String[] args) {
			/*使用者註冊輸入一個名字tom*/
			String name = "tom";
			
			/*使用使用者的輸入的名字建立一個物件*/
			Function<String, Student> f1 =(s)->new Student(s);
			//注意上面的程式碼也可以寫出這樣,引用類中的構造器
			//Function<String, Student> f1 =Student::new;
			Student stu1 = f1.apply(name);
			System.out.println(stu1.getName());
			
			/*需求改變,使用name建立Student物件之前需要給name加一個字首*/
			Function<String,String> before = (s)->"briup_"+s;
			//表示f1呼叫之前先執行before物件的方法,把before物件的方法返回結果作為f1物件方法的引數
			Student stu2 = f1.compose(before).apply(name);
			System.out.println(stu2.getName());
			
			/*獲得建立好的物件中的名字的長度*/
			Function<Student,Integer> after = (stu)->stu.getName().length();
			//before先呼叫方法,結果作為引數傳給f1來呼叫方法,結果再作為引數傳給after,結果就是我們接收的資料
			int len = f1.compose(before).andThen(after).apply(name);
			System.out.println(len);
			
		}
		
	}        



	2.7 Supplier介面
	Supplier介面返回一個任意範型的值,和Function介面不同的是該介面沒有任何引數
	public interface Supplier<T> {
		T get();
	}
	例如:
	public class LambdaTest8 {
		public static void main(String[] args) {
			//生成一個八位的隨機字串
			Supplier<String> f = ()->{
				String base = "abcdefghijklmnopqrstuvwxyz0123456789";     
				Random random = new Random();     
				StringBuffer sb = new StringBuffer();     
				for (int i = 0; i < 8; i++) {  
					//生成[0,base.length)之間的隨機數
					int number = random.nextInt(base.length());     
					sb.append(base.charAt(number));     
				}     
				return sb.toString();   
			};
			System.out.println(f.get());
		}
		
	}


	2.8 Consumer介面
	Consumer介面接收一個任意範型的值,和Function介面不同的是該介面沒有任何值
	public interface Consumer<T> {

		void accept(T t);

		default Consumer<T> andThen(Consumer<? super T> after) {
			Objects.requireNonNull(after);
			return (T t) -> { accept(t); after.accept(t); };
		}
	}
	例如:
	public class LambdaTest9 {
		//靜態內部類
		private static class Student{
			private String name;

			public String getName() {
				return name;
			}

			public void setName(String name) {
				this.name = name;
			}
		}
		
		public static void main(String[] args) {
			Student s = new Student();
			s.setName("tom");
			
			Consumer<Student> c = 
			stu->System.out.println("hello!"+stu.getName());
			c.accept(s);
			
		}
		
	}

	總結:
		Function<T, R>  介面   R apply(T t);       有引數有返回值
		Supplier<T>       介面   T get();		  沒引數有返回值
		Consumer<T>    介面   void accept(T t); 有引數沒返回值

		另外需要注意的介面: 其用法和上面介紹的介面使用方式類同
		BinaryOperator<T>介面    T apply(T t, T t)  將兩個T作為輸入,返回一個T作為輸出
		BiFunction<T, U, R>介面  R apply(T t, U u)  將一個T和一個U輸入,返回一個R作為輸出
		BinaryOperator介面繼承了BiFunction介面
		public interface BinaryOperator<T> extends BiFunction<T,T,T>

		BiConsumer<T, U>介面  void accept(T t, U u) 將倆個引數傳入,沒有返回值

		

	2.9 Optional類
	Optional 不是介面而是一個類,這是個用來防止NullPointerException異常的輔助型別
	Optional 被定義為一個簡單的容器,其值可能是null或者不是null。
	在Java8之前一般某個函式應該返回非空物件但是偶爾卻可能返回了null,而在Java 8中,不推薦你返回null而是返回Optional。
	這是一個可以為null的容器物件。
	如果值存在則isPresent()方法會返回true,呼叫get()方法會返回該物件。
	public class Optotion {
	
	public static void main(String[] args) {
		
		/*of方法 為非null的值建立一個Optional*/
		//of方法通過工廠方法建立Optional類。
		//需要注意的是,建立物件時傳入的引數不能為null。
		//如果傳入引數為null,則丟擲NullPointerException 。
		Optional<String> op1 = Optional.of("hello");
		
		/*ofNullable方法 為指定的值建立一個Optional,如果指定的值為null,則返回一個空的Optional。*/
		//ofNullable與of方法相似,唯一的區別是可以接受引數為null的情況
		Optional<String> op2 = Optional.ofNullable(null);
		
		/*isPresent方法 如果值存在返回true,否則返回false。*/
		/*get方法 如果Optional有值則將其返回,否則丟擲NoSuchElementException。*/
		if(op1.isPresent()){
			System.out.println(op1.get());
		}
		if(op2.isPresent()){
			System.out.println(op2.get());
		}
		
		/*ifPresent方法 如果Optional例項有值則為其呼叫consumer,否則不做處理*/
		//consumer介面中的方法只有引數沒有返回值
		op1.ifPresent(str->System.out.println(str));
		op2.ifPresent(str->System.out.println(str));//這個不執行 因為op2裡面的值是null
		
		
		/*orElse方法 如果有值則將其返回,否則返回指定的其它值。*/
		System.out.println(op1.orElse("如果op1中的值為null則返回這句話,否則返回這個值"));
		System.out.println(op2.orElse("如果op2中的值為null則返回這句話,否則返回這個值"));
		
		
		/*orElseGet方法 orElseGet與orElse方法類似,區別在於得到的預設值。orElse方法將傳入的字串作為預設值,orElseGet方法可以接受Supplier介面的實現用來生成預設值。*/
		//Supplier介面中的方法沒有引數但是有返回值
		System.out.println(op1.orElseGet(()->"自己定義的返回值"));
		System.out.println(op2.orElseGet(()->"自己定義的返回值"));
		
		
		/*orElseThrow方法 如果有值則將其返回,否則丟擲supplier介面建立的異常。*/
		//在orElseThrow中我們可以傳入一個lambda表示式或方法,如果值不存在來丟擲異常。
		//orElseThrow方法的宣告如下 所有隻能返回一個Throwable型別物件
		//public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
		try {
			System.out.println(op1.orElseThrow(Exception::new));;
			//System.out.println(op2.orElseThrow(Exception::new));;這個會丟擲異常
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
		/*map方法 如果有值,則對其執行呼叫mapper函式得到返回值。*/
		//返回值並且依然Optional包裹起來,其泛型和你返回值的型別一致
		//public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
		Optional<Integer> map1 = op1.map(str->1);
		System.out.println(map1.get());
		Optional<Double> map2 = op2.map(str->1.2);
		System.out.println(map2.orElse(0.0));
		
		
		/*flatMap方法 如果有值,為其執行mapper函式返回Optional型別返回值,否則返回空Optional。*/
		//flatMap與map方法類似,區別在於flatMap中的mapper返回值必須是Optional。呼叫結束時,flatMap不會對結果用Optional封裝。
		//需要我們自己把返回值封裝為Optional
		//public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 
		System.out.println(op1.flatMap(str->Optional.of(str+"_briup")).get());
		//op1.flatMap(str->"");編譯出錯
		
		
		/*filter方法 如果有值並且滿足斷言條件返回包含該值的Optional,否則返回空Optional。*/
		//public Optional<T> filter(Predicate<? super T> predicate) 
		op1 = op1.filter(str->str.length()<10);
		System.out.println(op1.orElse("值為null"));
		op1 = op1.filter(str->str.length()>10);
		System.out.println(op1.orElse("值為null"));
	}

	2.10 Stream 介面
	java.util.Stream 表示能應用在一組元素上一次執行的操作序列。
	Stream 操作分為中間操作或者最終操作兩種,最終操作返回一特定型別的計算結果,
	而中間操作返回Stream本身,這樣你就可以將多個操作依次串起來(鏈式程式設計)。
	Stream 的建立需要指定一個數據源,比如 java.util.Collection的子類,List或者Set, Map不支援。
	Stream的操作可以序列執行或者並行執行。
	Stream 作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念。
	Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、
	高效的聚合操作(aggregate operation),或者大批量資料操作 (bulk data operation)。
	Stream API 藉助於同樣新出現的Lambda表示式,極大的提高程式設計效率和程式可讀性。
	同時它提供序列和並行兩種模式進行匯聚操作

	2.10.1 Stream物件的構建:
	// 1.使用值構建
	Stream<String> stream = Stream.of("a", "b", "c");
	// 2. 使用陣列構建
	String[] strArray = new String[] {"a", "b", "c"};
	Stream<String> stream = Stream.of(strArray);
	Stream<String> stream = Arrays.stream(strArray);
	// 3. 利用集合構建(不支援Map集合)
	List<String> list = Arrays.asList(strArray);
	stream = list.stream();

	對於基本數值型,目前有三種對應的包裝型別 Stream:IntStream、LongStream、DoubleStream。
	當然我們也可以用 Stream<Integer>、Stream<Long> 、Stream<Double>,但是 自動拆箱裝箱會很耗時,所以特別為這三種基本數值型提供了對應的 Stream。
	Java 8 中還沒有提供其它基本型別數值的Stream

	2.10.2 數值Stream的構建:
	IntStream stream1 = IntStream.of(new int[]{1, 2, 3});
	//[1,3)
	IntStream stream2 = IntStream.range(1, 3);
	//[1,3]
	IntStream stream3 = IntStream.rangeClosed(1, 3);

	2.10.3 Stream轉換為其它型別:
	Stream<String> stream = Stream.of("hello","world","tom");
	// 1. 轉換為Array
	String[] strArray  = stream.toArray(String[]::new);
	// 2. 轉換為Collection
	List<String> list1 = stream.collect(Collectors.toList());
	List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
	Set<String> set3 = stream.collect(Collectors.toSet());
	Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
	// 3. 轉換為String
	String str = stream.collect(Collectors.joining()).toString();

	特別注意 : 一個 Stream 只可以使用一次,上面的程式碼為了簡潔而重複使用了多次。
	這個程式碼直接執行會丟擲異常的:
	java.lang.IllegalStateException: stream has already been operated upon or closed


	2.10.4 Stream操作
	當把一個數據結構包裝成Stream後,就要開始對裡面的元素進行各類操作了。常見的操作可以歸類如下。

	Intermediate:中間操作
	map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

	Terminal: 最終操作
	forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

	Short-circuiting: 短路操作
	anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit


	map/flatMap對映 把 Stream中 的每一個元素,對映成另外一個元素。
	例子:
	轉換大寫
	Stream<String> wordList = Stream.of("hello","world","tom");
	List<String> output = wordList.
			 		map(String::toUpperCase).
				  	collect(Collectors.toList());
  	//也可以直接使用forEach迴圈輸出
	wordList.map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);

	例子:
	計算平方數
	List<Integer> nums = Arrays.asList(1, 2, 3, 4);
	List<Integer> squareNums = 
				nums.stream().
				map(n -> n * n).
				collect(Collectors.toList());
	map生成的是個1:1對映,每個輸入元素,都按照規則轉換成為另外一個元素。還有一些場景,是一對多對映關係的,這時需要 flatMap。
	map和flatMap的方法宣告是不一樣的
	<R> Stream<R>      map(Function<? super T, ? extends R> mapper);
	<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

	例子:
	//stream1中的每個元素都是一個List集合物件
	Stream<List<Integer>> stream1 = Stream.of(
					 Arrays.asList(1),
					 Arrays.asList(2, 3),
					 Arrays.asList(4, 5, 6)
				 );
				Stream<Integer> stream2 = stream1.
				flatMap((e) -> e.stream());
				
	stream2.forEach(e->System.out.println(e));//輸出1 2 3 4 5 6
	flatMap 把 stream1 中的層級結構扁平化,就是將最底層元素抽出來放到一起,最終新的 stream2 裡面已經沒有 List 了,都是直接的數字。

	例子:
	Stream<String> stream1 = Stream.of("tom.Li","lucy.Liu");
	//flatMap方法把stream1中的每一個字串都用[.]分割成了倆個字串
	//最後返回了一個包含4個字串的stream2
	Stream<String> stream2 = stream1.flatMap(s->Stream.of(s.split("[.]")));
	stream2.forEach(System.out::println);
	輸出結果:
		tom
		Li
		lucy
		Liu



	forEach 遍歷 接收一個 Lambda 表示式,然後在 Stream 的每一個元素上執行該表示式。
	forEach 是 terminal 操作,執行完stream就不能再用了
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	list.stream().forEach(System.out::println);


	filter 過濾 對原始 Stream 進行某項測試,通過測試的元素被留下來生成一個新 Stream。
	通過一個predicate介面來過濾並只保留符合條件的元素,該操作屬於中間操作,所以我們可以在過濾後的結果來應用其他Stream操作(比如forEach)。forEach需要一個函式來對過濾後的元素依次執行。forEach是一個最終操作,所以我們不能在forEach之後來執行其他Stream操作
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	list.stream().filter(s->s.length()>4).forEach(System.out::println);
	注意:System.out::println 這個是lambda表示式中對靜態方法的引用


	peek 對每個元素執行操作並返回一個新的 Stream
	注意:呼叫peek之後,一定要有一個最終操作
	peek是一個intermediate 操作
	例子:
	List<String> list = Arrays.asList("one", "two", "three", "four");
	List<String> list2 = list.stream()
				 .filter(e -> e.length() > 3)
				 .peek(e -> System.out.println("第一次符合條件的值為: " + e))
				 .filter(e->e.length()>4)
				 .peek(e -> System.out.println("第二次符合條件的值為: " + e))
				 .collect(Collectors.toList());
	System.out.println(list2.size());//列印結果為 1
	最後list2中就存放的篩選出來的元素


	findFirst 總是返回 Stream 的第一個元素,或者空,返回值型別:Optional。
	如果集中什麼都沒有,那麼list.stream().findFirst()返回一個Optional<String>物件,
		但是裡面封裝的是一個null。
	例子:
	List<String> list = Arrays.asList("test","hello","world");
	Optional<String> first = list.stream().findFirst();
	System.out.println(first.orElse("值為null"));


	sort 排序
	排序是一箇中間操作,返回的是排序好後的Stream。如果你不指定一個自定義的Comparator則會使用預設排序。
	對 Stream 的排序通過 sorted 進行,它比陣列的排序更強之處在於你可以首先對 Stream 進行各類 map、filter、limit、skip 甚至 distinct 來減少元素數量後,再排序,這能幫助程式明顯縮短執行時間。
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	list.stream().sorted().filter(s->s.startsWith("j")).forEach(System.out::println);
	//按照字串的長短排序
	list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);
	需要注意的是,排序只建立了一個排列好後的Stream,而不會影響原有的資料來源,排序之後原資料list是不會被修改的:


	Map 對映
	中間操作map會將元素根據指定的Function介面來依次將元素轉成另外的物件,
		下面的示例展示了將字串轉換為大寫字串。
		你也可以通過map來講物件轉換成其他型別,
		map返回的Stream型別是根據你map傳遞進去的函式的返回值決定的。
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	list.stream().map(s->s.toUpperCase()).forEach(System.out::println);


	Match 匹配
	Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。
	所有的匹配操作都是最終操作,並返回一個boolean型別的值。
	//所有元素匹配成功才返回true 否則返回false
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	boolean allMatch = list.stream().allMatch((s)->s.startsWith("j"));
	System.out.println(allMatch);

	//任意一個匹配成功就返回true 否則返回false
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	boolean anyMatch = list.stream().anyMatch((s)->s.startsWith("j"));
	System.out.println(anyMatch);

	//沒有一個匹配的就返回true 否則返回false
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	boolean noneMatch = list.stream().noneMatch((s)->s.startsWith("j"));
	System.out.println(noneMatch);


	Count 計數
	計數是一個最終操作,返回Stream中元素的個數,返回值型別是long。
	例子:
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	long count = list.stream().filter(s->s.startsWith("j")).count();
	System.out.println(count);


	Reduce 規約/合併
	這是一個最終操作,允許通過指定的函式來將stream中的多個元素規約合併為一個元素.
	它提供一個起始值(種子),然後依照運算規則(BinaryOperator),
		和前面 Stream 的第一個、第二個、第 n 個元素組合。Stream.reduce,
		常用的方法有average, sum, min, max, and count,返回單個的結果值,
		並且reduce操作每處理一個元素總是建立一個新值.
		從這個意義上說,字串拼接、數值的 sum、min、max等都是特殊的 reduce。
		例如 Stream 的 sum 就相當於
	IntStream integers = IntStream.range(1, 10);
	Integer sum = integers.reduce(0, (a, b) -> a+b); 或
	Integer sum = integers.reduce(0, Integer::sum);
	也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。
	OptionalInt min = integers.reduce((a, b) -> a<b?a:b);
	// 字串連線,concat = "ABCD"
	String concat 		 = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
	Optional<String> opStr = Stream.of("A", "B", "C", "D").reduce(String::concat); 

	例子:
	List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
	Optional<String> reduce = list.stream().sorted((s1,s2)->s2.length()-s1.length()).filter(s->s.startsWith("j")).map(s->s+"_briup").reduce((s1,s2)->s1+"|"+s2);
	System.out.println(reduce.orElse("值為空"));//列印結果為: javascript_briup|javap_briup|java_briup
	整個程式碼有點長,可以換行看下:
	Optional<String> reduce    =  list.stream()
					  .sorted((s1,s2)->s2.length()-s1.length())
					  .filter(s->s.startsWith("j"))
					  .map(s->s+"_briup")
					  .reduce((s1,s2)->s1+"|"+s2);
		1.先呼叫stream方法
		2.再排序,按照字串的長度進行排序,長的在前短的再後
		3.再過濾,字串必須是以字元'j'開頭的
		4.再進行對映,把每個字串後面拼接上"_briup"
		5.再呼叫reduce進行合併資料,使用"|"連線字串
		6.最後返回Optional<String>型別資料,處理好的字串資料就封裝在這個物件中				  



	limit/skip 
	limit 返回 Stream 的前面 n 個元素;skip 則是跳過前 n 個元素只要後面的元素
	例子:
	List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
	list.stream().limit(5).forEach(System.out::println);
	list.stream().skip(5).forEach(System.out::println);




	min/max/distinct
	例子:
	找出字元檔案中字元字元最長的一行
	BufferedReader br = new BufferedReader(new FileReader("src/com/briup/test/a.txt"));
	int maxLen = br.lines().
		   	mapToInt(String::length).
		   	max().
		   	getAsInt();

	System.out.println(maxLen);	 
	注意:lines方法把檔案中所有行都返回並且轉換為一個Stream<String>型別物件,因為每行讀出的String型別資料,同時String::length是使用方法引用的特殊方式(因為泛型的緣故),上面的筆記中已經介紹過了,max()方法執行後返回的時候OptionalInt型別物件,所以接著呼叫了getAsInt方法來獲得這次執行結果的int值

	例子:
	找出全文的單詞,轉小寫,去掉空字元,去除重複單詞並排序
	BufferedReader br = new BufferedReader(new FileReader("src/com/briup/test4/day17.txt"));
	br.lines().
	   flatMap(s->Stream.of(s.split(" "))).
	   filter(s->s.length()>0).
	   map(s->s.toLowerCase()).
	   distinct().
	   sorted().
	   forEach(System.out::println);
	


	Stream.generate
	通過Supplier介面,可以自己來控制Stream的生成。這種情形通常用於隨機數、常量的 Stream,或者需要前後元素間維持著某種狀態資訊的 Stream。把 Supplier 例項傳遞給 Stream.generate() 生成的 Stream,由於它是無限的,在管道中,必須利用limit之類的操作限制Stream大小。可以使用此方式製造出海量的測試資料
	public static<T> Stream<T> generate(Supplier<T> s);
	例子:
	生成100個隨機數並由此創建出Stream例項
	Stream.generate(()->(int)(Math.random()*100)).limit(100).forEach(System.out::println);
		


	Stream.iterate
	iterate 跟 reduce 操作很像,接受一個種子值,和一個 UnaryOperator(假設是 f)。
		然後種子值成為 Stream 的第一個元素,f(seed) 為第二個,f(f(seed)) 第三個,
		f(f(f(seed))) 第四個,以此類推。
	該方法的宣告為:
	public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

	UnaryOperator介面繼承了Function介面:
	public interface UnaryOperator<T> extends Function<T, T>
	例子:
	生成一個等差數列
	Stream.iterate(0, n -> n + 3).
				limit(10). 
				forEach(x -> System.out.print(x + " "));
	列印結果:
	0 3 6 9 12 15 18 21 24 27 




	Collectors 
	java.util.stream.Collectors 類的主要作用就是輔助進行各類有用的操作。
	例如把Stream轉變輸出為 Collection,或者把 Stream 元素進行分組。
	例子:
	把Stream中的元素進行過濾然後再轉為List集合
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	List<String> result = list.stream().filter(s->s.length()>4).collect(Collectors.toList());

	//分組:按照字串的長度分組
	List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
	//相同長度的字串放到一個List集合中作為Map的value,字串的長度作為Map的Key
	Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(String::length));
	//注意下面寫法可能寫到s->s.length()的時候Eclipse裡面有可能不會程式碼提示,這個要看你先的是=號的哪一邊
	//最終原因還是泛型的事情
	Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(s->s.length()));
		
	//分割:按照字串是否包含java進行劃分  partitioning分割劃分的意思
	Map<Boolean, List<String>> collect = 
			list.stream().collect(Collectors.partitioningBy(s->s.indexOf("java")!=-1));
	for(Boolean b:collect.keySet()){
		System.out.println(b+" : "+collect.get(b).size());
	}


	2.11 並行Streams
	Stream有序列和並行兩種,序列Stream上的操作是在一個執行緒中依次完成,而並行Stream則是在多個執行緒上同時執行。
	例子:
	public class LambdaTest12 {
	
		public static void main(String[] args) {
			
			//生成100萬個不同的字串放到集合中
			int max = 1000000;
			List<String> values = new ArrayList<String>(max);
			for (int i = 0; i < max; i++) {
			    UUID uuid = UUID.randomUUID();
			    values.add(uuid.toString());
			}


			//1納秒*10^9=1秒 
			long t0 = System.nanoTime();
			//序列stream 
			long count = values.stream().sorted().count();
			//並行stream
			//long count = values.parallelStream().sorted().count();
			long t1 = System.nanoTime();

			long time = t1 - t0;
			System.out.println(count);
			System.out.println(time);
		}
		
	}

	結論:對100萬個字串進行排序和計數操作,序列和並行運算的用時差別還是很明顯的



	2.12 Map集合
	Map型別不支援stream,不過Map提供了一些新的有用的方法來處理一些日常任務。
	Java8為Map新增的方法:

	Object compute(Object key, BiFunction remappingFunction):該方法使用remappingFunction根據原key-value對計算一個新的value。只要新的value不為null,就使用新的value覆蓋原value;如果新的value為null,則刪除原key-value對;

	Object computeIfAbsent(Object key, Function mappingFunction):如果傳入的key引數在Map中對應的value為null,該方法將使用mappingFunction根據原key、value計算一個新的結果,則用該計算結果覆蓋原value;如果傳入的key引數在Map中對應的value為null,則該方法不做任何事情;如果原Map原來不包括該key,該方法可能會新增一組key-value對。

	Object computeIfPresent(Object key, BiFunction remappingFunction):如果傳給該方法的key引數在Map中對應的value不為null,該方法將使用remappingFunction根據原key、value計算一個新結果,並且該計算結果不為null,則使用該結果覆蓋原來的value;如果計算結果為null,則刪除原key-value對。

	void forEach(BiConsumer action):該方法是Java8為Map新增的一個遍歷key-value對的方法。

	Object getOrDefault(Object key, V defaultValue):獲取指定的key對應的value。如果該key不存在,則返回defaultValue。

	Object merge(Object key, Object value, BiFunction remappingFunction):該方法會先根據key引數獲取該Map中對應的value。如果獲取的value為null,則直接使用傳入的value覆蓋原value(在這種情況下,可能會新增一組key-value);如果獲取的value不為null,則使用remappingFunction函式根據原value、新value計算一個新的結果,並用新的結果去覆蓋原有的value。

	Object putIfAbsent(Object key, Object value):該方法會自動檢測指定的key對應的value是否為null,如果該key對應的value為null,則使用傳入的新value代替原來的null。如果該key對應的value不是null,那麼該方法不做任何事情。

	Object replace(Object key, Object value):將Map中指定key對應的value替換成新value並把被替換掉的舊值返回。如果key在Map中不存在,該方法不會新增key-value對,而是返回null。

	Boolean replace(K key, V oldValue, V newValue):將Map中指定的key-value對的原value替換成新value。如果在Map中找到指定的key-value對,則執行替換並返回true,否則返回false。

	replaceAll(BiFunction function):該方法使用function對原key-value對執行計算,並將計算結果作為key-value對的value值




牛刀小試:使用Java8新特性獲取股票資料
https://blog.csdn.net/u014646662/article/details/82936131