Java8的函數式編程

Java8中的函數式編程

函數式編程:是一種如何搭建應用程序的方法論。(lambda表達式+Stream流的思想)java

命令式編程和函數式編程的異同點:apache

  1. 命令式編程關注怎麼樣作,而函數式編程中關注作什麼編程

    import java.util.stream.IntStream;
    
     public class MinDemo {
    
     	public static void main(String[] args) {
     		int[] nums = {35,65,-55,100,-676,95};
     		
     		//命令式編程(具體怎麼作)
     		int min = Integer.MAX_VALUE;
     		for (int i : nums) {
     			if(i < min) {
     				min = i;
     			}
     		}
     		
     		System.out.println(min);
     		
     		//jdk8的函數式編,多線程並行處理(須要作什麼)
     		int min2 = IntStream.of(nums).parallel().min().getAsInt();
     		System.out.println(min2);
     	}
    
     }
    複製代碼
  2. 函數式編程讓代碼更加可讀bootstrap

    public class ThreadDemo {
     	public static void main(String[] args) {
     		//命令式編程中的寫法
     		Object target = new Runnable() {
     			@Override
     			public void run() {
     				System.out.println("新建了一個線程");
     			}
     		};
     		new Thread((Runnable) target).start();
     
     		// jdk8 lambda函數式編程中的寫法
     		Object target2 = (Runnable)() -> System.out.println("新建了一個線程");
     		Runnable target3 = () -> System.out.println("新建了一個線程");
     		System.out.println(target2 == target3); // false
     		
     		new Thread((Runnable) target2).start();
     	}
    
     }
    複製代碼
lambda表達式中返回一個接口須要知足的條件:
  1. 該接口中只能有一個方法數組

  2. 須要在接口上加上@FunctionalInterface註解(編譯器的校驗)數據結構

  3. JDK8中在接口中增長的默認的方法多線程

    package lambda;
     
     import java.text.DecimalFormat;
     import java.util.Arrays;
     import java.util.function.Function;
     
     @FunctionalInterface
     interface Interface1 {
     	int doubleNum(int i);
     
     	default int add(int x, int y) {
     		return x + y;
     	}
     
     	static int sub(int x, int y) {
     		return x - y;
     	}
     }
     
     @FunctionalInterface
     interface Interface2 {
     	int doubleNum(int i);
     
     	default int add(int x, int y) {
     		return x + y;
     	}
     }
     
     @FunctionalInterface
     interface Interface3 extends Interface2, Interface1 {
     
     	@Override
     	default int add(int x, int y) {
     		return Interface1.super.add(x, y);
     	}
     
     }
     
     public strictfp class LambdaDemo1 {
     
     	public static void main(String[] args) {
     		Interface1 i1 = (i) -> i * 2;
     
     		Interface1.sub(10, 3);
     		System.out.println(i1.add(3, 7));
     		System.out.println(i1.doubleNum(20));
     
     		Interface1 i2 = i -> i * 2;
     
     		Interface1 i3 = (int i) -> i * 2;
     
     		Interface1 i4 = (int i) -> {
     			System.out.println("-----");
     			return i * 2;
     		};
     	}
     }
    複製代碼
JDK8自帶的函數接口的鏈式操做:
class Money {
	private final int money;

	public MyMoney(int money) {
		this.money = money;
	}

	public void printMoney(Function<Integer, String> moneyFormat) {
		System.out.println("個人存款" + moneyFormat.apply(this.money));
	}
}

public class MoneyDemo {

	public static void main(String[] args) {
		Money me = new Money(99999999);

		Function<Integer, String> moneyFormat = i -> new DecimalFormat("#,###")
				.format(i);
		
		// 函數接口的鏈式操做
		me.printMoney(moneyFormat.andThen(s -> "人們幣" + s));
	}

}
複製代碼
其餘JdK8自帶的接口函數

import java.util.function.Consumer;
import java.util.function.IntPredicate;

public class FunctionDemo {
	
	public static void main(String[] args) {
		// 斷言函數接口
		IntPredicate predicate = i -> i > 0;
		System.out.println(predicate.test(-9));//false
		
		// IntConsumer
		// 消費函數接口
		Consumer<String> consumer = s -> System.out.println(s);
		consumer.accept("輸入的數據");//輸入的數據
	}

}
複製代碼

Java8中lamdba中方法的引用:app

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;

class Dog {
	private String name = "哮天犬";

	/**
	 * 默認10斤狗糧
	 */
	private int food = 10;

	public Dog() {

	}

	/**
	 * 帶參數的構造函數
	 * 
	 * @param name
	 */
	public Dog(String name) {
		this.name = name;
	}

