函數(shù)式編程
概念:
使用代碼以某種方式操縱其他代碼,與傳統(tǒng)的面向過程的編程以及面向?qū)ο蟮木幊逃兴惶粯犹汛:瘮?shù)式編程可以將方法作為參數(shù)叮阅,并使得調(diào)用者動(dòng)態(tài)執(zhí)行行為璧函。這很大地提高了代碼的可擴(kuò)展性和維護(hù)性。讓行為綁定更加靈活勋桶。
其中大部分用到了Lambda表達(dá)式和方法引用脱衙。有點(diǎn)類似與C++中的函數(shù)指針或是C#中的Delegate委托類±裕可以將函數(shù)方法作為一個(gè)變量傳遞給某個(gè)通用形式的接收器捐韩。之后用這個(gè)接收器去執(zhí)行回調(diào)操作。
關(guān)于回調(diào)函數(shù)的定義:回調(diào)函數(shù)就是一個(gè)通過函數(shù)指針調(diào)用的函數(shù)鹃锈。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù)荤胁,當(dāng)這個(gè)指針被用來調(diào)用其所指向的函數(shù)時(shí),我們就說這是回調(diào)函數(shù)屎债〗稣回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的盆驹,用于對(duì)該事件或條件進(jìn)行響應(yīng)圆丹。
函數(shù)指針是C++的概念,在Java中可以理解為函數(shù)的引用召娜。
函數(shù)式接口
語法規(guī)則: 有且僅有一個(gè)抽象方法的接口才能被視為函數(shù)式接口(Functional Interface)运褪。
通常用
@FunctionalInterface
來進(jìn)行與其它接口進(jìn)行區(qū)分注釋。
@FunctionalInterface
interface Callable{ void call();}
- 用函數(shù)式接口定義的變量來接收右側(cè)的方法引用時(shí)玖瘸,右側(cè)的方法引用會(huì)隱式地轉(zhuǎn)化為函數(shù)式接口的變量秸讹。
? 函數(shù)式接口變量 = 方法的引用 ( = 兩側(cè)性質(zhì)其實(shí)不同,在Java的語法下會(huì)進(jìn)行隱式轉(zhuǎn)換)雅倒。
class Person{
void eat(){ ... };
}
class Test{
void test(){
Person p = new Person();
Callable c = p::eat;
c.eat(); //調(diào)用時(shí)璃诀,直接通過函數(shù)式接口中唯一的抽象方法來調(diào)用。
}
}
Lambda表達(dá)式
又叫匿名函數(shù)蔑匣,Lambda表達(dá)式可以作為方法引用通過等號(hào)隱式轉(zhuǎn)化為左側(cè)的函數(shù)式接口變量劣欢。
interface Runnable{ void run();} //定義了一個(gè)只含一個(gè)函數(shù)的接口
class Test{
void test{
Runnable r; //用接口定義一個(gè)變量
r = () -> System.out.println("...") ; // 將Lambda表達(dá)式作為函數(shù)引用 賦值給 r
r.run(); //運(yùn)行
}
}
Lambda表達(dá)式中會(huì)自行判斷參數(shù)以及返回值類型
例子略。
方法引用
interface Callable{ void call(int i)}
class Person{
void walk(int step){...}
}
class Test{
void test{
Callbale c;
c = new Person()::walk;
}
}
如果是 普通方法 的引用裁良,就用 對(duì)象名::普通方法名凿将。
如果是 靜態(tài)方法 的引用,就用 類名::靜態(tài)方法名价脾。
如果是 構(gòu)造方法 的引用牧抵,就用 類名::new。
如果是 動(dòng)態(tài)對(duì)象綁定 (原文是:非綁定接收)的引用, 還可以用 類名::普通方法名 的形式犀变。這個(gè)時(shí)候需要在函數(shù)式接口的抽象方法上必須加一個(gè)參數(shù)妹孙,作為調(diào)用者傳入。
class Person{
void eat(){ System.out.println("I'm eating"); }
}
interface Activity{
void doSomething(Person someone); //必須有個(gè)參數(shù)获枝,用來接收調(diào)用者
}
class Test{
void test(){
Person person = new Person();
Activity act = Person::eat; //這個(gè)Person::eat 實(shí)際上是一個(gè)Lambda表達(dá)式的簡易寫法:
// (someone) -> { someone.eat(); }
act.doSomething(person);
}
}
用這種方式有個(gè)好處就是可以把調(diào)用者和行為分開來蠢正,動(dòng)態(tài)調(diào)用的時(shí)候很方便。比如下面這個(gè)例子:
class Person{
String name;
Person(String name){ this.name = name;}
void eat(){ System.out.println(name+" is eating"); }
void sleep(){ System.out.println(name+" is sleeping"); }
void walk(){ System.out.println(name+" is walking"); }
void work(){ System.out.println(name+" is working");}
}
@FunctionalInterface
interface Activity{
void doSomething(Person someone);
}
public class FPTest1 {
public static void main(String[] args) {
Person[] player = {
new Person("Jimmy"),
new Person("Fish"),
new Person("Jack"),
new Person("Rose")
};
Activity[] act = {
Person::eat,
Person::sleep,
Person::walk,
Person::work,
};
Scanner sc = new Scanner(System.in);
int pi = sc.nextInt();
int ai = sc.nextInt();
SomeOneDoSomething( player[pi] , act[ai] ); //通過下標(biāo)索引省店,動(dòng)態(tài)改變"調(diào)用方"對(duì)應(yīng)要做的"行為"嚣崭。
}
static void SomeOneDoSomething(Person someone, Activity activity){
activity.doSomething(someone); //將調(diào)用方作為參數(shù)傳入
}
}
常用內(nèi)置接口
在java.util.function
包中有許多JDK提供的通用函數(shù)式接口。
其中主要為4種:
- Suppiler : 用于無輸入地產(chǎn)生結(jié)果
- Consumer : 用于對(duì)輸入進(jìn)行執(zhí)行或使用
- Predicate : 用于謂語邏輯的判斷
- Function : 用于對(duì)輸入加工處理后輸出
1. Suppiler接口
首先是 Suppiler 的源碼:
抽象方法: T get() 無參數(shù)萨西,返回T類型
@FunctionalInterface
public interface Supplier<T> {
T get();
}
應(yīng)用:
public class Test {
void test(){
Supplier<String> func0 = () -> "Hi!" + "Jimmy!"; //直接調(diào)用Supplier<T>
System.out.println( func0.get() );
}
}
2. Consumer接口
首先是 Consumer 的源碼:
抽象方法: accept(T t) 參數(shù)類型T有鹿,無返回類型
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {...} //還有這個(gè)默認(rèn)方法暫時(shí)這里不展開了
}
應(yīng)用:
public class Test {
void test(){
Consumer<String> func1 = s -> System.out.println(s);
func1.accept("msg1");
}
}
3. Predicate接口
首先是 Predicate 的源碼:
抽象方法: boolean test(T t) 接收T類型,返回boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {...} //暫略
default Predicate<T> negate() {...} //暫略
// ... 還有好多暫略
}
應(yīng)用:
public class Test {
void test(){
Predicate<Integer> isZero = num -> num==0? true:false;
System.out.println(isZero.test(1));
}
}
4. Function接口
首先是 Function 的源碼:
抽象方法:R apply(T t); 接收T類型谎脯,返回R類型
public interface Function<T, R> {
R apply(T t);
//... 其余暫略
}
應(yīng)用:
public class Test {
void test(){
Function<String,Integer> str2int = s -> Integer.parseInt(s);
int a = str2int.apply("10");
sout(a);
}
}