Java和C++在细节上的差异:接口与内部类

开发 后端
本文主要从接口与内部类以及异常和断言方面讲解了Java和C++在细节上的差异。

继上篇文章:Java和C++在细节上的差异:枚举与反射

 六、接口与内部类:

1. 接口和抽象类:Java通过interface关键字来表示接口,接口中不能包含非静态域字段,所有的域成员均是公有的抽象方法,如Comparable接口,如果希望利用Arrays.sort方法,数组的成员必须实现该接口。抽象类中包含抽象方法,和接口一样抽象类也不能被实例化。

1) 接口不能被实例化,但是可以声明接口的变量指向其实现类的对象。

2) 每个类只能有一个超类,但是可以实现多个接口。

以下为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一样不能被实例化,但是并没有相应的关键字存在,而是以一种潜在规则的方式存在,见如下代码:

  1. //Comparable对象声明的方法中只有纯虚方法存在(析构函数除外),且没有任何成员变量。 
  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++的实现中,基于接口编程,同时导出C接口的工厂方法对于跨编译器极为重要,该方式比较类似于Windows中的COM技术。

C++支持多重继承,因此也存在虚基类(菱形结构)等问题带来的负面影响,既子类的两个父类中同时存在相同签名的虚方法。见如下代码:

  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。这样的浅拷贝将能够达到对象clone的预期。换言之,如果对象内部存在可变对象的引用,浅拷贝将会带来原始对象和cloned对象引用相同对象引用的问题。如果希望避免该问题的发生,子类需要实现Cloneable接口。这里需要指出的是Cloneable接口并未提供clone方法,只是提供了一种契约签名。子类真正做的还是重载Object方法中的clone方法,由于Object中该方法为protected方法,所以caller不能直接调用它,只能将子类的clone方法声明为共有类型,caller才能调用。

  1. //该实现类使用浅拷贝已经可以满足其需要了 
  2. public class implements Cloneable { 
  3. //这里已经提升了clone方法的级别为public。 
  4. public Employee clone() throws CloneNotSupportedException { 
  5. return (Employee)super.clone(); 
  6. //深拷贝clone方法,必须clone对象内部所有可变的实例域,其中这些可变类 
  7. //必须全部都实现了自己的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; 

注:数组对象可以通过Array的clone(public)方法完成元素的拷贝。

在C++中由于并不存在Object这样的单根结构的框架,因此C++是以另外一种方式表现该问题的,既缺省拷贝构造和缺省等于操作符重载。和Java类似,这两个方法也是member bitwise拷贝的,但这是由编译器在生成对象模型时自动完成的缺省行为,如果该类重载了拷贝构造函数和等于操作符,在需要copy的时候则会调用重载后的方法,类的实现者应该在这两个方法中完成深拷贝。C++中还可以通过将这两个方法显示的声明为private类型的方法来禁用这种对象之间的copy行为,一旦出现,编译器将会在在编译器报错。在C++中还存在一个explicit的关键字,可以有效的防止编译器通过自行推演隐式的调用对象的拷贝构造函数和等于操作符函数,见如下代码:

  1. //该类将会使用缺省的copy constructor,因此也会出现两个对象 
  2. //引用相同_name变量地址的问题。 
  3. class Employee { 
  4. private
  5. char* _name; 
  6. }; 
  7. //该类由于将这两个方法私有化,一旦出现对象的隐式拷贝构造, 
  8. //将会导致编译错误。 
  9. class Employee { 
  10. private
  11. Employee(Employee& other); 
  12. const Employee& operator= (Employee& other); 
  13. private
  14. char* _name; 
  15. }; 
  16. //将会调用重载后的这两个函数 
  17. class Employee { 
  18. Employee(Employee& other); 
  19. const Employee& operator= (Employee& other); 
  20. private
  21. char* _name; 
  22. }; 

注:C++中有一种被称为引用计数的技术,经常会用在这个地方,以便提高对象copy的效率。

3. 接口与回调:严格意义上讲,回调这个属于更多的应用于C/C++这些支持基于过程编程的语言,Java中的回调是通过接口的方式来实现的,由于在接口的实现类中可以附带更多的信息,因此其表达能力要由于C/C++中的函数指针,见如下代码:

  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参数既为接口,Thread对象在启动的时候会调用该接口实现对象的run方法,但是在调用之前可以给该实现类传入更多的状态等相关数据,以便在线程类调用run方法时可以得到更多的信息。
以下为回调函数在C/C++中的实现:

  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的基于接口的回调(Java的回调方式,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; 

在以上的实现中,T无需是某个接口的实现类,只要保证该类型包含run()方法即可,注意:C++中的模板是引用才编译的方式,如果没有任何Thread<T>的声明,不会导致任何编译错误,只有当声明的类型对象中不包含run()方法时才会导致编译错误。

4. 内部类:Java中内部类可以为私有内部类,既只有外部类可以访问该内部类,而Java外部类的可见性只有包可见和public两种。C++中的内部类比较类似于Java中的静态内部类,只是一种作用域限制的行为,以下为Java非静态内部类的说明:

1) 内部类可以访问外部类的所有域成员和域字段,这也同样包括私有的字段和成员。

2) Java的编译器在构造外部类调用内部类构造方法时,自动将外部类的this变量作为一个隐式参数传给了内部类的构造函数,内部类则在构造函数中保留了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. //编译器的会将以下构造隐式替换为InnerClass inner = new InnerClass(this); 
  14. //因为Java在为其编译的时候发现InnerClass为非静态内部类,则自动添加了以下构造: 
  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; 

注:针对以上事例,内部类InnerClass可以通过Employee.this._jobYears的全称来显式的代替_jobYears > 10 中的_jobYears。反过来在raiseSalary方法中可以通过this.new InnerClass()语法格式更加明确的创建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作为内部该内部类对象的外部类对象  
  17. //引用,可以根据需要替换为其他外部类对象的引用,如:  
  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. }  