	/**
	 * 狗叫,靜態方法
	 * 
	 * @param dog
	 */
	public static void bark(Dog dog) {
		System.out.println(dog + "叫了");
	}

	/**
	 * 吃狗糧 JDK
	 * 
	 * 默認會把當前實例傳入到非靜態方法,參數名爲this,位置是第一個;
	 * 
	 * @param num
	 * @return 還剩下多少斤
	 */
	public int eat(int num) {
		System.out.println("吃了" + num + "斤狗糧");
		this.food -= num;
		return this.food;
	}

	@Override
	public String toString() {
		return this.name;
	}
}

public class MethodRefrenceDemo {

	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.eat(3);

		// 方法引用
		Consumer<String> consumer = System.out::println;
		consumer.accept("接受的數據");

		// 靜態方法的方法引用
		Consumer<Dog> consumer2 = Dog::bark;
		consumer2.accept(dog);

		// 非靜態方法,使用對象實例的方法引用
		// Function<Integer, Integer> function = dog::eat;
		// UnaryOperator<Integer> function = dog::eat;
		IntUnaryOperator function = dog::eat;
		
		// dog置空,不影響下面的函數執行,由於java 參數是傳值
		dog = null;//java中的函數是值傳遞不是傳引用,因此不會出現空指針異常
		System.out.println("還剩下" + function.applyAsInt(2) + "斤");
		
		// 使用類名來方法引用
		// BiFunction<Dog, Integer, Integer> eatFunction = Dog::eat;
		// System.out.println("還剩下" + eatFunction.apply(dog, 2) + "斤");
		
		// 構造函數的方法引用
		// Supplier<Dog> supplier = Dog::new;
		// System.out.println("建立了新對象:" + supplier.get());
		
		// 帶參數的構造函數的方法引用
		// Function<String, Dog> function2 = Dog::new;
		// System.out.println("建立了新對象:" + function2.apply("旺財"));

		// 測試java變量是傳值仍是傳引用即值傳遞
		List<String> list = new ArrayList<>();
		test(list);

		System.err.println(list);
	}

	private static void test(List<String> list) {
		list = null;
	}
}
複製代碼

lambda表達式中的級聯表達式和函數的柯里化dom

import java.util.function.Function;

/**
 * 級聯表達式和柯里化 
 * 柯里化:把多個參數的函數轉換爲只有一個參數的函數 
 * 柯里化的目的:函數標準化
 * 高階函數:就是返回函數的函數
 */
public class CurryDemo {

	public static void main(String[] args) {
		// 實現了x+y的級聯表達式
		Function<Integer, Function<Integer, Integer>> fun = x -> y -> x
				+ y;
		System.out.println(fun.apply(2).apply(3));

		Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = x -> y -> z -> x
				+ y + z;
		//函數的柯里化
		System.out.println(fun2.apply(2).apply(3).apply(4));

		int[] nums = { 2, 3, 4 };
		Function f = fun2;
		
		//柯里化的函數的遞歸調用
		for (int i = 0; i < nums.length; i++) {
			if (f instanceof Function) {
				Object obj = f.apply(nums[i]);
				if (obj instanceof Function) {
					f = (Function) obj;
				} else {
					System.out.println("調用結束:結果爲" + obj);
				}
			}
		}
	}
}
複製代碼

lambda底層實現原理

  1. 編譯器會爲每個lambda表達式生成一個方法 方法名是lambda$0,1,2,3,但方法引用的表達式不會生成方法。
  2. 在lambda地方會產生一個invokeDynamic指令,這個指令會調用 bootstrap(引導)方法,bootstrap方法會指向自動生成的lambda$0 方法或者方法引用的方法。
  3. bootstrap方法使用上是調用了LambdaMetafactory.metafactory靜態方法 該方法返回了CallSite(調用站點),裏面包含了MethodHandle(方法句柄) 也就是最終調用的方法。
  4. 引導方法只會調用一次。

自動生成的方法:ide

1). 輸入和輸出和lambda一致

2). 若是沒有使用this,那麼就是static方法,不然就是成員方法

Stream流的建立:

Stream流的建立

public static void main(String[] args) {
	List<String> list = new ArrayList<>();

	// 從集合建立
	list.stream();
	list.parallelStream();

	// 從數組建立
	Arrays.stream(new int[] { 2, 3, 5 });

	// 建立數字流
	IntStream.of(1, 2, 3);
	IntStream.rangeClosed(1, 10);

	// 使用random建立一個無限流
	new Random().ints().limit(10);
	Random random = new Random();

	// 本身產生流
	Stream.generate(() -> random.nextInt()).limit(20);

}
複製代碼

Stream流的中間操做:

Stream流的中間操做

