Java和C++在細(xì)節(jié)上的差異:接口與內(nèi)部類
繼上篇文章: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的接口和抽象類的定義方式:
- public interface Comparable {
- int compareTo(Object other);
- }
- public interface Comparable<T> {
- int compareTo(T other);
- }
- abstract class Employee implements Comparable {
- public abstract int compareTo(Object other);
- }
在C++中同樣存在接口和抽象類的概念,也和Java一樣不能被實(shí)例化,但是并沒有相應(yīng)的關(guān)鍵字存在,而是以一種潛在規(guī)則的方式存在,見如下代碼:
- //Comparable對象聲明的方法中只有純虛方法存在(析構(gòu)函數(shù)除外),且沒有任何成員變量。
- class Comparable {
- public:
- virtual ~Comparable() {}
- //compareTo為純虛方法
- virtual int compareTo(Comparable& other) = 0;
- }
- //Employee對象中存在部分純虛方法,且可以有成員變量存在。
- class Employee {
- public:
- virtual int compareTo(Comparable& other) { return 0; }
- virtual int backgroud() = 0;
- private:
- int _age;
- }
在C++的實(shí)現(xiàn)中,基于接口編程,同時(shí)導(dǎo)出C接口的工廠方法對于跨編譯器極為重要,該方式比較類似于Windows中的COM技術(shù)。
C++支持多重繼承,因此也存在虛基類(菱形結(jié)構(gòu))等問題帶來的負(fù)面影響,既子類的兩個(gè)父類中同時(shí)存在相同簽名的虛方法。見如下代碼:
- class TcpServerTask {
- public:
- virtual void run() {}
- }
- class SentObjectTask {
- public:
- virtual void run() {}
- }
- 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)用。
- //該實(shí)現(xiàn)類使用淺拷貝已經(jīng)可以滿足其需要了
- public class implements Cloneable {
- //這里已經(jīng)提升了clone方法的級別為public。
- public Employee clone() throws CloneNotSupportedException {
- return (Employee)super.clone();
- }
- }
- //深拷貝clone方法,必須clone對象內(nèi)部所有可變的實(shí)例域,其中這些可變類
- //必須全部都實(shí)現(xiàn)了自己的clone方法,否則將會跑出異常。
- public class Employee implements Cloneable {
- public Employee clone() throws CloneNotSupportedException {
- //缺省clone完成了域字段的按位淺拷貝。
- Employee cloned = (Employee)super.clone();
- cloned.hireday = (Date)hireday.clone();
- }
- 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ù),見如下代碼:
- //該類將會使用缺省的copy constructor,因此也會出現(xiàn)兩個(gè)對象
- //引用相同_name變量地址的問題。
- class Employee {
- private:
- char* _name;
- };
- //該類由于將這兩個(gè)方法私有化,一旦出現(xiàn)對象的隱式拷貝構(gòu)造,
- //將會導(dǎo)致編譯錯誤。
- class Employee {
- private:
- Employee(Employee& other);
- const Employee& operator= (Employee& other);
- private:
- char* _name;
- };
- //將會調(diào)用重載后的這兩個(gè)函數(shù)
- class Employee {
- Employee(Employee& other);
- const Employee& operator= (Employee& other);
- private:
- char* _name;
- };
注: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ù)指針,見如下代碼:
- public class Thread {
- public Thread(Runnable r) {}
- }
- public class MyTask implements Runnable {
- public MyTask(int taskID) {
- _taskID = taskID;
- }
- public void setOk(bool ok) {
- _ok = ok;
- }
- public void run() {}
- }
- public static void main(String[] args){
- MyTask t = new MyTask(5);
- Thread thrd = new Thread(t);
- t.setOk(true);
- 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):
- typedef int(*TestCallback)(int,int);
- int testCaller(TestCallback cb,int a,int b) {
- return cb(a,b);
- }
- int testCallback(int a,int b) {
- return a * b;
- }
- int main() {
- TestCallback cb = testCallback;
- return testCall(cb,5,6);
- }
在C++中還可以通過模板以更加松散的方式完成類似Java的基于接口的回調(diào)(Java的回調(diào)方式,C++完全可以做到),見如下代碼:
- template<typename T>
- class Thread {
- public:
- Thread(T* r) _r = r {}
- void start() { if (_r) _r->run(); }
- private:
- 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變量的引用,該行為為編譯器隱式行為。
- public class Employee {
- public class InnerClass {
- bool test() {
- //這里的_jobYears為外部類域字段。
- return _jobYears > 10;
- }
- }
- public Employee(int jobYears,String name) {
- _name = name;
- _jobYears = jobYears;
- _salary = 0;
- }
- public void raiseSalary() {
- //編譯器的會將以下構(gòu)造隱式替換為InnerClass inner = new InnerClass(this);
- //因?yàn)镴ava在為其編譯的時(shí)候發(fā)現(xiàn)InnerClass為非靜態(tài)內(nèi)部類,則自動添加了以下構(gòu)造:
- //public InnerClass(Employee e)
- InnerClass inner = new InnerClass();
- if (test())
- _salary += 1000;
- }
- private String _name;
- private int _jobYears;
- private int _salary;
- }
注:針對以上事例,內(nèi)部類InnerClass可以通過Employee.this._jobYears的全稱來顯式的代替_jobYears > 10 中的_jobYears。反過來在raiseSalary方法中可以通過this.new InnerClass()語法格式更加明確的創(chuàng)建InnerClass的對象。
- public class Employee {
- public class InnerClass {
- bool test() {
- //這里的_jobYears為外部類域字段。
- return Employee.this._jobYears > 10;
- }
- }
- public Employee(int jobYears,String name) {
- _name = name;
- _jobYears = jobYears;
- _salary = 0;
- }
- public void raiseSalary() {
- //這里也可以不使用this作為內(nèi)部該內(nèi)部類對象的外部類對象
- //引用,可以根據(jù)需要替換為其他外部類對象的引用,如:
- // Employee other = new Employee();
- // InnerClass innser = other.new InnerClass();
- InnerClass inner = this.new InnerClass();
- if (test())
- _salary += 1000;
- }
- ......
- }
注:在外部類的作用域之外調(diào)用public內(nèi)部類的語法為 OutClass.InnerClass。
3) 局部內(nèi)部類的可見范圍僅僅限于聲明該局部類的函數(shù)內(nèi)部,見如下代碼:
- public void start() {
- class TimePrinter implements ActionListener {
- public void actionPerformed(ActionEvent e) {
- Date now = new Date();
- System.out.println("At the tone,the time is " + now);
- //beep為外部類的域字段
- if (beep)
- Tookkit.getDefaultToolkit().beep();
- }
- }
- ActionListener l = new TimePrinter();
- new Timer(interval,l).start();
- }
局部類同樣可以訪問函數(shù)內(nèi)部的局部變量,但是要求該變量必須是final的。
- public void start(final bool beep) {
- class TimePrinter implements ActionListener {
- public void actionPerformed(ActionEvent e) {
- Date now = new Date();
- System.out.println("At the tone,the time is " + now);
- //beep為外部函數(shù)的局部變量。
- if (beep)
- Tookkit.getDefaultToolkit().beep();
- }
- }
- ActionListener l = new TimePrinter();
- new Timer(interval,l).start();
- }
為了規(guī)避局部類只能訪問final局部變量的限制,既一次賦值之后不能再被重新賦值。但是我們可以通過數(shù)組的方式進(jìn)行巧妙的規(guī)避,在下例中數(shù)組counter對象本身是final的,因此他不可以被重新賦值,然而其引用的數(shù)組元素則可以被重新賦值,見下例:
- public void test() {
- final int[] counter = new int[1];
- for (int i = 0; i < dates.length; ++i) {
- dates[i] = new Date() {
- public int compareTo(Date other) {
- //這里如果counter不是數(shù)組,而是被定義為final int counter,
- //則會導(dǎo)致編譯失敗。
- counter[0]++;
- return super.compareTo(other);
- }
- }
- }
- }
C++中同樣可以做到這些,其規(guī)則和Java的主要差異為C++的內(nèi)部類無法直接訪問外部類的任何成員。
- class OuterClass {
- public:
- void testOuter() {
- class FunctionInnerClass {
- public:
- void test() {
- printf("This is FunctionInnerClass.\n");
- }
- };
- FunctionInnerClass innerClass;
- innerClass.test();
- }
- };
- int main()
- {
- OuterClass outer;
- outer.testOuter();
- 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++中不支持匿名類。見下例:
- public void start(final bool beep) {
- ActionListener l = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Date now = new Date();
- System.out.println("At the tone,the time is " + now);
- //beep為外部函數(shù)的局部變量。
- if (beep)
- Tookkit.getDefaultToolkit().beep();
- }
- }
- 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的,外圍類是不可以這樣定義的。
- public class TestMain {
- private static boolean classField = false;
- private boolean objectField = false;
- static class InnerClass {
- public void test() {
- //這里由于classField是靜態(tài)域字段,所以靜態(tài)內(nèi)部類可以直接訪問,
- //但是對于objectField對象域字段而言,由于靜態(tài)內(nèi)部類中沒有包含
- //外部類的引用,因此不能直接訪問objectField.
- if (classField)
- System.out.println("Hello.");
- }
- }
- public static void main(String[] args) {
- classField = true;
- 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)編譯錯誤。
- public class TestMain {
- static class InnerClass {
- public void test() {
- System.out.println("Hello.\n");
- }
- }
- private static InnerClass createInnerClass() {
- return new InnerClass();
- }
- public static void main(String[] args) {
- createInnerClass().test();
- }
- }
如果InnerClass不是靜態(tài)內(nèi)部類,則需要將上例改寫為:
- public class TestMain {
- class InnerClass {
- public void test() {
- System.out.println("Hello.\n");
- }
- }
- private static InnerClass createInnerClass() {
- //為了確保InnerClass可以得到外部類的對象引用。
- return new TestMain().new InnerClass();
- }
- public static void main(String[] args) {
- createInnerClass().test();
- }
- }
6) 代理類:通過以下代碼step by step解釋代理類的機(jī)制
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- import java.util.Arrays;
- import java.util.Random;
- public class TestMain {
- public static void main(String[] args) {
- Object[] elements = new Object[1000];
- for (int i = 0; i < elements.length; ++i) {
- Integer v = i + 1;
- //h(調(diào)用處理接口)是代理類的核心處理單元。由于代理類對象只是包含了InvocationHandler
- //這樣一個(gè)對象實(shí)例,并且是存放于超類Proxy中的,而實(shí)際的被代理實(shí)例必須存放于InvocationHandler
- //的實(shí)現(xiàn)類中,如這里的Integer對象v。其中的核心代理代碼也是在InvocationHandler子類的
- //invoke方法中完成的。
- InvocationHandler h = new TraceHandler(v);
- //1. 第一個(gè)參數(shù)表示ClassLoader,這里使用缺省加載器,因此傳入null即可。
- //2. 第二個(gè)參數(shù)表示該代理類需要implement的接口數(shù)組(Java中可以實(shí)現(xiàn)多個(gè)接口)。
- //3. 調(diào)用處理器接口,是代理類如果實(shí)現(xiàn)代理的核心,后面會介紹該類。
- //4. 將該代理類作為Integer的代理存入數(shù)組。
- elements[i] = Proxy.newProxyInstance(null, new Class[] {Comparable.class}, h);
- }
- Integer key = new Random().nextInt(elements.length) + 1;
- //1. 由于代理類也都實(shí)現(xiàn)Comparable接口,因此可以用于Arrays.binarySearch中。
- //2. 對代理類進(jìn)行二分查找的比較時(shí),將會直接調(diào)用代理類的compareTo方法。
- //3. 該自動生成的Proxy的子類,其中的compareTo方法會將所有外部調(diào)用的信息,連同
- // 方法名一并傳給其內(nèi)部調(diào)用處理器對象的invoke方法,并調(diào)用該方法(invoke).
- //4. 這里Proxy子類會將所有實(shí)例化時(shí)指定接口(Comparable)的方法(compareTo),以及
- // Object中toString、equals和hashCode方法的調(diào)用都會傳遞給調(diào)用處理器的invoke方法。
- //5. 因此在輸出結(jié)果中不僅可以看到compareTo方法的調(diào)用被打印出,toString也可打印。
- int result = Arrays.binarySearch(elements, key);
- if (result >= 0)
- System.out.println(elements[result]);
- }
- }
- class TraceHandler implements InvocationHandler {
- //由于Proxy的子類是動態(tài)生成的,其具體的實(shí)現(xiàn)也是編譯器動態(tài)生成后傳給JVM的。
- //因此這里是整個(gè)代理機(jī)制中唯一存放被代理對象的地方。
- public TraceHandler(Object t) {
- target = t;
- }
- //在此例中,該方法是被Comparable接口中的compareTo方法調(diào)用的,該實(shí)現(xiàn)邏輯是位于該
- //動態(tài)生成的Proxy子類中,如
- //public MyProxy extends Proxy implements Comparable {
- // int compareTo(Object other) { h.invoke(...); }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //打印出實(shí)際被調(diào)用方法的名稱和參數(shù)值。
- System.out.print(target);
- System.out.print("." + method.getName() + "(");
- if (args != null) {
- for (int i = 0; i < args.length; ++i) {
- System.out.print(args[i]);
- if (i < args.length - 1)
- System.out.print(", ");
- }
- }
- System.out.println(")");
- //交給被代理類做實(shí)際的比較。
- return method.invoke(target, args);
- }
- private Object target = null;
- }
- /* 輸出結(jié)果如下:
- 500.compareTo(128)
- 250.compareTo(128)
- 125.compareTo(128)
- 187.compareTo(128)
- 156.compareTo(128)
- 140.compareTo(128)
- 132.compareTo(128)
- 128.compareTo(128)
- 128.toString()
- 128 */
#p#
七、異常和斷言:
1. 異常處理:
1) 異常規(guī)范表示對于"已檢查"(checked)異常,如FileNotFoundException等,既在程序運(yùn)行期間可以預(yù)測到的邏輯問題引發(fā)的異常,對于該類異常,需要在包含該異常的函數(shù)聲明部分標(biāo)識出來,該函數(shù)可能會引發(fā)此類異常,如:
- 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ī)范的說法,如
- 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++中則沒有這樣的限制,也不存在這樣的異常接口。見如下代碼:
- public void howToThrowException() {
- if (someErrorOccurred)
- //1. 通過throw關(guān)鍵字直接拋出指定異常類型的對象即可。
- 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á)到這樣的效果,見如下代碼:
- public void testExceptionChain() throws MyCustomizedFileException {
- try {
- FileInputStream in = new FileInputStream("myfile");
- } catch (FileNotFoundException e) {
- //定義了新的,準(zhǔn)備再次被拋出的異常對象。
- Throwable te = new MyCustomizedFileException("access file error.");
- //將原始異常鏈接到該異常對象的內(nèi)部,以供之后需要時(shí)通過getCause()方法重新獲取。
- te.initCause(e);
- throw te;
- }
- }
- public static void main(String[] args) {
- try {
- testExceptionChain();
- } catch (MyCustomizedFileException e) {
- //獲取該異常對象的原始異常。
- Throwable te = e.getCause();
- System.out.println(te.getClass().getName());
- }
- }
- /* 輸出結(jié)果如下:
- FileNotFoundException
- */
5) finally字句:在Java的異常機(jī)制中存在finally這樣的關(guān)鍵字,其塊中的代碼無論異常是否發(fā)生都將會被執(zhí)行,從而可以確保函數(shù)內(nèi)部分配或者打開的資源都能在函數(shù)內(nèi)部進(jìn)行釋放或者關(guān)閉,如Socket連接、DB連接,見如下代碼:
- public void testFinally() {
- InputStream in = null;
- try {
- in = new FileInputStream("myfile");
- } catch (IOException e) {
- //TODO: do something for this exception.
- } finally {
- in.close();
- }
- //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)用者。修正代碼如下:
- public void testFinally() {
- InputStream in = null;
- try {
- in = new FileInputStream("myfile");
- } catch (IOException e) {
- //TODO: do something for this exception.
- } finally {
- try {
- in.close();
- } catch (IOException e) {
- }
- }
- //Do the following code.
- }
在C++中,由于對象是可以在棧上聲明并且分配空間的,當(dāng)棧退出后會自行調(diào)用該對象的析構(gòu)函數(shù),因此該對象的資源釋放代碼可以放在類的析構(gòu)函數(shù)中。該方式對于一個(gè)多出口的函數(shù)而言也是非常有效的,特別是對于加鎖和解鎖操作需要在同一個(gè)函數(shù)中完成,為了防止在某個(gè)退出分支前意外的漏掉解鎖操作,可以采用該技巧,見如下代碼:
- template<typename LockT>
- class ScopedLock {
- public:
- ScopedLock(T& lock) : _lock(lock) {
- _lock.lock();
- }
- ~ScopedLock() {
- _lock.unlock();
- }
- private:
- LockT _lock;
- };
- void testFunc() {
- ScopedLock s1(myLock);
- if (cond1) {
- return;
- } else if (cond2) {
- //TODO: do something
- return;
- } else {
- //TODO: do something
- }
- return;
- }
對于以上代碼,無論函數(shù)從哪個(gè)分支退出,s1的析構(gòu)函數(shù)都將調(diào)用,因此myLock的解鎖操作也會被調(diào)用。
6) 異常堆棧跟蹤:通過Throwable的getStackTrace方法獲取在異常即將被拋出的時(shí)間點(diǎn)上程序的調(diào)用堆棧,這樣有利于日志的輸出和錯誤的分析,見如下代碼:
- public void testStackTrace() {
- try {
- //TODO: call function, which may be raise some exception.
- } catch (Throwable e) {
- StackTraceElement[] frames = e.getStackTrace();
- for (StackTraceElement f : frames) {
- System.out.printf("Filename is = %s\n",f.getFileName());
- System.out.printf("LineNumber is = %d\n",f.getLineNumber());
- System.out.printf("ClassName is = %s\n",f.getClassName());
- System.out.printf("Methodname is = %s\n",f.getMethodName());
- System.out.printf("isNativeMethod = %s\n",f.isNativeMethod() ? "true" : "false");
- }
- }
- }
也可以直接通過Throwable對象函數(shù)當(dāng)前函數(shù)的運(yùn)行棧信息,見如下代碼:
- public static void main(String[] args) {
- Throwable e = new Throwable();
- StackTraceElement[] frames = e.getStackTrace();
- for (StackTraceElement f : frames) {
- System.out.printf("Filename is = %s\n",f.getFileName());
- System.out.printf("LineNumber is = %d\n",f.getLineNumber());
- System.out.printf("ClassName is = %s\n",f.getClassName());
- System.out.printf("Methodname is = %s\n",f.getMethodName());
- System.out.printf("isNativeMethod = %s\n",f.isNativeMethod() ? "true" : "false");
- }
- }
- /* 輸入如下:
- Filename is = TestMain.java
- LineNumber is = 3
- ClassName is = TestMain
- Methodname is = main
- 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的一種方式和技巧,語法如下:
- assert condition OR assert condition : expression
其中assert為關(guān)鍵字,當(dāng)condition為false時(shí),程序運(yùn)行中斷,同時(shí)報(bào)出指定的錯誤信息,如果使用assert的后面一種形式,expression的結(jié)果將會同時(shí)輸出,這樣更有助于錯誤的判斷,見如下兩種代碼形式:
- public static void main(String[] args) {
- int a = 5;
- assert a > 10 : a;
- System.out.println("Ok.");
- }
- /* 輸出結(jié)果:
- Exception in thread "main" java.lang.AssertionError: 5
- at TestMain.main(TestMain.java:4)
- */
- public static void main(String[] args) {
- int[] a = null;
- assert a != null;
- System.out.println("Ok.");
- }
- /* 輸出結(jié)果:
- Exception in thread "main" java.lang.AssertionError
- at TestMain.main(TestMain.java:4)
- */
在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
【系列文章】