您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java和C++的泛型程序設(shè)計(jì)有什么不同”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Java和C++的泛型程序設(shè)計(jì)有什么不同”吧!
泛型程序設(shè)計(jì):
1. 泛型類(lèi)的定義,見(jiàn)如下代碼:
public class Pair<T,U> { public Pair() { first = null; second = null; } public Pair(T first,U second) { this.first = first; this.second = second; } public T getFirst() { return first; } public U getSecond() { return second; } public void setFirst(T first) { this.first = first; } public void setSecond(U second) { this.second = second; } private T first; private U second; }
以上代碼中的T,U都是泛型類(lèi)Pair的類(lèi)型參數(shù)。以下為C++中模板類(lèi)的定義方式:
template<typename T,typename U> class Pair { public: Pair(T first,U second): _first(first),_second(second) {} ~Pair() {} public: T getFirst() { return _first; } U getSecond() { return _second; } void setFirst(T frist) { _first = first; } void setSecond(U second) { _second = second; } private: T _first; U _second; }
2. 泛型方法的定義,在Java中泛型方法不一定聲明在泛型類(lèi)中,可以聲明在普通類(lèi)中,見(jiàn)如下代碼:
public class MyFirst { public static void main(String[] args) throws Exception { String[] names = {"john","Q.","Public"}; String middle = ArrayAlgo.<String>getMiddle(names); System.out.println(middle); } } class ArrayAlgo { public static <T> T getMiddle(T[] a) { return a[a.length/2]; } }
在以上代碼中可以看出getMiddle方法為靜態(tài)泛型方法,類(lèi)型變量位于修飾符"public static" 的后面,返回值的前面。調(diào)用的時(shí)候在方法名的前面給出了參數(shù)類(lèi)型。由于Java的編譯器提供類(lèi)型推演的功能,既類(lèi)型參數(shù)可以通過(guò)函數(shù)參數(shù)的類(lèi)型進(jìn)行推演,因此也可以直接調(diào)用泛型函數(shù),如String middle = ArrayAlgo.getMiddle(names)。如果編譯器無(wú)法通過(guò)函數(shù)參數(shù)的類(lèi)型推演出類(lèi)型參數(shù)的實(shí)際類(lèi)型,這樣將會(huì)導(dǎo)致編譯錯(cuò)誤。在C++中同樣存在模板函數(shù),也同樣存在模板函數(shù)的類(lèi)型推演,在這一點(diǎn)上主要的差異來(lái)自于函數(shù)聲明的語(yǔ)法,見(jiàn)如下C++代碼:
class ArrayAlgo { public: template<typename T> static T getMiddle(T* a,size_t len) { return a[len/2]; } }; int main() { int v[] = {1,2,3}; int ret = ArrayAlgo::getMiddle(v,3); printf("This value is %d.\n",ret); return 0; }
3. 類(lèi)型參數(shù)的限定:有些泛型函數(shù)在使用類(lèi)型參數(shù)變量時(shí),經(jīng)常會(huì)用到該類(lèi)型的特殊方法,如在進(jìn)行數(shù)組元素比較時(shí),要求數(shù)組中的元素必須是Comparable接口的實(shí)現(xiàn)類(lèi),見(jiàn)如下代碼:
public static <T> T min(T[] a) { if (a == null || a.length == 0) return null; T smallest = a[0]; for (int i = 0; i < a.length; ++i) { if (smallest.compareTo(a[i]) > 0) smallest = a[i]; } return smallest; }
在以上代碼中,數(shù)組元素的類(lèi)型為T(mén),如果該類(lèi)型并未提供compareTo域方法,將會(huì)導(dǎo)致編譯錯(cuò)誤,如何確保類(lèi)型參數(shù)確實(shí)提供了compareTo方法呢?如果T是Comparable接口的實(shí)現(xiàn)類(lèi),那么該方法一定會(huì)被提供,因此可以通過(guò)Java語(yǔ)法中提供的類(lèi)型參數(shù)限定的方式來(lái)確保這一點(diǎn)。見(jiàn)如下修訂代碼:
public static <T extends Comparable> T min(T[] a) { if (a == null || a.length == 0) return null; T smallest = a[0]; for (int i = 0; i < a.length; ++i) { if (smallest.compareTo(a[i]) > 0) smallest = a[i]; } return smallest; }
其中的<T extends Comparable>語(yǔ)法保證了類(lèi)型參數(shù)必須是Comparable接口的實(shí)現(xiàn)類(lèi),否則將會(huì)導(dǎo)致編譯錯(cuò)誤。Java中可以支持多接口的限定,之間用&分隔,如<T extends Comparable & Serializable>和之前的例子一樣,盡管同樣都會(huì)導(dǎo)致編譯錯(cuò)誤,但是后者不僅會(huì)產(chǎn)生更為明確的編譯錯(cuò)誤信息,同樣也使使用者能夠更加清晰的看到該方法的使用規(guī)則。在標(biāo)準(zhǔn)C++中并未提供這樣的限定,但是在C++中對(duì)該種方式有另外一種稱(chēng)謂,叫做"類(lèi)型綁定",在Boost等開(kāi)源庫(kù)中通過(guò)更復(fù)雜的模板技巧模仿了該功能的實(shí)現(xiàn)。然而,就泛型的該功能而言,C#的支持也是相當(dāng)不錯(cuò)的,可參考C#泛型中的which關(guān)鍵字。
在標(biāo)準(zhǔn)C++中,其模板的實(shí)現(xiàn)較Java而言更為靈活和強(qiáng)大。對(duì)于***個(gè)例子中的代碼,只是要求模參必須提供compareTo方法即可通過(guò)編譯。
template<typename T> static T min(T* a,size_t len) { T smallest = a[0]; for (int i = 0; i < len; ++i) { if (smallest.compareTo(a[i]) > 0) smallest = a[i]; } return smallest; }
注:C++中的模板是在引用時(shí)才編譯的,因此如果在模板類(lèi)型中出現(xiàn)任何語(yǔ)法錯(cuò)誤,但此時(shí)尚未有任何引用時(shí),編譯器是不會(huì)報(bào)錯(cuò)的。
4. 泛型代碼中的類(lèi)型擦除:記得在我閱讀Thinking in Java 4th 的時(shí)候,書(shū)中給出了一些比較明確的解釋?zhuān)瑸槭裁碕ava會(huì)這樣實(shí)現(xiàn)泛型,其最主要的原因是為了考慮向前兼容,也承認(rèn)這樣的實(shí)現(xiàn)方式有著很多的缺陷和弊病,希望Java在今后的版本中予以補(bǔ)足。
簡(jiǎn)單的說(shuō)類(lèi)型擦除,就是幾乎所有的泛型相關(guān)的行為都是由編譯器通過(guò)暗插各種各樣的代碼,或者是暗自修訂部分代碼的聲明,然后再將修訂后的代碼(基本不再包含泛型信息)生成字節(jié)碼后交給JVM去執(zhí)行,因此可以據(jù)此判斷在JVM中對(duì)我們的泛型類(lèi)型是一無(wú)所知的。C++也是同樣的道理,只是編譯器完成的工作被定義為類(lèi)型展開(kāi)或類(lèi)型實(shí)例化,因此,同樣的模板類(lèi),如果實(shí)例化的類(lèi)型參數(shù)不同,那么用他們聲明出來(lái)的類(lèi)對(duì)象也同樣不屬于相同類(lèi)型的對(duì)象,其限制主要表現(xiàn)為,不能通過(guò)缺省copy constructor或者缺省賦值操作符來(lái)完成對(duì)象之間的復(fù)制,除非其中某個(gè)類(lèi)型實(shí)例化后的對(duì)象專(zhuān)門(mén)針對(duì)另外一種類(lèi)型實(shí)例化后的類(lèi)型進(jìn)行了copy constructor和賦值等于的重載。
1) 類(lèi)型擦除:將類(lèi)型參數(shù)替換為限定類(lèi)型,如果沒(méi)有限定類(lèi)型則替換為Object,見(jiàn)如下代碼:
public class Pair<T> { public Pair(T first,T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public void setFirst(T first) { this.first = first; } public void setSecond(T second) { this.second = second; } private T first; private T second; }
由于Pair中的類(lèi)型參數(shù)T沒(méi)有限定類(lèi)型,因此類(lèi)型擦除后將會(huì)變成如下代碼:
public class Pair { public Pair(Object first,Object second) { this.first = first; this.second = second; } public Object getFirst() { return first; } public Object getSecond() { return second; } public void setFirst(Object first) { this.first = first; } public void setSecond(Object second) { this.second = second; } private Object first; private Object second; }
因此盡管在調(diào)用Pair時(shí),傳遞的類(lèi)型參數(shù)有所不同,如String、Date,但是在類(lèi)型擦除之后,他們將成為相同的類(lèi)型。如果類(lèi)型參數(shù)存在多個(gè)限定類(lèi)型,則取***個(gè)限定類(lèi)型作為擦除后的類(lèi)型參數(shù),見(jiàn)如下代碼:
public class Interval<T extends Comparable & Serializable> implements Serializable { public Interval(T first, T second) { if (first.compareTo(second) <= 0) { lower = first; upper = second; } else { lower = second; uppper = first; } } private T lower; private T upper; }
擦除類(lèi)型信息后的原始類(lèi)型如下:
public class Interval implements Serializable { public Interval(Comparable first, Comparable second) { if (first.compareTo(second) <= 0) { lower = first; upper = second; } else { lower = second; uppper = first; } } private Comparable lower; private Comparable upper; }
5. 泛型類(lèi)向遺留代碼的兼容:由于編譯器自動(dòng)完成了類(lèi)型信息的擦除,因此在原有調(diào)用原始類(lèi)型的地方,可以直接傳入等價(jià)的泛型類(lèi),只要保證該泛型類(lèi)在類(lèi)型擦除后可以符合被調(diào)用函數(shù)參數(shù)的語(yǔ)法要求即可,見(jiàn)如下代碼:
public class TestMain { public static void test(MyClass t) { System.out.println(t.getValue()); } public static void main(String[] args) { MyClass<Integer> v = new MyClass<Integer>(5); test(v); } } class MyClass<T> { public MyClass(T t) { this.t = t; } public T getValue() { return t;} private T t; }
6. 約束與局限性:
1) 不能使用原始類(lèi)型作為類(lèi)型參數(shù),如int、double等,因?yàn)樗麄兒蚈bject之間沒(méi)有直接的繼承關(guān)系,因此在需要時(shí)只能使用包裝類(lèi),如Integer、Double分別予以替換,不能這樣的替換確實(shí)也帶來(lái)了效率上的折損,C++中沒(méi)有這樣的限制,因此模板類(lèi)的增多只會(huì)影響編譯的效率和不會(huì)影響運(yùn)行時(shí)的效率。
2) 運(yùn)行時(shí)的類(lèi)型查詢(xún)只適用于原始類(lèi)型,即if (a instanceof Pair<String>) 等價(jià)于 if (a instanceof Pair)。
3) 泛型類(lèi)對(duì)象調(diào)用getClass()方法返回的Class對(duì)象都是擦除類(lèi)型信息的原始Class類(lèi)型,因此在做比較時(shí),他們將為真,見(jiàn)如下代碼:
public class TestMain { public static void main(String[] args) { MyClass<Integer> i = new MyClass<Integer>(5); MyClass<Double> d = new MyClass<Double>(5.0); //返回的均為MyClass.Class if (d.getClass() == i.getClass()) System.out.println("Type info will be ignored here"); } } class MyClass<T> { public MyClass(T t) { this.t = t; } public T getValue() { return t;} private T t; } /* 輸入結(jié)果: Type info will be ignored here */
4) 泛型類(lèi)不能實(shí)現(xiàn)Throwable接口,換言之泛型類(lèi)不能成為異常類(lèi),否則會(huì)導(dǎo)致編譯錯(cuò)誤。
5) 不能聲明參數(shù)化類(lèi)型的數(shù)組,如Pair<String>[] table = new Pair<String>[10]; 在擦除類(lèi)型后將會(huì)變?yōu)镻air[] table = new Pair[10]; 因此可以執(zhí)行該轉(zhuǎn)換:Objec[] objarray = table; 由于數(shù)組可以記住元素的類(lèi)型,如果此時(shí)試圖插入錯(cuò)誤的類(lèi)型元素,將會(huì)導(dǎo)致異常ArrayStoreException的拋出。C++中沒(méi)有該限制。
6) 不能實(shí)例化泛型類(lèi)型的變量,如public Pair() { first = new T(); second = new T();},C++中不存在這樣的限制,針對(duì)以上寫(xiě)法,類(lèi)型T只要存在缺省的構(gòu)造函數(shù)即可。如果確實(shí)需要實(shí)例化類(lèi)型參數(shù)的對(duì)象,見(jiàn)如下代碼:
public static <T> Pair<T> makePair(Class<T> c1) { return new Pair<T>(c1.newInstance(),c1.newInstance()); } public static void main(String[] args) { //String.class的類(lèi)型為Class<String> Pair<String> p = Pair.makePair(String.class); }
這里主要是利用Class類(lèi)型本身也是泛型類(lèi)型,可以利用Class<T>的類(lèi)型參數(shù)推演出Pair<T>中T的類(lèi)型。同樣的道理帶有類(lèi)型參數(shù)的數(shù)組對(duì)象也不能直接創(chuàng)建,需要利用Array的反射機(jī)制來(lái)輔助完成,見(jiàn)如下代碼:
public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = (T[])Array.newInstance(a.getClass().getComponentType(),a.length); //do something here based on mm return mm; }
7) 泛型類(lèi)不能應(yīng)用于靜態(tài)上下文中,見(jiàn)如下代碼:
public class Singleton<T> { public static T getInstance() { //Compilation ERROR return singleInstance; } private T singleInstance; //Compilation ERROR }
因?yàn)檫@樣的寫(xiě)法在定義Singleton<String>和Singleton<Date>之后,由于類(lèi)型擦除,將會(huì)生成唯一一個(gè)Singleton原始共享對(duì)象,事實(shí)上這并不是我們所期望的結(jié)果,在C++中沒(méi)有這樣的限制,甚至有的時(shí)候還可以利用這樣的機(jī)制針對(duì)不同類(lèi)型的對(duì)象作聲明計(jì)數(shù)器用,見(jiàn)如下代碼:
template<typename T> class MyClassCounter { public: MyClassCounter(T t) { _t = t; _counter++; } operator T() { return _t; } T* operator->() { return &_t; } int getCount() const { return _counter; } private: T _t; static int _counter; }
8) 泛型類(lèi)不能同時(shí)繼承或?qū)崿F(xiàn)只是擁有不同參數(shù)類(lèi)型的同一泛型類(lèi),如 public class MyClass implements Comparable<String>, Comparable<Date> {}
7. 泛型類(lèi)型的繼承規(guī)則:
1) 如果 public class Manager extends Employee {},那么Pair<Employee> pe = new Pair<Manager>()將會(huì)是非常的賦值操作,會(huì)導(dǎo)致編譯錯(cuò)誤。試想如下代碼引發(fā)的運(yùn)行時(shí)問(wèn)題。在C++中這種賦值方式同樣會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)樗麄冊(cè)陬?lèi)型實(shí)例化之后就被視為完全無(wú)關(guān)的兩個(gè)類(lèi)型。
public void test() { Pair<Manager> manager = new Pair<Manager>(); Pair<Employee> employee = manager; //compilation error employee.setFirst(otherEmployeeButNotManager); //employee的另外一個(gè)子類(lèi),但不是Manager。 }
2) 數(shù)組由于在運(yùn)行時(shí)會(huì)記住元素的類(lèi)型,因此數(shù)組可以完成這樣的賦值,如Manager[] manager = {}; Employee[] employee = manager;如果賦值之后出現(xiàn)錯(cuò)誤的元素賦值將會(huì)引發(fā)ArrayStoreException異常。
3) 泛型類(lèi)型可以直接賦值給擦除類(lèi)型后的原始類(lèi)型,但是同樣也會(huì)出現(xiàn)2)中數(shù)組賦值的問(wèn)題,只是觸發(fā)的異常改為ClassCastException,見(jiàn)如下代碼:
public void test() { Pair<Manager> manager = new Pair<Manager>(); Pair rawType = manager; rawType.setFirst("Hello"); //only compilation warning, but will encounter runtime error. }
4) 如果 public class ArrayList<Manager> extends List<Manager> {}, 那么從ArrayList<Manager>到List<Manager>的賦值是允許的,這一點(diǎn)和普通類(lèi)型是一致的,該規(guī)則同樣適用于C++。
8. 泛型類(lèi)型的通配符類(lèi)型:該泛型特征在標(biāo)準(zhǔn)C++中完全不被支持,C#中存在類(lèi)似的特征。見(jiàn)以下代碼:
1) 子類(lèi)型限定:
public class Manager extends Employee {} public static void printBuddies(Pair<Employee> p) { } public static void main(String[] args) { printBuddies(new Pair<Employee>()); //legal printBuddies(new Pair<Manager>()); //illegal; }
但是如果將printBuddies改為:void printBuddies(Pair<? extends Employee> p),上例中main函數(shù)將可以通過(guò)編譯。<? extends Employee>的語(yǔ)義為所有Employee的子類(lèi)都可以做printBuddies函數(shù)參數(shù)的類(lèi)型參數(shù)。對(duì)于7-1)中的示例代碼,如果改為通配符類(lèi)型將可以通過(guò)編譯并正常運(yùn)行,但是仍然存在一定的限制,見(jiàn)如下代碼:
public void test() { Pair<Manager> manager = new Pair<Manager>(); Pair<? extends Employee> employee = manager; //legal here. //由于otherEmployeeButNotManager雖為Employee子類(lèi),但可能并非Manager類(lèi), //由于setFirst的參數(shù)將會(huì)聲明為void setFirst(? extends Employee),由于 //編譯器無(wú)法確定setFirst參數(shù)的實(shí)際類(lèi)型,因此將會(huì)直接報(bào)告編譯錯(cuò)誤。 employee.setFirst(otherEmployeeButNotManager); //compilation error }
和setFirst相比,getFirst將會(huì)正常編譯并運(yùn)行,因?yàn)榉祷刂禑o(wú)論是什么子類(lèi)型,都不會(huì)帶來(lái)影響和破壞。
2) 超類(lèi)型限定:Pair<? super Manager>表示參數(shù)類(lèi)型一定是Manager的超類(lèi)。因此和子類(lèi)型限定剛好相反,setFirst將是合法的,而getFirst將會(huì)產(chǎn)生編譯錯(cuò)誤。
3) 無(wú)限定通配符,如Pair<?>,該泛型類(lèi)型的setFirst(?)方法不能被調(diào)用,即便傳入的參數(shù)是Object,這樣是Pair<?>和Pair之間***的差異。該方法還有一個(gè)比較重要的作用就是用于提示泛型函數(shù)的調(diào)用者,該泛型函數(shù)更期望參數(shù)是帶有類(lèi)型參數(shù)的泛型類(lèi)型,而不是原始類(lèi)型,即便原始類(lèi)型也可能正常的工作,見(jiàn)如下代碼:
public class TestMain { @SuppressWarnings("unchecked") //public static <T> T print(MyClass myclass),同樣可以正常的工作, //但是會(huì)有編譯警告產(chǎn)生。 public static <T> T print(MyClass<?> myclass) { myclass.print(); return (T)myclass.get(); } public static void main(String[] args) { Integer ii = new Integer(5); print(new MyClass<Integer>(ii)); } } class MyClass<T> { public MyClass(T t) { _t = t; } T get() { return _t;} public void print() { System.out.println(_t); } private T _t; }
感謝各位的閱讀,以上就是“Java和C++的泛型程序設(shè)計(jì)有什么不同”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Java和C++的泛型程序設(shè)計(jì)有什么不同這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。