函數式接口 VS 委託java
在C中,可使用函數指針來存儲函數的入口,從而使得函數能夠像變量同樣賦值、傳遞和存儲,使得函數的調用變得十分靈活,是實現函數回調的基礎。然而函數指針不存在函數的簽名信息,甚至能夠指向任何地址,使用上有諸多不安全因素,所以在不少現代語言中不存在函數指針這種類型。數據庫
在Java中,包裝一個方法的調用,須要建立一個接口類型和相應的實現類型,在實現中調用須要包裝的方法,若是須要調用的是實例方法,還須要將實例的引用傳遞進接口實現的實例中(後面再比較閉包)。這種實現方式的好處是不須要引入更多的語法概念,能夠保持語言的精簡,學習曲線平緩。缺點是代碼量多,不能清晰的區分和表達「這是一個包裝了方法的對象」這種概念,同時接口的定義多樣,實例的使用者須要瞭解接口的細節才能很好地使用。編程
public interface Func<T, TResult> {設計模式
TResult invoke(T arg);安全
}數據結構
public static void M(){閉包
Func<String, String> func = new Func<String, String>(){異步
public String invoke(String arg){函數
return method(arg);性能
}
};
String result = func.invoke("world");
}
public static String method(String arg){
return "hello " + arg;
}
在C#(1+)中,有一種特殊的引用類型叫作委託(Delegate),專門用於表達方法的引用。全部的委託類型都繼承於System.Delegate,同時,擁有一組特殊的語法,使得委託的定義、實例化、調用十分簡單和極具語義。委託的調用能夠像調用普通方法同樣,使用者不須要了解委託的內在實現。
delegate TResult Func<T,TResult>(T arg); //定義委託
public static void M(string arg){
Func<string, string> func = Method;//指向靜態方法
Program p = new Program();
func = p.InstanceMethod;//指向實例方法
string result = func("world");//調用方法
}
public static string Method(string arg){
return "hello " + arg;
}
public string InstanceMethod(string arg){
return "Woo " + arg; ;
}
在.NET中,委託的內部其實是封裝了一個函數指針。
Java8中新加的函數式接口,採用了相似於C#的委託的語法,使得一個方法定義能夠直接賦值給一個「函數式接口」,所謂函數式接口就是隻包含一個方法定義的接口,可使用@FunctionalInterface加以約束。
@FunctionalInterface
public interface Func<T, TResult> {
TResult invoke(T arg);
}
public static void M(){
Func<String, String> func = Programe::method;
Programe p = new Programe();
func = p::instanceMethod;
String result = func.invoke("world");
}
public static String method(String arg){
return "hello " + arg;
}
public String instanceMethod(String arg){
return "Woo " + arg;
}
能夠看到,不管是指向靜態方法仍是實例方法,Java8的語法都和C#十分接近,他們的內在實現也是相似的,.NET使用ldftn指令獲取方法地址,而Java則使用invokedynamic。在C#,獲取方法引用和調用方法的語法徹底一致,而Java,靜態方法必須使用類名::方法名的寫法,和調用時能夠只需方法名的寫法有區別,同時多了一個::符號,增長了語法的複雜度,不明白這樣設計的緣由是什麼,難道僅僅是爲了區別C#。
更多
.NET的Delegate基類提供了委託的鏈表鏈接以實現多播,是事件機制實現的基礎;提供了運行於線程池的異步調用方法,使用起來也十分方便。
匿名方法和閉包
當一個方法的定義僅僅是爲了被引用到一個變量中,每每會使用內聯方法定義,也就是匿名方法。
Java:
Func<String, String> func = new Func<String, String>(){
public String invoke(String arg){
return "hello " + arg;
}
};
String result = func.invoke("world");
C#
Func<string, string> func = delegate(string arg){
return "hello " + arg;
};
string result = func("world");
相似的語法,使得一個方法出如今了另外一個方法體內,在編譯以後,都生成了兩個獨立的方法。通常說來兩個方法之間的臨時變量有各自的棧範圍,是不能夠共享的,也就是說內部方法裏不能訪問外部方法的變量。然而從代碼結構上,彷佛又應該容許這樣的訪問,的確,若是內部方法若是能夠訪問外部方法的臨時變量,將會帶來不少便利,代碼邏輯也更直觀。Java和C#都實現了這種變量穿越的能力,就是閉包。
Java
public static void M(int arg){
final int v = arg;
Func<String, String> func = new Func<String, String>(){
public String invoke(String arg){
return "hello " + arg + v;
}
};
}
C#
public static void M(){
int v=1;
Func<string, string> func = delegate(string arg) {
return "hello " + arg + v;
};
string result = func("world");
}
然而他們的實現機制則有一些的區別。
Java的實現,在「初始化接口」時將內部須要的變量做爲自動生成的匿名類型的構造方法的參數傳遞進實例內部並使用字段存儲,內部方法調用外部變量實質上是調用實例的字段。這樣一來,內部訪問的變量和外部的變量在XXX實例化的那一刻開始,就獨立變化了,表現得不像是同一個變量,所以在Java中閉包變量必須加上final修飾,以解決這一問題。
在C#中,內部方法的訪問實現和Java相似,同時添加了對閉包字段訪問的屬性,在外部方法訪問變量時,一樣訪問的是閉包對象的屬性,也就是和內部訪問的相同,從而實現了內部方法和外部方法共享一個「變量」。
二者的實現都將方法內的變量提高到了對象的成員,都將延長對象生命週期,須要注意。
泛型與類型推斷
泛型是C#2添加的新特性,給C#帶來了十分靈動的類型定義方式。例如,再也不須要爲各類含有不一樣類型的函數簽名的方法定義委託,只須要根據不一樣參數個數以及有無返回值定義一批泛型委託就能夠表示絕大部分函數了。
delegate TResult Func<TResult>();
delegate TResult Func<T, TResult>(T arg);
delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
……
delegate void Action();
delegate void Action<T>(T arg);
delegate void Action<T1, T2>(T1 arg1, T2 arg2);
……
再例如,複雜的數據結構能夠用嵌套的泛型定義:如Dictionary<Tuple<int, KeyValuePair<string, long>>, List<int>>。這樣的數據結構定義比自定義類型更爲直觀和統一。可是代碼則較爲冗長,在new這樣的實例時,須要重複寫兩遍。在C#3中,添加了對匿名類型的類型推導能力,自動根據等號右邊的表達式推導左邊變量的類型。
var data = new Dictionary<Tuple<int, KeyValuePair<string, long>>, List<int>>();
var data = new Func<int>(delegate() {
return 0;
});
在C#中,各類開泛型和閉泛型在編譯後都是區別存在的,Action<>,Action<int>,Action<string>都是不一樣的類型,從而真正實現了強類型約束以及運行時獲取類型參數的能力,同時類型參數能夠是值類型,提升了泛型集合的性能。
Lambda表達式
Lambda表達式是C#3添加的新特性。用於簡化委託的定義。
對於如下匿名方法的定義:
Func<string, string> func = delegate(string arg) {
return "hello " + arg;
};
去掉delegate關鍵字,並用符號=>鏈接參數與方法體:
Func<string, string> func = (string arg) => {
return "hello " + arg;
};
經過類型推斷,能夠省去參數的類型:
Func<string, string> func = (arg) => {
return "hello " + arg;
};
對於只有一個參數的方法,能夠省去參數的括號:
Func<string, string> func = arg => {
return "hello " + arg;
};
對於只有一行表達式的方法,能夠省去方法體括號和return關鍵字:
Func<string, string> func = arg => "hello " + arg;
Java8中的lambda表達式幾乎和C#徹底一致,只不過鏈接符號使用->而不是=>。
Func<String, String> func = arg -> "hello " + arg;
顯然,lambda表達式大大化簡了匿名方法的定義,方法內聯到任何須要匿名方法的地方。
如,定義一個可枚舉類型,提供一個篩選元素的方法Where:
class Enumerable<T> : IEnumerable<T>
{
public IEnumerable<T> Where(Func<T, bool> predicat)
{
foreach (var item in this)
{
if (predicat(item))
{
yield return item;
}
}
}
……
}
當我須要對已存在的集合進行按條件篩選,則須要提供一個篩選條件的委託類型,這裏可使用lambda表達式來定義,就顯得十分方便和直觀。
Enumerable<int> collection;
var result = collection.Where(item => item > 100);
高階函數
Lambda表達式除了簡化匿名方法的定義之外,因爲其強大的表達能力,賦於了語言更多的函數式表達能力。
將參數或者返回類型爲函數的函數稱爲高階函數。
如斐波那契數列函數定義:
f(0) = 1;
f(1) = 1;
f(n) = f(n-1) + f(n-2);
用C#能夠寫成:
Func<int, int> f = null;
f = x => x <= 1 ? 1 : f(x - 1) + f(x - 2);
int result = f(5);
Streams VS linq
在前面的例子中,定了一個Where方法用於對集合元素的篩選,事實上.NET4內置提供了多種相似的集合操做方法(這些方法都是經過擴展方法(C#3新特性)添加的,能夠在不修改類定義的狀況下爲類添加相似於實例方法的效果)。利用這些方法和lambda表達式,能夠爲集合進行多種操做。
var result = Enumerable.Range(0, 100)//遍歷1到100
.Where(n => n % 2 == 0)//篩選能被2整除的數
.Where(n => n % 3 == 0)//篩選能被2整除的數
.Select(n => n.ToString())//轉換成字符串
.Reverse();//反轉順序
經過這樣的鏈式編程,能夠表達各類數據集合操做,而這些都只是操做定義,真正的數據操做在使用時才進行,達到了延時操做效果。
更進一步,C#實現了linq,一種語言級查詢語言,將查詢簡化到了極致,達到了相似於SQL的效果。
上面的查詢能夠寫成:
var reuslt = from n in Enumerable.Range(0, 100)
where n % 2 == 0 && n % 3 == 0
select n.ToString();
用一行語句表達了純粹的查詢,不存在任何方法、委託的痕跡。
Java8中添加java.util.Stream包,用於實現相似於C#鏈式查詢的效果。
List<String> stringCollection = null;
String[] result = stringCollection.stream()
.filter(s -> s.startsWith("a"))
.map(s -> s.toString())
.toArray();
表達式樹
在C#中,lambda表達式能夠被編譯爲一種數據結構,稱爲表達式樹,而這個數據結構能夠在運行時被分析、處理或者編譯,作到不少靈活、有趣且高效的效果,其中最爲矚目的就是將linq做爲ORM的查詢語言,將數據庫的查詢融入到語言中,接受編譯時強類型檢查。
Annotation VS Attribute
註解(annotation)是Java6添加的新特性,在Java8中,能夠對同一個元素添加劇復的註解。
註解十分相似於.NET(1+)中的特性(attribute)。不一樣的是.NET Attribute是類而不是接口,能夠帶有方法邏輯而不只僅是數據,從而能夠利用各類設計模式,獲得更多的設計可能性。