注:在外部类的作用域之外调用public内部类的语法为 OutClass.InnerClass。

3) 局部内部类的可见范围仅仅限于声明该局部类的函数内部,见如下代码:

  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(); 

局部类同样可以访问函数内部的局部变量,但是要求该变量必须是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为外部函数的局部变量。 
  7. if (beep) 
  8. Tookkit.getDefaultToolkit().beep(); 
  9. ActionListener l = new TimePrinter(); 
  10. new Timer(interval,l).start(); 

为了规避局部类只能访问final局部变量的限制,既一次赋值之后不能再被重新赋值。但是我们可以通过数组的方式进行巧妙的规避,在下例中数组counter对象本身是final的,因此他不可以被重新赋值,然而其引用的数组元素则可以被重新赋值,见下例:

  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不是数组,而是被定义为final int counter, 
  7. //则会导致编译失败。 
  8. counter[0]++; 
  9. return super.compareTo(other); 

C++中同样可以做到这些,其规则和Java的主要差异为C++的内部类无法直接访问外部类的任何成员。

  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) 匿名内部类,其基本规则和局部内部类相似,差别在于该内部类不能有声明构造函数,这主要是因为Java要求类的构造函数和类名相同,而匿名内部类自身没有类名,因此在new新对象的时候,传入的构造函数参数为超类的构造函数参数。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为外部函数的局部变量。 
  7. if (beep) 
  8. Tookkit.getDefaultToolkit().beep(); 
  9. new Timer(interval,l).start(); 

5) 静态内部类,其功能和C++中的嵌套类非常相似,但是和Java自身的非静态内部类之间还是存在一些差异,如静态内部类不能直接访问外围类的对象引用域字段,但是可以访问外部类的static域字段(包括private)。在Java中只有内部类可以被定义为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是静态域字段,所以静态内部类可以直接访问, 
  7. //但是对于objectField对象域字段而言,由于静态内部类中没有包含 
  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(); 

