自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java和C++在細(xì)節(jié)上的差異:接口與內(nèi)部類

開發(fā) 后端
本文主要從接口與內(nèi)部類以及異常和斷言方面講解了Java和C++在細(xì)節(jié)上的差異。

繼上篇文章:Java和C++在細(xì)節(jié)上的差異:枚舉與反射

 六、接口與內(nèi)部類:

1. 接口和抽象類:Java通過interface關(guān)鍵字來表示接口,接口中不能包含非靜態(tài)域字段,所有的域成員均是公有的抽象方法,如Comparable接口,如果希望利用Arrays.sort方法,數(shù)組的成員必須實(shí)現(xiàn)該接口。抽象類中包含抽象方法,和接口一樣抽象類也不能被實(shí)例化。

1) 接口不能被實(shí)例化,但是可以聲明接口的變量指向其實(shí)現(xiàn)類的對象。

2) 每個(gè)類只能有一個(gè)超類,但是可以實(shí)現(xiàn)多個(gè)接口。

以下為Java的接口和抽象類的定義方式:

  1. public interface Comparable { 
  2. int compareTo(Object other); 
  3.  
  4. public interface Comparable<T> { 
  5. int compareTo(T other); 
  6.  
  7. abstract class Employee implements Comparable { 
  8. public abstract int compareTo(Object other); 

在C++中同樣存在接口和抽象類的概念,也和Java一樣不能被實(shí)例化,但是并沒有相應(yīng)的關(guān)鍵字存在,而是以一種潛在規(guī)則的方式存在,見如下代碼:

  1. //Comparable對象聲明的方法中只有純虛方法存在(析構(gòu)函數(shù)除外),且沒有任何成員變量。 
  2. class Comparable { 
  3. public
  4. virtual ~Comparable() {} 
  5. //compareTo為純虛方法 
  6. virtual int compareTo(Comparable& other) = 0
  7.  
  8. //Employee對象中存在部分純虛方法,且可以有成員變量存在。 
  9. class Employee { 
  10. public
  11. virtual int compareTo(Comparable& other) { return 0; } 
  12. virtual int backgroud() = 0
  13.  
  14. private
  15. int _age; 

在C++的實(shí)現(xiàn)中,基于接口編程,同時(shí)導(dǎo)出C接口的工廠方法對于跨編譯器極為重要,該方式比較類似于Windows中的COM技術(shù)。

C++支持多重繼承,因此也存在虛基類(菱形結(jié)構(gòu))等問題帶來的負(fù)面影響,既子類的兩個(gè)父類中同時(shí)存在相同簽名的虛方法。見如下代碼:

  1. class TcpServerTask { 
  2. public
  3. virtual void run() {} 
  4.  
  5. class SentObjectTask { 
  6. public
  7. virtual void run() {} 
  8.  
  9. class TcpServerSentTask : public TcpServerTask, public SentObjectTask { } 

2. 對象克?。?Object對象中存在protected類型的clone方法,該方法將會完成子類對象clone的缺省操作,既對象域字段的淺拷貝,如果該對象的成員均為原始類型,如int、float等,或者為不可變類型,如String。這樣的淺拷貝將能夠達(dá)到對象clone的預(yù)期。換言之,如果對象內(nèi)部存在可變對象的引用,淺拷貝將會帶來原始對象和cloned對象引用相同對象引用的問題。如果希望避免該問題的發(fā)生,子類需要實(shí)現(xiàn)Cloneable接口。這里需要指出的是Cloneable接口并未提供clone方法,只是提供了一種契約簽名。子類真正做的還是重載Object方法中的clone方法,由于Object中該方法為protected方法,所以caller不能直接調(diào)用它,只能將子類的clone方法聲明為共有類型,caller才能調(diào)用。

  1. //該實(shí)現(xiàn)類使用淺拷貝已經(jīng)可以滿足其需要了 
  2. public class implements Cloneable { 
  3. //這里已經(jīng)提升了clone方法的級別為public。 
  4. public Employee clone() throws CloneNotSupportedException { 
  5. return (Employee)super.clone(); 
  6. //深拷貝clone方法,必須clone對象內(nèi)部所有可變的實(shí)例域,其中這些可變類 
  7. //必須全部都實(shí)現(xiàn)了自己的clone方法,否則將會跑出異常。 
  8. public class Employee implements Cloneable { 
  9. public Employee clone() throws CloneNotSupportedException { 
  10. //缺省clone完成了域字段的按位淺拷貝。 
  11. Employee cloned = (Employee)super.clone(); 
  12. cloned.hireday = (Date)hireday.clone(); 
  13. private Date hireday; 

注:數(shù)組對象可以通過Array的clone(public)方法完成元素的拷貝。

在C++中由于并不存在Object這樣的單根結(jié)構(gòu)的框架,因此C++是以另外一種方式表現(xiàn)該問題的,既缺省拷貝構(gòu)造和缺省等于操作符重載。和Java類似,這兩個(gè)方法也是member bitwise拷貝的,但這是由編譯器在生成對象模型時(shí)自動完成的缺省行為,如果該類重載了拷貝構(gòu)造函數(shù)和等于操作符,在需要copy的時(shí)候則會調(diào)用重載后的方法,類的實(shí)現(xiàn)者應(yīng)該在這兩個(gè)方法中完成深拷貝。C++中還可以通過將這兩個(gè)方法顯示的聲明為private類型的方法來禁用這種對象之間的copy行為,一旦出現(xiàn),編譯器將會在在編譯器報(bào)錯。在C++中還存在一個(gè)explicit的關(guān)鍵字,可以有效的防止編譯器通過自行推演隱式的調(diào)用對象的拷貝構(gòu)造函數(shù)和等于操作符函數(shù),見如下代碼:

  1. //該類將會使用缺省的copy constructor,因此也會出現(xiàn)兩個(gè)對象 
  2. //引用相同_name變量地址的問題。 
  3. class Employee { 
  4. private
  5. char* _name; 
  6. }; 
  7. //該類由于將這兩個(gè)方法私有化,一旦出現(xiàn)對象的隱式拷貝構(gòu)造, 
  8. //將會導(dǎo)致編譯錯誤。 
  9. class Employee { 
  10. private
  11. Employee(Employee& other); 
  12. const Employee& operator= (Employee& other); 
  13. private
  14. char* _name; 
  15. }; 
  16. //將會調(diào)用重載后的這兩個(gè)函數(shù) 
  17. class Employee { 
  18. Employee(Employee& other); 
  19. const Employee& operator= (Employee& other); 
  20. private
  21. char* _name; 
  22. }; 

注:C++中有一種被稱為引用計(jì)數(shù)的技術(shù),經(jīng)常會用在這個(gè)地方,以便提高對象copy的效率。

3. 接口與回調(diào):嚴(yán)格意義上講,回調(diào)這個(gè)屬于更多的應(yīng)用于C/C++這些支持基于過程編程的語言,Java中的回調(diào)是通過接口的方式來實(shí)現(xiàn)的,由于在接口的實(shí)現(xiàn)類中可以附帶更多的信息,因此其表達(dá)能力要由于C/C++中的函數(shù)指針,見如下代碼:

  1. public class Thread { 
  2. public Thread(Runnable r) {} 
  3.  
  4. public class MyTask implements Runnable { 
  5. public MyTask(int taskID) { 
  6. _taskID = taskID; 
  7.  
  8. public void setOk(bool ok) { 
  9. _ok = ok; 
  10.  
  11. public void run() {} 
  12.  
  13. public static void main(String[] args){ 
  14. MyTask t = new MyTask(5); 
  15. Thread thrd = new Thread(t); 
  16. t.setOk(true); 
  17. thrd.start(); 

這里的Runnable參數(shù)既為接口,Thread對象在啟動的時(shí)候會調(diào)用該接口實(shí)現(xiàn)對象的run方法,但是在調(diào)用之前可以給該實(shí)現(xiàn)類傳入更多的狀態(tài)等相關(guān)數(shù)據(jù),以便在線程類調(diào)用run方法時(shí)可以得到更多的信息。
以下為回調(diào)函數(shù)在C/C++中的實(shí)現(xiàn):

  1. typedef int(*TestCallback)(int,int); 
  2. int testCaller(TestCallback cb,int a,int b) { 
  3. return cb(a,b); 
  4.  
  5. int testCallback(int a,int b) { 
  6. return a * b; 
  7.  
  8. int main() { 
  9. TestCallback cb = testCallback; 
  10. return testCall(cb,5,6); 

在C++中還可以通過模板以更加松散的方式完成類似Java的基于接口的回調(diào)(Java的回調(diào)方式,C++完全可以做到),見如下代碼:

  1. template<typename T> 
  2. class Thread { 
  3. public
  4. Thread(T* r) _r = r {} 
  5. void start() { if (_r) _r->run(); } 
  6. private
  7. T* _r; 

在以上的實(shí)現(xiàn)中,T無需是某個(gè)接口的實(shí)現(xiàn)類,只要保證該類型包含run()方法即可,注意:C++中的模板是引用才編譯的方式,如果沒有任何Thread<T>的聲明,不會導(dǎo)致任何編譯錯誤,只有當(dāng)聲明的類型對象中不包含run()方法時(shí)才會導(dǎo)致編譯錯誤。

4. 內(nèi)部類:Java中內(nèi)部類可以為私有內(nèi)部類,既只有外部類可以訪問該內(nèi)部類,而Java外部類的可見性只有包可見和public兩種。C++中的內(nèi)部類比較類似于Java中的靜態(tài)內(nèi)部類,只是一種作用域限制的行為,以下為Java非靜態(tài)內(nèi)部類的說明:

1) 內(nèi)部類可以訪問外部類的所有域成員和域字段,這也同樣包括私有的字段和成員。

2) Java的編譯器在構(gòu)造外部類調(diào)用內(nèi)部類構(gòu)造方法時(shí),自動將外部類的this變量作為一個(gè)隱式參數(shù)傳給了內(nèi)部類的構(gòu)造函數(shù),內(nèi)部類則在構(gòu)造函數(shù)中保留了this變量的引用,該行為為編譯器隱式行為。

  1. public class Employee { 
  2. public class InnerClass { 
  3. bool test() { 
  4. //這里的_jobYears為外部類域字段。 
  5. return _jobYears > 10
  6.  
  7. public Employee(int jobYears,String name) {  
  8. _name = name;  
  9. _jobYears = jobYears;  
  10. _salary = 0
  11.  
  12. public void raiseSalary() { 
  13. //編譯器的會將以下構(gòu)造隱式替換為InnerClass inner = new InnerClass(this); 
  14. //因?yàn)镴ava在為其編譯的時(shí)候發(fā)現(xiàn)InnerClass為非靜態(tài)內(nèi)部類,則自動添加了以下構(gòu)造: 
  15. //public InnerClass(Employee e) 
  16. InnerClass inner = new InnerClass(); 
  17. if (test())  
  18. _salary += 1000
  19. private String _name; 
  20. private int _jobYears; 
  21. private int _salary; 

注:針對以上事例,內(nèi)部類InnerClass可以通過Employee.this._jobYears的全稱來顯式的代替_jobYears > 10 中的_jobYears。反過來在raiseSalary方法中可以通過this.new InnerClass()語法格式更加明確的創(chuàng)建InnerClass的對象。

  1. public class Employee {  
  2. public class InnerClass {  
  3. bool test() {  
  4. //這里的_jobYears為外部類域字段。  
  5. return Employee.this._jobYears > 10;  
  6. }  
  7. }  
  8.   
  9. public Employee(int jobYears,String name) {   
  10. _name = name;   
  11. _jobYears = jobYears;   
  12. _salary = 0;  
  13. }  
  14.   
  15. public void raiseSalary() {  
  16. //這里也可以不使用this作為內(nèi)部該內(nèi)部類對象的外部類對象  
  17. //引用,可以根據(jù)需要替換為其他外部類對象的引用,如:  
  18. // Employee other = new Employee();  
  19. // InnerClass innser = other.new InnerClass();  
  20. InnerClass inner = this.new InnerClass();  
  21. if (test())   
  22. _salary += 1000;  
  23. }  
  24. ......  
  25. }  

注:在外部類的作用域之外調(diào)用public內(nèi)部類的語法為 OutClass.InnerClass。

3) 局部內(nèi)部類的可見范圍僅僅限于聲明該局部類的函數(shù)內(nèi)部,見如下代碼:

  1. public void start() { 
  2. class TimePrinter implements ActionListener { 
  3. public void actionPerformed(ActionEvent e) { 
  4. Date now = new Date(); 
  5. System.out.println("At the tone,the time is " + now); 
  6. //beep為外部類的域字段 
  7. if (beep) 
  8. Tookkit.getDefaultToolkit().beep(); 
  9. ActionListener l = new TimePrinter(); 
  10. new Timer(interval,l).start(); 

局部類同樣可以訪問函數(shù)內(nèi)部的局部變量,但是要求該變量必須是final的。

  1. public void start(final bool beep) { 
  2. class TimePrinter implements ActionListener { 
  3. public void actionPerformed(ActionEvent e) { 
  4. Date now = new Date(); 
  5. System.out.println("At the tone,the time is " + now); 
  6. //beep為外部函數(shù)的局部變量。 
  7. if (beep) 
  8. Tookkit.getDefaultToolkit().beep(); 
  9. ActionListener l = new TimePrinter(); 
  10. new Timer(interval,l).start(); 

為了規(guī)避局部類只能訪問final局部變量的限制,既一次賦值之后不能再被重新賦值。但是我們可以通過數(shù)組的方式進(jìn)行巧妙的規(guī)避,在下例中數(shù)組counter對象本身是final的,因此他不可以被重新賦值,然而其引用的數(shù)組元素則可以被重新賦值,見下例:

  1. public void test() { 
  2. final int[] counter = new int[1]; 
  3. for (int i = 0; i < dates.length; ++i) { 
  4. dates[i] = new Date() { 
  5. public int compareTo(Date other) { 
  6. //這里如果counter不是數(shù)組,而是被定義為final int counter, 
  7. //則會導(dǎo)致編譯失敗。 
  8. counter[0]++; 
  9. return super.compareTo(other); 

C++中同樣可以做到這些,其規(guī)則和Java的主要差異為C++的內(nèi)部類無法直接訪問外部類的任何成員。

  1. class OuterClass { 
  2. public
  3. void testOuter() { 
  4. class FunctionInnerClass { 
  5. public
  6. void test() { 
  7. printf("This is FunctionInnerClass.\n"); 
  8. }; 
  9. FunctionInnerClass innerClass; 
  10. innerClass.test(); 
  11. }; 
  12.  
  13. int main() 
  14. OuterClass outer; 
  15. outer.testOuter(); 
  16. return 0; 

4) 匿名內(nèi)部類,其基本規(guī)則和局部內(nèi)部類相似,差別在于該內(nèi)部類不能有聲明構(gòu)造函數(shù),這主要是因?yàn)镴ava要求類的構(gòu)造函數(shù)和類名相同,而匿名內(nèi)部類自身沒有類名,因此在new新對象的時(shí)候,傳入的構(gòu)造函數(shù)參數(shù)為超類的構(gòu)造函數(shù)參數(shù)。C++中不支持匿名類。見下例:

  1. public void start(final bool beep) { 
  2. ActionListener l = new ActionListener() { 
  3. public void actionPerformed(ActionEvent e) { 
  4. Date now = new Date(); 
  5. System.out.println("At the tone,the time is " + now); 
  6. //beep為外部函數(shù)的局部變量。 
  7. if (beep) 
  8. Tookkit.getDefaultToolkit().beep(); 
  9. new Timer(interval,l).start(); 

5) 靜態(tài)內(nèi)部類,其功能和C++中的嵌套類非常相似,但是和Java自身的非靜態(tài)內(nèi)部類之間還是存在一些差異,如靜態(tài)內(nèi)部類不能直接訪問外圍類的對象引用域字段,但是可以訪問外部類的static域字段(包括private)。在Java中只有內(nèi)部類可以被定義為static的,外圍類是不可以這樣定義的。

  1. public class TestMain { 
  2. private static boolean classField = false
  3. private boolean objectField = false
  4. static class InnerClass { 
  5. public void test() { 
  6. //這里由于classField是靜態(tài)域字段,所以靜態(tài)內(nèi)部類可以直接訪問, 
  7. //但是對于objectField對象域字段而言,由于靜態(tài)內(nèi)部類中沒有包含 
  8. //外部類的引用,因此不能直接訪問objectField. 
  9. if (classField)  
  10. System.out.println("Hello."); 
  11.  
  12. public static void main(String[] args) { 
  13. classField = true
  14. new InnerClass().test(); 

以下示例中的內(nèi)部類只能是靜態(tài)內(nèi)部類,因?yàn)樵撏獠款惖撵o態(tài)方法在返回內(nèi)部類的實(shí)例時(shí),無法將一個(gè)外部類的對象引用傳遞給該內(nèi)部類,因?yàn)楸仨氁笤搩?nèi)部類為靜態(tài)內(nèi)部類,否則將會報(bào)編譯錯誤。

  1. public class TestMain { 
  2. static class InnerClass { 
  3. public void test() { 
  4. System.out.println("Hello.\n"); 
  5.  
  6. private static InnerClass createInnerClass() { 
  7. return new InnerClass(); 
  8. public static void main(String[] args) { 
  9. createInnerClass().test(); 

如果InnerClass不是靜態(tài)內(nèi)部類,則需要將上例改寫為:

  1. public class TestMain { 
  2. class InnerClass { 
  3. public void test() { 
  4. System.out.println("Hello.\n"); 
  5.  
  6. private static InnerClass createInnerClass() { 
  7. //為了確保InnerClass可以得到外部類的對象引用。 
  8. return new TestMain().new InnerClass(); 
  9. public static void main(String[] args) { 
  10. createInnerClass().test(); 

6) 代理類:通過以下代碼step by step解釋代理類的機(jī)制

  1. import java.lang.reflect.InvocationHandler; 
  2. import java.lang.reflect.Proxy; 
  3. import java.util.Arrays; 
  4. import java.util.Random; 
  5.  
  6. public class TestMain { 
  7. public static void main(String[] args) { 
  8. Object[] elements = new Object[1000]; 
  9. for (int i = 0; i < elements.length; ++i) { 
  10. Integer v = i + 1
  11. //h(調(diào)用處理接口)是代理類的核心處理單元。由于代理類對象只是包含了InvocationHandler 
  12. //這樣一個(gè)對象實(shí)例,并且是存放于超類Proxy中的,而實(shí)際的被代理實(shí)例必須存放于InvocationHandler 
  13. //的實(shí)現(xiàn)類中,如這里的Integer對象v。其中的核心代理代碼也是在InvocationHandler子類的 
  14. //invoke方法中完成的。 
  15. InvocationHandler h = new TraceHandler(v); 
  16. //1. 第一個(gè)參數(shù)表示ClassLoader,這里使用缺省加載器,因此傳入null即可。 
  17. //2. 第二個(gè)參數(shù)表示該代理類需要implement的接口數(shù)組(Java中可以實(shí)現(xiàn)多個(gè)接口)。 
  18. //3. 調(diào)用處理器接口,是代理類如果實(shí)現(xiàn)代理的核心,后面會介紹該類。 
  19. //4. 將該代理類作為Integer的代理存入數(shù)組。 
  20. elements[i] = Proxy.newProxyInstance(nullnew Class[] {Comparable.class}, h); 
  21. Integer key = new Random().nextInt(elements.length) + 1
  22. //1. 由于代理類也都實(shí)現(xiàn)Comparable接口,因此可以用于Arrays.binarySearch中。 
  23. //2. 對代理類進(jìn)行二分查找的比較時(shí),將會直接調(diào)用代理類的compareTo方法。 
  24. //3. 該自動生成的Proxy的子類,其中的compareTo方法會將所有外部調(diào)用的信息,連同 
  25. // 方法名一并傳給其內(nèi)部調(diào)用處理器對象的invoke方法,并調(diào)用該方法(invoke). 
  26. //4. 這里Proxy子類會將所有實(shí)例化時(shí)指定接口(Comparable)的方法(compareTo),以及 
  27. // Object中toString、equals和hashCode方法的調(diào)用都會傳遞給調(diào)用處理器的invoke方法。 
  28. //5. 因此在輸出結(jié)果中不僅可以看到compareTo方法的調(diào)用被打印出,toString也可打印。 
  29. int result = Arrays.binarySearch(elements, key); 
  30. if (result >= 0
  31. System.out.println(elements[result]); 
  32.  
  33. class TraceHandler implements InvocationHandler { 
  34. //由于Proxy的子類是動態(tài)生成的,其具體的實(shí)現(xiàn)也是編譯器動態(tài)生成后傳給JVM的。 
  35. //因此這里是整個(gè)代理機(jī)制中唯一存放被代理對象的地方。 
  36. public TraceHandler(Object t) { 
  37. target = t; 
  38.  
  39. //在此例中,該方法是被Comparable接口中的compareTo方法調(diào)用的,該實(shí)現(xiàn)邏輯是位于該 
  40. //動態(tài)生成的Proxy子類中,如  
  41. //public MyProxy extends Proxy implements Comparable { 
  42. // int compareTo(Object other) { h.invoke(...); } 
  43. @Override 
  44. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  45. //打印出實(shí)際被調(diào)用方法的名稱和參數(shù)值。 
  46. System.out.print(target); 
  47. System.out.print("." + method.getName() + "("); 
  48. if (args != null) { 
  49. for (int i = 0; i < args.length; ++i) { 
  50. System.out.print(args[i]); 
  51. if (i < args.length - 1
  52. System.out.print(", "); 
  53. System.out.println(")"); 
  54. //交給被代理類做實(shí)際的比較。 
  55. return method.invoke(target, args); 
  56. private Object target = null
  57. /* 輸出結(jié)果如下: 
  58. 500.compareTo(128) 
  59. 250.compareTo(128) 
  60. 125.compareTo(128) 
  61. 187.compareTo(128) 
  62. 156.compareTo(128) 
  63. 140.compareTo(128) 
  64. 132.compareTo(128) 
  65. 128.compareTo(128) 
  66. 128.toString() 
  67. 128 */ 

#p#

七、異常和斷言:

1. 異常處理:

1) 異常規(guī)范表示對于"已檢查"(checked)異常,如FileNotFoundException等,既在程序運(yùn)行期間可以預(yù)測到的邏輯問題引發(fā)的異常,對于該類異常,需要在包含該異常的函數(shù)聲明部分標(biāo)識出來,該函數(shù)可能會引發(fā)此類異常,如:

  1. public Image loadImage(String s) throws IOException, MalformedURLException 

如果在loadImage中仍然存在其他"已檢查",但是沒有在函數(shù)的異常規(guī)范中聲明出來,那么將會導(dǎo)致編譯失敗,因此對于函數(shù)中所有"已檢查"必須按照J(rèn)ava異常規(guī)范的要求,在函數(shù)的聲明中予以標(biāo)識。對于該函數(shù)(loadImage)的調(diào)用者而言,在調(diào)用該函數(shù)時(shí),必須將其放入try塊中,同時(shí)在catch字句中捕捉異常規(guī)范中標(biāo)識的異?;蛩麄兊某?。
對于"運(yùn)行時(shí)異常"(unchecked or runtime),由于大多是程序Bug或JVM問題所致,因此不可預(yù)測性極強(qiáng),如ArrayIndexOutOfBoundException,對于該類異常無需在函數(shù)的異常規(guī)范部分予以聲明。

在C++標(biāo)準(zhǔn)中也同樣存在異常規(guī)范的說法,如

  1. File* loadFile(const char* s) throw std::bad_error 

所不同的是C++沒有明確的要求如果函數(shù)內(nèi)部拋出了該異常,則必須在函數(shù)聲明的異常規(guī)范部分予以聲明,對于函數(shù)調(diào)用者而言也同樣沒有這樣的規(guī)定,必須捕獲其中的異常,因此異常規(guī)范在目前的C++編譯器中只是一種提示性的聲明。Java和C++在異常規(guī)范方面還存在的另一個(gè)區(qū)別是,C++中,如果函數(shù)沒有throw字句,那么該函數(shù)仍然可以拋出任何異常,但是對于Java的"已檢查"(checked)異常則必須通過throws字句聲明。

2) 如何拋出異常,在此方面,Java和C++沒有太大的差異,唯一的不同是Java拋出的異常必須是Throwable的實(shí)現(xiàn)類,C++中則沒有這樣的限制,也不存在這樣的異常接口。見如下代碼:

  1. public void howToThrowException() { 
  2. if (someErrorOccurred) 
  3. //1. 通過throw關(guān)鍵字直接拋出指定異常類型的對象即可。 
  4. throw new MyCheckedException(); 

3) 異常捕捉:由于Java中所有的異常均繼承自Throwable,所以catch(Throwable e)可以捕捉所有類型的異常,無論該異常是否為checked or unchecked異常,但是C++中并不存在這樣的異常祖先接口,因此如果想達(dá)到這樣的效果需要使用catch(...)關(guān)鍵字,這同樣表示捕捉所有的異常。

4) 異常鏈:當(dāng)異常第一次拋出并且被catch住的時(shí)候,catch塊中的代碼可以再次拋出捕捉到的異常,同時(shí)也可以為了使上層業(yè)務(wù)邏輯能夠得到更加清晰的判斷,在第一次捕捉到異常后重新定義一個(gè)新的異常并再次拋出。有的時(shí)候,如果上層邏輯在需要的時(shí)候依然可以看到原始異常,將會對錯誤的處理更加合理。在Java中可以通過異常鏈的方式達(dá)到這樣的效果,見如下代碼:

  1. public void testExceptionChain() throws MyCustomizedFileException { 
  2. try { 
  3. FileInputStream in = new FileInputStream("myfile"); 
  4. catch (FileNotFoundException e) { 
  5. //定義了新的,準(zhǔn)備再次被拋出的異常對象。 
  6. Throwable te = new MyCustomizedFileException("access file error."); 
  7. //將原始異常鏈接到該異常對象的內(nèi)部,以供之后需要時(shí)通過getCause()方法重新獲取。 
  8. te.initCause(e); 
  9. throw te; 
  10.  
  11. public static void main(String[] args) { 
  12. try { 
  13. testExceptionChain(); 
  14. catch (MyCustomizedFileException e) { 
  15. //獲取該異常對象的原始異常。 
  16. Throwable te = e.getCause(); 
  17. System.out.println(te.getClass().getName()); 
  18. /* 輸出結(jié)果如下: 
  19. FileNotFoundException 
  20. */ 

5) finally字句:在Java的異常機(jī)制中存在finally這樣的關(guān)鍵字,其塊中的代碼無論異常是否發(fā)生都將會被執(zhí)行,從而可以確保函數(shù)內(nèi)部分配或者打開的資源都能在函數(shù)內(nèi)部進(jìn)行釋放或者關(guān)閉,如Socket連接、DB連接,見如下代碼:

  1. public void testFinally() { 
  2. InputStream in = null
  3. try { 
  4. in = new FileInputStream("myfile"); 
  5. catch (IOException e) { 
  6. //TODO: do something for this exception. 
  7. finally { 
  8. in.close(); 
  9. //Do the following code. 

在以上的代碼中,無論try塊中異常是否發(fā)生,finally塊中的代碼"in.close()" 都將會在函數(shù)退出之前或catch處理之后被執(zhí)行,從而保證了FileInputStream對象能夠在函數(shù)退出之前被關(guān)閉。然而這樣的做法仍然可能導(dǎo)致一些影響代碼流程的問題,如果try塊中的代碼沒有產(chǎn)生異常,而是在finally中的in.close引發(fā)了異常,那么整個(gè)try{}catch{}finally{}代碼塊之后的代碼將不會被執(zhí)行,而是直接退出該函數(shù),同時(shí)拋出in.close()引發(fā)的異常給該函數(shù)的調(diào)用者。修正代碼如下:

  1. public void testFinally() { 
  2. InputStream in = null
  3. try { 
  4. in = new FileInputStream("myfile"); 
  5. catch (IOException e) { 
  6. //TODO: do something for this exception. 
  7. finally { 
  8. try { 
  9. in.close(); 
  10. catch (IOException e) { 
  11. //Do the following code. 

在C++中,由于對象是可以在棧上聲明并且分配空間的,當(dāng)棧退出后會自行調(diào)用該對象的析構(gòu)函數(shù),因此該對象的資源釋放代碼可以放在類的析構(gòu)函數(shù)中。該方式對于一個(gè)多出口的函數(shù)而言也是非常有效的,特別是對于加鎖和解鎖操作需要在同一個(gè)函數(shù)中完成,為了防止在某個(gè)退出分支前意外的漏掉解鎖操作,可以采用該技巧,見如下代碼:

  1. template<typename LockT> 
  2. class ScopedLock { 
  3. public
  4. ScopedLock(T& lock) : _lock(lock) { 
  5. _lock.lock(); 
  6.  
  7. ~ScopedLock() { 
  8. _lock.unlock(); 
  9. private
  10. LockT _lock; 
  11. }; 
  12.  
  13. void testFunc() { 
  14. ScopedLock s1(myLock); 
  15. if (cond1) { 
  16. return
  17. else if (cond2) { 
  18. //TODO: do something 
  19. return
  20. else { 
  21. //TODO: do something 
  22. return

對于以上代碼,無論函數(shù)從哪個(gè)分支退出,s1的析構(gòu)函數(shù)都將調(diào)用,因此myLock的解鎖操作也會被調(diào)用。

6) 異常堆棧跟蹤:通過Throwable的getStackTrace方法獲取在異常即將被拋出的時(shí)間點(diǎn)上程序的調(diào)用堆棧,這樣有利于日志的輸出和錯誤的分析,見如下代碼:

  1. public void testStackTrace() { 
  2. try { 
  3. //TODO: call function, which may be raise some exception. 
  4. catch (Throwable e) { 
  5. StackTraceElement[] frames = e.getStackTrace(); 
  6. for (StackTraceElement f : frames) { 
  7. System.out.printf("Filename is = %s\n",f.getFileName()); 
  8. System.out.printf("LineNumber is = %d\n",f.getLineNumber()); 
  9. System.out.printf("ClassName is = %s\n",f.getClassName()); 
  10. System.out.printf("Methodname is = %s\n",f.getMethodName()); 
  11. System.out.printf("isNativeMethod = %s\n",f.isNativeMethod() ? "true" : "false"); 

也可以直接通過Throwable對象函數(shù)當(dāng)前函數(shù)的運(yùn)行棧信息,見如下代碼:

  1. public static void main(String[] args) { 
  2. Throwable e = new Throwable(); 
  3. StackTraceElement[] frames = e.getStackTrace(); 
  4. for (StackTraceElement f : frames) { 
  5. System.out.printf("Filename is = %s\n",f.getFileName()); 
  6. System.out.printf("LineNumber is = %d\n",f.getLineNumber()); 
  7. System.out.printf("ClassName is = %s\n",f.getClassName()); 
  8. System.out.printf("Methodname is = %s\n",f.getMethodName()); 
  9. System.out.printf("isNativeMethod = %s\n",f.isNativeMethod() ? "true" : "false");  
  10. }  
  11. /* 輸入如下: 
  12. Filename is = TestMain.java 
  13. LineNumber is = 3 
  14. ClassName is = TestMain 
  15. Methodname is = main 
  16. isNativeMethod = false */ 

C++語言本身并未提供這樣的方法,只是提供了__FUNCTION__、__LINE__、__FILE__這樣的3個(gè)宏來獲取當(dāng)前函數(shù)的函數(shù)名、行號和文件名,但是無法得到調(diào)用棧信息,如果確實(shí)需要這樣的信息,只能通過操作系統(tǒng)的工具包來協(xié)助完成(僅針對Debug版本),目前Windows(vc)和Linux(gcc)都提供這樣的開發(fā)包。

2. 斷言:是主要用于開發(fā)、調(diào)試和系統(tǒng)集成測試期間進(jìn)行Debug的一種方式和技巧,語法如下:

  1. assert condition OR assert condition : expression 

其中assert為關(guān)鍵字,當(dāng)condition為false時(shí),程序運(yùn)行中斷,同時(shí)報(bào)出指定的錯誤信息,如果使用assert的后面一種形式,expression的結(jié)果將會同時(shí)輸出,這樣更有助于錯誤的判斷,見如下兩種代碼形式:

  1. public static void main(String[] args) { 
  2. int a = 5
  3. assert a > 10 : a; 
  4. System.out.println("Ok."); 
  5. /* 輸出結(jié)果: 
  6. Exception in thread "main" java.lang.AssertionError: 5 
  7. at TestMain.main(TestMain.java:4) 
  8. */ 
  9. public static void main(String[] args) { 
  10. int[] a = null
  11. assert a != null
  12. System.out.println("Ok."); 
  13. /* 輸出結(jié)果: 
  14. Exception in thread "main" java.lang.AssertionError 
  15. at TestMain.main(TestMain.java:4) 
  16. */ 

在eclipse中,缺省情況下斷言是被禁用的,如果需要開啟斷言,則需要在Run(Debug) As->Run(Debug) Configurations...->Arguments->VM arguments中添加"-enableassertions" 運(yùn)行期參數(shù)。如果斷言被禁用,assert中的代碼不會被執(zhí)行,因此在系統(tǒng)發(fā)布后也不會影響程序的運(yùn)行時(shí)效率。使用者也可以通過該命令行參數(shù)-ea:MyClass -ea:com.mypackage.mylib 來指定需要啟用斷言的class和package,如果啟用的是package,那么該包內(nèi)的所有class都將啟用斷言。在C++中,是依靠crt中的assert(cond)函數(shù)來實(shí)現(xiàn)的,如果cond為false,程序?qū)⒓赐V?,但是在使用前首先需要保證assert.h文件被包含進(jìn)當(dāng)前文件,再有就是當(dāng)前編譯的程序必須是Debug版本,對于Release版本,無論Win32和Linux,斷言的語句都將不會被執(zhí)行。

原文鏈接:http://www.cnblogs.com/stephen-liu74/archive/2011/08/09/2131740.html

【系列文章】

  1. Java和C++在細(xì)節(jié)上的差異:泛型程序設(shè)計(jì)
  2. Java和C++在細(xì)節(jié)上的差異:程序設(shè)計(jì)結(jié)構(gòu)
  3. Java和C++在細(xì)節(jié)上的差異:枚舉與反射
責(zé)任編輯:林師授 來源: Stephen_Liu的博客
相關(guān)推薦

2011-12-06 10:48:32

Java

2011-12-06 09:42:51

Java

2011-12-06 12:16:58

Java

2009-06-01 08:48:19

作用域變量作用域對象作用域

2023-10-19 13:24:00

Java工具

2020-01-15 11:14:21

Java算法排序

2020-12-14 10:23:23

Java內(nèi)部類外部類

2024-07-01 12:48:00

C++內(nèi)部類開發(fā)

2010-01-28 15:22:12

C++嵌套類

2009-08-26 18:00:07

C#內(nèi)部類

2010-02-05 15:32:33

Java內(nèi)部類

2012-11-08 09:49:30

C++Java程序員

2009-06-11 13:08:29

Java內(nèi)部類Java編程思想

2020-09-21 07:00:42

Java內(nèi)部類接口

2011-03-29 14:11:15

內(nèi)部類

2011-07-21 15:44:33

Java內(nèi)部類

2011-07-20 16:30:42

C++

2009-06-11 11:07:25

Java局部內(nèi)部類Final類型

2023-03-06 07:53:36

JavaN種內(nèi)部類

2010-08-26 10:41:45

C#內(nèi)部類
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號