函數式編程 面向對象過分強調“必須通過對象的形式來做事情”,而函數式思想則儘量忽略面向對象的複雜語法——強調做什麼,而不是怎麼做。 有時只是為了做某事情而不得不創建一個對象,而傳遞一段代碼才是我們真正的目的。 Lambda Lambda是一個匿名函數,可以理解為一段可以傳遞的代碼。 當需要啟動一個線 ...
函數式編程
面向對象過分強調“必須通過對象的形式來做事情”,而函數式思想則儘量忽略面向對象的複雜語法——強調做什麼,而不是怎麼做。
有時只是為了做某事情而不得不創建一個對象,而傳遞一段代碼才是我們真正的目的。
Lambda
Lambda是一個匿名函數,可以理解為一段可以傳遞的代碼。
當需要啟動一個線程去完成任務時, 通常會通過java.lang.Runnable
介面來定義任務內容,並使用java.lang.Thread
類來啟動該線程
傳統寫法,代碼如下:
public class Demo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多線程任務執行!");
}
}).start();
}
}
藉助Java 8的全新語法,上述Runnable
介面的匿名內部類寫法可以通過更簡單的Lambda表達式達到同樣的效果:
public class Demo04LambdaRunnable {
public static void main(String[] args) {
new Thread(() -> System.out.println("多線程任務執行!")).start(); // 啟動線程
}
}
Lambda的優點 簡化匿名內部類的使用,語法更加簡單。
前提條件
必須是介面, 介面中有且只有一個抽象方法
有且僅有一個抽象方法的介面,稱為函數式介面。
格式
Lambda表達式的標準格式為:
() -> {}
() 參數列表,無參數留空
-> 固定寫法, 代表指向動作
{} 方法體
省略規則
在Lambda標準格式的基礎上,使用省略寫法的規則為:
- 參數類型可省略
- 如果只有一個參數 ()可以省略
- 如果方法體只有一句話 return 大括弧 分號都可省略, 但必須同時省略
new Thread(() -> System.out.println("省略格式開啟線程")).start();
原理
- 在匿名方法所在類中新增一個方法,方法體為Lambda表達式中的代碼
- 運行時形成一個新的類,並實現對應介面
- 重寫方法的方法體中調用匿名方法所在類中新生成的方法.
函數式介面
函數式介面在Java中是指:有且僅有一個抽象方法的介面。
函數式介面,即適用於函數式編程場景的介面。而Java中的函數式編程體現就是Lambda,所以函數式介面就是可以適用於Lambda使用的介面。
從應用層面來講,Java中的Lambda可以看做是匿名內部類的簡化格式,但是二者在原理上不同。
格式
只要確保介面中有且僅有一個抽象方法即可:
修飾符 interface 介面名稱 {
public abstract 返回值類型 方法名稱(可選參數信息);
}
由於介面當中抽象方法的public abstract
是可以省略的,所以定義一個函數式介面很簡單:
public interface MyFunctionalInterface {
void myMethod();
}
@FunctionalInterface
@FunctionalInterface
該註解可用於一個介面的定義上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用該註解來定義介面,編譯器將會強制檢查該介面是否確實有且僅有一個抽象方法,否則將會報錯。不過,即使不使用該註解,只要滿足函數式介面的定義,這仍然是一個函數式介面
常用函數式介面
Supplier介面
java.util.function.Supplier<T>
介面,它意味著"供給" , 對應的Lambda表達式需要“對外提供”一個符合泛型類型的對象數據。
抽象方法:
T get()
用來獲取一個泛型參數指定類型的對象數據
求數組元素最大值
使用Supplier
介面作為方法參數類型,通過Lambda表達式求出int數組中的最大值
public class supplierInterface {
public static void main(String[] args) {
int[] arr = {3,24,346,4,13};
method(() -> {
Arrays.sort(arr);
return arr[arr.length - 1];
});
}
public static void method(Supplier<Integer> s) {
Integer max = s.get();
System.out.println(max);
}
}
Consumer介面
java.util.function.Consumer<T>
介面不生產數據,而是消費一個數據,其數據類型由泛型參數決定
抽象方法
void accept(T t)
,意為消費一個指定泛型的數據
預設方法
default Consumer<T> andThen(Consumer<? super T> after)
public class _4_consumerInterface {
public static void main(String[] args) {
method("Hello World", (String s) -> {
System.out.println(s.toUpperCase());
});
method("HEllO WorlD", s -> System.out.println(s.toLowerCase()));
System.out.println("==========================");
method("HEllO WorlD", (String s) -> {
System.out.println(s.toUpperCase());
}, (String s) -> {
System.out.println(s.toLowerCase());
});
method("HEllO WorlD",
s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase())
);
}
public static void method(String s, Consumer<String> c) {
c.accept(s);
}
public static void method(String s, Consumer<String> c1, Consumer<String> c2) {
// c1.accept(s);
// c2.accept(s);
// andThen c1.accept(s)後執行c2.accept(s) 等同於上面的寫法
c1.andThen(c2).accept(s);
}
}
Function介面
java.util.function.Function<T,R>
介面用來根據一個類型的數據得到另一個類型的數據,前者稱為前置條件,後者稱為後置條件
抽象方法:
R apply(T t)
根據類型T的參數獲取類型R的結果
public class Test {
public static void main(String[] args) {
Function<String,Integer> f = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
};
Integer apply = f.apply("100");
System.out.println(apply);
Function<String,Integer> f2 = s -> Integer.parseInt(s);
System.out.println(f2.apply("200"));
}
}
預設方法:andThen
Function
介面中有一個預設的andThen
方法,用來進行組合操作,與Consumer介面相同
public class Test {
public static void main(String[] args) {
method5("10", (String s) -> {
return Integer.parseInt(s);
}, (Integer i) -> {
return i * 10;
});
method5("100", s -> Integer.parseInt(s), i -> i * 10);
method5("1000", Integer::parseInt, i -> i * 10);
}
private static void method5(String s, Function<String, Integer> f1, Function<Integer, Integer> f2) {
// Integer i = f1.apply(s);
// Integer n = f2.apply(i);
Integer n = f1.andThen(f2).apply(s);
System.out.println(n);
}
}
Function的前置條件泛型和後置條件泛型可以相同
Predicate介面
java.util.function.Predicate
抽象方法: boolean test(T t)
返回boolean
public class predicateInterface {
public static void main(String[] args) {
method("HelloWorld.java", (String s) -> {
return s.toLowerCase().endsWith(".java");
});
method("Hello.java", s -> s.toLowerCase().endsWith(".java"));
}
public static void method(String filename, Predicate<String> p) {
boolean b = p.test(filename);
System.out.println(b);
}
}
預設方法
Predicate<T> and(Predicate<? super T> other)
並且, 底層使用 &&
Predicate<T> or(Predicate<? super T> other)
或者, 底層使用 ||
Predicate<T> negate()
取反, 底層使用 !
public class Test {
public static void main(String[] args) {
method("Helloworld" ,s -> s.contains("H"), s -> s.contains("W"));
}
private static void method(String str ,Predicate<String> one, Predicate<String> two) {
boolean b1 = one.test(str);
boolean b2 = two.test(str);
System.out.println("字元串符合要求嗎:" + (b1 && b2));
boolean isValid = one.and(two).test(str);
System.out.println("字元串符合要求嗎:" + isValid);
}
}
public class Test {
public static void main(String[] args) {
method("Helloworld" ,s -> s.contains("H"), s -> s.contains("W"));
}
private static void method(String str ,Predicate<String> one, Predicate<String> two) {
boolean b1 = one.test(str);
boolean b2 = two.test(str);
System.out.println("字元串符合要求嗎:" + (b1 || b2));
boolean isValid = one.or(two).test(str);
System.out.println("字元串符合要求嗎:" + isValid);
}
}
public class Test {
public static void main(String[] args) {
isLong("aaa", new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length()<5;
}
});
isLong("bbbaa",s -> s.length()>=5);
}
public static void isLong(String s , Predicate<String> p){
boolean test = p.test(s);
System.out.println(!test);
boolean b2 = p.negate().test(s);
System.out.println(b2);
}
}
方法引用
前提
Lambda表達式中只有一句話時 可能使用
格式
符號表示 : ::
符號說明 : 雙冒號為方法引用運算符,而它所在的表達式被稱為方法引用。
推導與省略 : ** 如果使用Lambda,那麼根據“可推導就是可省略**”的原則,無需指定參數類型,也無需指定的重載形式——它們都將被自動推導
應用Lambda表達式 , 在accept方法中接收字元串 , 目的就是為了列印顯示字元串 , 那麼通過Lambda來使用它的代碼很簡單:
public class DemoPrintSimple {
private static void printString(Consumer<String> data, String str) {
data.accept(str);
}
public static void main(String[] args) {
printString(s -> System.out.println(s), "Hello World");
}
}
使用方法引用進行簡化
public class DemoPrintRef {
private static void printString(Consumer<String> data, String str) {
data.accept(str);
}
public static void main(String[] args) {
printString(System.out::println, "HelloWorld");
}
}
其他引用
public class _5_functionInterface {
public static void main(String[] args) {
method("100", (String s) -> {
return Integer.parseInt(s);
});
method("10", s -> Integer.parseInt(s));
/*
類名引用靜態方法
類名::方法名
*/
method("1000", Integer::parseInt);
/*
類名引用構造方法
類名::new
*/
method2("張三", Person::new);
method2("李四", Person::new);
method2("王五", s -> new Person(s));
method3(Person::new);
/*
數組引用構造方法
數據類型[]::new
*/
method4(5,int[]::new);
method4(3, (Integer i) -> {
return new int[i];
});
method4(1, i -> new int[i]);
}
public static void method(String s, Function<String, Integer> f) {
Integer n = f.apply(s);
System.out.println(n);
}
private static void method2(String s, Function<String, Person> f) {
Person p = f.apply(s);
System.out.println(p);
}
private static void method3(Supplier<Person> su) {
Person p = su.get();
System.out.println(p);
}
private static void method4(Integer i, Function<Integer, int[]> f) {
int[] arr = f.apply(i);
System.out.println(Arrays.toString(arr));
}
}
class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}