以下示例中的内部类只能是静态内部类,因为该外部类的静态方法在返回内部类的实例时,无法将一个外部类的对象引用传递给该内部类,因为必须要求该内部类为静态内部类,否则将会报编译错误。

  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不是静态内部类,则需要将上例改写为:

  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解释代理类的机制

  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(调用处理接口)是代理类的核心处理单元。由于代理类对象只是包含了InvocationHandler 
  12. //这样一个对象实例,并且是存放于超类Proxy中的,而实际的被代理实例必须存放于InvocationHandler 
  13. //的实现类中,如这里的Integer对象v。其中的核心代理代码也是在InvocationHandler子类的 
  14. //invoke方法中完成的。 
  15. InvocationHandler h = new TraceHandler(v); 
  16. //1. 第一个参数表示ClassLoader,这里使用缺省加载器,因此传入null即可。 
  17. //2. 第二个参数表示该代理类需要implement的接口数组(Java中可以实现多个接口)。 
  18. //3. 调用处理器接口,是代理类如果实现代理的核心,后面会介绍该类。 
  19. //4. 将该代理类作为Integer的代理存入数组。 
  20. elements[i] = Proxy.newProxyInstance(nullnew Class[] {Comparable.class}, h); 
  21. Integer key = new Random().nextInt(elements.length) + 1
  22. //1. 由于代理类也都实现Comparable接口,因此可以用于Arrays.binarySearch中。 
  23. //2. 对代理类进行二分查找的比较时,将会直接调用代理类的compareTo方法。 
  24. //3. 该自动生成的Proxy的子类,其中的compareTo方法会将所有外部调用的信息,连同 
  25. // 方法名一并传给其内部调用处理器对象的invoke方法,并调用该方法(invoke). 
  26. //4. 这里Proxy子类会将所有实例化时指定接口(Comparable)的方法(compareTo),以及 
  27. // Object中toString、equals和hashCode方法的调用都会传递给调用处理器的invoke方法。 
  28. //5. 因此在输出结果中不仅可以看到compareTo方法的调用被打印出,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的子类是动态生成的,其具体的实现也是编译器动态生成后传给JVM的。 
  35. //因此这里是整个代理机制中唯一存放被代理对象的地方。 
  36. public TraceHandler(Object t) { 
  37. target = t; 
  38.  
  39. //在此例中,该方法是被Comparable接口中的compareTo方法调用的,该实现逻辑是位于该 
  40. //动态生成的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. //打印出实际被调用方法的名称和参数值。 
  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. //交给被代理类做实际的比较。 
  55. return method.invoke(target, args); 
  56. private Object target = null
  57. /* 输出结果如下: 
  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) 异常规范表示对于"已检查"(checked)异常,如FileNotFoundException等,既在程序运行期间可以预测到的逻辑问题引发的异常,对于该类异常,需要在包含该异常的函数声明部分标识出来,该函数可能会引发此类异常,如:

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

如果在loadImage中仍然存在其他"已检查",但是没有在函数的异常规范中声明出来,那么将会导致编译失败,因此对于函数中所有"已检查"必须按照Java异常规范的要求,在函数的声明中予以标识。对于该函数(loadImage)的调用者而言,在调用该函数时,必须将其放入try块中,同时在catch字句中捕捉异常规范中标识的异常或他们的超类。
对于"运行时异常"(unchecked or runtime),由于大多是程序Bug或JVM问题所致,因此不可预测性极强,如ArrayIndexOutOfBoundException,对于该类异常无需在函数的异常规范部分予以声明。

在C++标准中也同样存在异常规范的说法,如

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

所不同的是C++没有明确的要求如果函数内部抛出了该异常,则必须在函数声明的异常规范部分予以声明,对于函数调用者而言也同样没有这样的规定,必须捕获其中的异常,因此异常规范在目前的C++编译器中只是一种提示性的声明。Java和C++在异常规范方面还存在的另一个区别是,C++中,如果函数没有throw字句,那么该函数仍然可以抛出任何异常,但是对于Java的"已检查"(checked)异常则必须通过throws字句声明。

2) 如何抛出异常,在此方面,Java和C++没有太大的差异,唯一的不同是Java抛出的异常必须是Throwable的实现类,C++中则没有这样的限制,也不存在这样的异常接口。见如下代码:

  1. public void howToThrowException() { 
  2. if (someErrorOccurred) 
  3. //1. 通过throw关键字直接抛出指定异常类型的对象即可。 
  4. throw new MyCheckedException(); 

3) 异常捕捉:由于Java中所有的异常均继承自Throwable,所以catch(Throwable e)可以捕捉所有类型的异常,无论该异常是否为checked or unchecked异常,但是C++中并不存在这样的异常祖先接口,因此如果想达到这样的效果需要使用catch(...)关键字,这同样表示捕捉所有的异常。

4) 异常链:当异常第一次抛出并且被catch住的时候,catch块中的代码可以再次抛出捕捉到的异常,同时也可以为了使上层业务逻辑能够得到更加清晰的判断,在第一次捕捉到异常后重新定义一个新的异常并再次抛出。有的时候,如果上层逻辑在需要的时候依然可以看到原始异常,将会对错误的处理更加合理。在Java中可以通过异常链的方式达到这样的效果,见如下代码:

  1. public void testExceptionChain() throws MyCustomizedFileException { 
  2. try { 
  3. FileInputStream in = new FileInputStream("myfile"); 
  4. catch (FileNotFoundException e) { 
  5. //定义了新的,准备再次被抛出的异常对象。 
  6. Throwable te = new MyCustomizedFileException("access file error."); 
  7. //将原始异常链接到该异常对象的内部,以供之后需要时通过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. /* 输出结果如下: 
  19. FileNotFoundException 
  20. */ 

5) finally字句:在Java的异常机制中存在finally这样的关键字,其块中的代码无论异常是否发生都将会被执行,从而可以确保函数内部分配或者打开的资源都能在函数内部进行释放或者关闭,如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块中异常是否发生,finally块中的代码"in.close()" 都将会在函数退出之前或catch处理之后被执行,从而保证了FileInputStream对象能够在函数退出之前被关闭。然而这样的做法仍然可能导致一些影响代码流程的问题,如果try块中的代码没有产生异常,而是在finally中的in.close引发了异常,那么整个try{}catch{}finally{}代码块之后的代码将不会被执行,而是直接退出该函数,同时抛出in.close()引发的异常给该函数的调用者。修正代码如下:

  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++中,由于对象是可以在栈上声明并且分配空间的,当栈退出后会自行调用该对象的析构函数,因此该对象的资源释放代码可以放在类的析构函数中。该方式对于一个多出口的函数而言也是非常有效的,特别是对于加锁和解锁操作需要在同一个函数中完成,为了防止在某个退出分支前意外的漏掉解锁操作,可以采用该技巧,见如下代码:

  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