public static void main(String[] args) {
	String str = "my name is 007";

	// 把每一個單詞的長度調用出來
	Stream.of(str.split(" ")).filter(s -> s.length() > 2)
			.map(s -> s.length()).forEach(System.out::println);

	// flatMap A->B屬性(是個集合), 最終獲得全部的A元素裏面的全部B屬性集合
	// intStream/longStream 並非Stream的子類, 因此要進行裝箱 boxed
	Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed())
			.forEach(i -> System.out.println((char) i.intValue()));

	// peek 用於debug. 是個中間操做,和 forEach 是終止操做
	System.out.println("--------------peek------------");
	Stream.of(str.split(" ")).peek(System.out::println)
			.forEach(System.out::println);

	// limit 使用, 主要用於無限流
	new Random().ints().filter(i -> i > 100 && i < 1000).limit(10)
			.forEach(System.out::println);

}
複製代碼

Stream流的終止操做:

Stream流的中間操做

public static void main(String[] args) {
	String str = "my name is 007";

	// 使用並行流
	str.chars().parallel().forEach(i -> System.out.print((char) i));
	System.out.println();
	// 使用 forEachOrdered 保證順序
	str.chars().parallel().forEachOrdered(i -> System.out.print((char) i));

	// 收集到list
	List<String> list = Stream.of(str.split(" "))
			.collect(Collectors.toList());
	System.out.println(list);

	// 使用 reduce 拼接字符串
	Optional<String> letters = Stream.of(str.split(" "))
			.reduce((s1, s2) -> s1 + "|" + s2);
	System.out.println(letters.orElse(""));

	// 帶初始化值的reduce
	String reduce = Stream.of(str.split(" ")).reduce("",
			(s1, s2) -> s1 + "|" + s2);
	System.out.println(reduce);

	// 計算全部單詞總長度
	Integer length = Stream.of(str.split(" ")).map(s -> s.length())
			.reduce(0, (s1, s2) -> s1 + s2);
	System.out.println(length);

	// max 的使用
	Optional<String> max = Stream.of(str.split(" "))
			.max((s1, s2) -> s1.length() - s2.length());
	System.out.println(max.get());

	// 使用 findFirst 短路操做
	OptionalInt findFirst = new Random().ints().findFirst();
	System.out.println(findFirst.getAsInt());
}
複製代碼
Stream中的並行流:
public static void main(String[] args) {
	// 調用parallel 產生一個並行流
	// IntStream.range(1, 100).parallel().peek(StreamDemo5::debug).count();

	// 如今要實現一個這樣的效果: 先並行,再串行
	// 屢次調用 parallel / sequential, 以最後一次調用爲準.
	// IntStream.range(1, 100)
	// // 調用parallel產生並行流
	// .parallel().peek(StreamDemo5::debug)
	// // 調用sequential 產生串行流
	// .sequential().peek(StreamDemo5::debug2)
	// .count();

	// 並行流使用的線程池: ForkJoinPool.commonPool
	// 默認的線程數是 當前機器的cpu個數
	// 使用這個屬性能夠修改默認的線程數
	// System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism",
	// "20");
	// IntStream.range(1, 100).parallel().peek(StreamDemo5::debug).count();

	// 使用本身的線程池, 不使用默認線程池, 防止任務被阻塞
	// 線程名字 : ForkJoinPool-1
	ForkJoinPool pool = new ForkJoinPool(20);
	pool.submit(() -> IntStream.range(1, 100).parallel()
			.peek(StreamDemo5::debug).count());
	pool.shutdown();
	
	synchronized (pool) {
		try {
			pool.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

public static void debug(int i) {
	System.out.println(Thread.currentThread().getName() + " debug " + i);
	try {
		TimeUnit.SECONDS.sleep(3);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

public static void debug2(int i) {
	System.err.println("debug2 " + i);
	try {
		TimeUnit.SECONDS.sleep(3);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}
複製代碼

lambda中的懶加載機制:在lambda中不調用終止操做,中間的全部過程是不會執行的。

import org.apache.commons.collections4.MapUtils;

/**
 * 學生 對象
 */
class Student {
	/**
	 * 姓名
	 */
	private String name;

	/**
	 * 年齡
	 */
	private int age;

	/**
	 * 性別
	 */
	private Gender gender;

	/**
	 * 班級
	 */
	private Grade grade;

	public Student(String name, int age, Gender gender, Grade grade) {
		super();
		this.name = name;
		this.age = age;
		this.gender = gender;
		this.grade = grade;
	}

	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 Grade getGrade() {
		return grade;
	}

	public void setGrade(Grade grade) {
		this.grade = grade;
	}

	public Gender getGender() {
		return gender;
	}

	public void setGender(Gender gender) {
		this.gender = gender;
	}

	@Override
	public String toString() {
		return "[name=" + name + ", age=" + age + ", gender=" + gender
				+ ", grade=" + grade + "]";
	}

}

/**
 * 性別
 */
enum Gender {
	MALE, FEMALE
}

/**
 * 班級
 */
enum Grade {
	ONE, TWO, THREE, FOUR;
}

public class CollectDemo {

	public static void main(String[] args) {
		// 測試數據
		List<Student> students = Arrays.asList(
				new Student("小明", 10, Gender.MALE, Grade.ONE),
				new Student("大明", 9, Gender.MALE, Grade.THREE),
				new Student("小白", 8, Gender.FEMALE, Grade.TWO),
				new Student("小黑", 13, Gender.FEMALE, Grade.FOUR),
				new Student("小紅", 7, Gender.FEMALE, Grade.THREE),
				new Student("小黃", 13, Gender.MALE, Grade.ONE),
				new Student("小青", 13, Gender.FEMALE, Grade.THREE),
				new Student("小紫", 9, Gender.FEMALE, Grade.TWO),
				new Student("小王", 6, Gender.MALE, Grade.ONE),
				new Student("小李", 6, Gender.MALE, Grade.ONE),
				new Student("小馬", 14, Gender.FEMALE, Grade.FOUR),
				new Student("小劉", 13, Gender.MALE, Grade.FOUR));

		// 獲得全部學生的年齡列表
		// s -> s.getAge() --> Student::getAge , 不會多生成一個相似 lambda$0這樣的函數
		Set<Integer> ages = students.stream().map(Student::getAge)
				.collect(Collectors.toCollection(TreeSet::new));
		System.out.println("全部學生的年齡:" + ages);

		// 統計彙總信息
		IntSummaryStatistics agesSummaryStatistics = students.stream()
				.collect(Collectors.summarizingInt(Student::getAge));
		System.out.println("年齡彙總信息:" + agesSummaryStatistics);

		// 分塊
		Map<Boolean, List<Student>> genders = students.stream().collect(
				Collectors.partitioningBy(s -> s.getGender() == Gender.MALE));
		// System.out.println("男女學生列表:" + genders);
		MapUtils.verbosePrint(System.out, "男女學生列表", genders);

		// 分組
		Map<Grade, List<Student>> grades = students.stream()
				.collect(Collectors.groupingBy(Student::getGrade));
		MapUtils.verbosePrint(System.out, "學生班級列表", grades);

		// 獲得全部班級學生的個數
		Map<Grade, Long> gradesCount = students.stream().collect(Collectors
				.groupingBy(Student::getGrade, Collectors.counting()));
		MapUtils.verbosePrint(System.out, "班級學生個數列表", gradesCount);

	}

}
複製代碼

lambda表達式的運行機制:

驗證stream運行機制

  1. 全部操做是鏈式調用, 一個元素只迭代一次

  2. 每個中間操做返回一個新的流. 流裏面有一個屬性sourceStage 指向同一個 地方,就是Head

  3. Head->nextStage->nextStage->... -> null(底層維護一個鏈表數據結構)

  4. 有狀態操做會把無狀態操做階段,單獨處理

  5. 並行環境下, 有狀態的中間操做不必定能並行操做.

  6. parallel/ sequetial 這2個操做也是中間操做(也是返回stream) 可是他們不建立流, 他們只修改 Head的並行標誌

    public class RunStream {

    public static void main(String[] args) {
       	Random random = new Random();
       	// 隨機產生數據
       	Stream<Integer> stream = Stream.generate(() -> random.nextInt())
       			// 產生500個 ( 無限流須要短路操做. )
       			.limit(500)
       			// 第1個無狀態操做
       			.peek(s -> print("peek: " + s))
       			// 第2個無狀態操做
       			.filter(s -> {
       				print("filter: " + s);
       				return s > 1000000;
       			})
       			// 有狀態操做
       			.sorted((i1, i2) -> {
       				print("排序: " + i1 + ", " + i2);
       				return i1.compareTo(i2);
       			})
       			// 又一個無狀態操做
       			.peek(s -> {
       				print("peek2: " + s);
       			}).parallel();
    
       	// 終止操做
       	stream.count();
       }
    
       /**
        * 打印日誌並sleep 5 毫秒
        * 
        * @param s
        */
       public static void print(String s) {
       	// System.out.println(s);
       	// 帶線程名(測試並行狀況)
       	System.out.println(Thread.currentThread().getName() + " > " + s);
       	try {
       		TimeUnit.MILLISECONDS.sleep(5);
       	} catch (InterruptedException e) {
       	}
       }
    複製代碼

    }

相關文章
相關標籤/搜索