对于以上代码,无论函数从哪个分支退出,s1的析构函数都将调用,因此myLock的解锁操作也会被调用。

6) 异常堆栈跟踪:通过Throwable的getStackTrace方法获取在异常即将被抛出的时间点上程序的调用堆栈,这样有利于日志的输出和错误的分析,见如下代码:

  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对象函数当前函数的运行栈信息,见如下代码:

  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个宏来获取当前函数的函数名、行号和文件名,但是无法得到调用栈信息,如果确实需要这样的信息,只能通过操作系统的工具包来协助完成(仅针对Debug版本),目前Windows(vc)和Linux(gcc)都提供这样的开发包。

2. 断言:是主要用于开发、调试和系统集成测试期间进行Debug的一种方式和技巧,语法如下:

  1. assert condition OR assert condition : expression 

其中assert为关键字,当condition为false时,程序运行中断,同时报出指定的错误信息,如果使用assert的后面一种形式,expression的结果将会同时输出,这样更有助于错误的判断,见如下两种代码形式:

  1. public static void main(String[] args) { 
  2. int a = 5
  3. assert a > 10 : a; 
  4. System.out.println("Ok."); 
  5. /* 输出结果: 
  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. /* 输出结果: 
  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" 运行期参数。如果断言被禁用,assert中的代码不会被执行,因此在系统发布后也不会影响程序的运行时效率。使用者也可以通过该命令行参数-ea:MyClass -ea:com.mypackage.mylib 来指定需要启用断言的class和package,如果启用的是package,那么该包内的所有class都将启用断言。在C++中,是依靠crt中的assert(cond)函数来实现的,如果cond为false,程序将会立即停止,但是在使用前首先需要保证assert.h文件被包含进当前文件,再有就是当前编译的程序必须是Debug版本,对于Release版本,无论Win32和Linux,断言的语句都将不会被执行。

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

【系列文章】

  1. Java和C++在细节上的差异:泛型程序设计
  2. Java和C++在细节上的差异:程序设计结构
  3. Java和C++在细节上的差异:枚举与反射
责任编辑:林师授 来源: Stephen_Liu的博客
相关推荐

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内部类外部类

2010-01-28 15:22:12

C++嵌套类

2010-02-05 15:32:33

Java内部类

2009-08-26 18:00:07

C#内部类

2009-06-11 13:08:29

Java内部类Java编程思想

2012-11-08 09:49:30

C++Java程序员

2020-09-21 07:00:42

Java内部类接口

2011-03-29 14:11:15

内部类

2011-07-21 15:44:33

Java内部类

2023-03-06 07:53:36

JavaN种内部类

2011-07-20 16:30:42

C++

2009-06-11 11:07:25

Java局部内部类Final类型

2015-12-08 09:05:41

Java内部类

2010-08-26 10:41:45

C#内部类
点赞
收藏

51CTO技术栈公众号