Java和C++在细节上的差异(三)

  接Java和C++在细节上的差异(二)

  五、反射:

  1、ava的反射机制主要表现为四点:

  1)在运行中分析类的能力;

  2)在运行中查看对象;

  3)实现数组的操作代码;

  4)利用Method对象,这个对象很像C++中的函数指针。

  注:Java的基于反射的应用主要用于一些工具类库的开发,在实际的应用程序开发中应用的场景较少。

  2、获取对象的名称(字符串形式) vs 通过对象的名称(字符串形式)创建对象实例,见如下代码:

  1publicstaticvoidmain(String args[]) {2//1. 通过对象获取其字符串表示的名称3Date d=newDate();      //or Class<? extends Date> c1 = d.class;4Class<?extendsDate>c1=d.getClass();5String className=c1.getName();67//2. 通过字符串形式的名称创建类实例。8className=”java.util.”+className;9try{10Class c2=Class.forName(className);11//这里用到的newInstance用于创建c2所表示的对象实例,但是必须要求待创建的类实例12//具有缺省构造函数(无参数),很明显newInstance调用并未传入任何参数用于构造对象。13Date d2=(Date)c2.newInstance();14}catch(ClassNotFoundException e) {15e.printStackTrace();16}catch(InstantiationException e) {17e.printStackTrace();18}catch(IllegalAccessException e) {19e.printStackTrace();20}21}

  如果需要通过调用带有参数的构造函数来创建对象实例,需要使用java.lang.reflect.Constructor对象来完成,见如下代码:

  1publicstaticvoidmain(String args[]) {2String className=”java.util.Date”;3try{4Class c2=Class.forName(className);5//找到只接受一个long类型参数的构造器6Constructor cc=c2.getConstructor(long.class);7longll=45L;8//将该Constructor期望的指定类型(long)的参数实例传入并构造Date对象。9Date dd=(Date)cc.newInstance(ll);10System.out.println(“Date.toString =”+dd);11}catch(Exception e) {12e.printStackTrace();13}14}

  3、遍历一个未知类型的所有域、构造方法和域方法,见如下函数原型:

  Field[] getFields(); 返回指定对象域字段数组,主要包含该类及其超类的所有公有(public)域。

  Field[] getDeclaredFields();返回指定对象域字段数组,主要包含该类自身的所有域,包括private等。

  Method[] getMethods(); 返回指定对象域方法数组,主要包含该类及其超类的所有公有(public)域方法。

  Method[] getDeclaredMethods();返回指定对象域方法数组,主要包含该类自身的所有域方法,包括private等。

  Constructor[] getConstructors(); 返回指定对象构造函数数组,主要包含该类所有公有(public)域构造器。

  Constructor[] getDeclaredConstructors();返回指定对象构造函数数组,主要包含该类所有域构造器。

  int getModifiers(); 返回一个用于描述构造器、方法或域的修饰符的整型数值,使用Modifier类中的静态方法可以协助分析这个返回值。

  String getName(); 返回一个用于描述构造器、方法和域名的字符串。

  Class[] getParameterTypes(); 返回一个用于描述参数类型的Class对象数组。

  Class[] getReturnType(); 返回一个用于描述返回值类型的Class对象。

  1 private static void printConstructors(Class c1) {2 Constructor[] constructors = c1.getDeclaredConstructors();3 for (Constructor c : constructors) {4 String name = c.getName();5 System.out.print(” “);6 String modifiers = Modifier.toString(c.getModifiers());7 if (modifiers.length() > 0)8 System.out.print(modifiers + ” “);9 System.out.print(name + “(“);10 11 Class[] paramTypes = c.getParameterTypes();12 for (int j = 0; j < paramTypes.length; ++j) {13 if (j > 0)14 System.out.print(“,”);15 System.out.print(paramTypes[j].getName());16 }17 System.out.println(“);”);18 }19 }20 21 private static void printMethods(Class c1) {22 Method[] methods = c1.getDeclaredMethods();23 for (Method m : methods) {24 Class retType = m.getReturnType();25 String name = m.getName();26 System.out.print(” “);27 28 String modifiers = Modifier.toString(m.getModifiers());29 if (modifiers.length() > 0)30 System.out.print(modifiers + ” “);31 System.out.print(retType.getName() + ” ” + name + “(“);32 Class[] paramTypes = m.getParameterTypes();33 for (int j = 0; j < paramTypes.length; ++j) {34 if (j > 0) 35 System.out.print(“, “);36 System.out.print(paramTypes[j].getName());37 }38 System.out.println(“);”);39 }40 }41 42 private static void printFields(Class c1) {43 Field[] fields = c1.getDeclaredFields();44 for (Field f : fields) {45 Class type = f.getType();46 String name = f.getName();47 System.out.print(” “);48 String modifiers = Modifier.toString(f.getModifiers());49 if (modifiers.length() > 0)50 System.out.print(modifiers + ” “);51 System.out.println(type.getName() + ” ” + name + “;”);52 }53 }54 55 public static void main(String args[]) {56 String name = “java.lang.Double”;57 try {58 Class c1 = Class.forName(name);59 Class superc1 = c1.getSuperclass();60 String modifier = Modifier.toString(c1.getModifiers());61 if (modifier.length() > 0)62 System.out.print(modifier + ” “);63 64 System.out.print(“class ” + name);65 if (superc1 != null && superc1 != Object.class)66 System.out.print(” extends ” + superc1.getName());67 68 System.out.print(“\n{\n”);69 printConstructors(c1);70 System.out.println();71 printMethods(c1);72 System.out.println();73 printFields(c1);74 System.out.println(“}”);75 } catch (Exception e) {76 e.printStackTrace();77 }78 }79 /* 输出结果如下:80 public final class java.lang.Double extends java.lang.Number81 {82 public java.lang.Double(java.lang.String);83 public java.lang.Double(double);84 85 public boolean equals(java.lang.Object);86 public java.lang.String toString();87 public static java.lang.String toString(double);88 public int hashCode();89 public static native long doubleToRawLongBits(double);90 public static long doubleToLongBits(double);91 public static native double longBitsToDouble(long);92 public int compareTo(java.lang.Double);93 public volatile int compareTo(java.lang.Object);94 public byte byteValue();95 public short shortValue();96 public int intValue();97 public long longValue();98 public float floatValue();99 public double doubleValue();100 public static java.lang.Double valueOf(double);101 public static java.lang.Double valueOf(java.lang.String);102 public static java.lang.String toHexString(double);103 public static int compare(double, double);104 public boolean isNaN();105 public static boolean isNaN(double);106 public boolean isInfinite();107 public static boolean isInfinite(double);108 public static double parseDouble(java.lang.String);109 110 public static final double POSITIVE_INFINITY;111 public static final double NEGATIVE_INFINITY;112 public static final double NaN;113 public static final double MAX_VALUE;114 public static final double MIN_NORMAL;115 public static final double MIN_VALUE;116 public static final int MAX_EXPONENT;117 public static final int MIN_EXPONENT;118 public static final int SIZE;119 public static final java.lang.Class TYPE;120 private final double value;121 private static final long serialVersionUID;122 }123 */

  4、通过反射编写泛型数组代码,见如下代码比较:

  1 static Object[] badArrayGrow(Object[] a) { 2 int newLength = a.length * 11 / 10 + 10; 3 //该对象数组的在创建时是基于Object的,所以返回后, 4 //再装回其他类型数组时将会抛出ClassCastException的异常。 5 Object[] newArray = new Object[newLength]; 6 System.arraycopy(a,0,newArray,0,a.length); 7 return newArray; 8 } 9 10 static Object goodArrayGrow(Object a) {//这里的参数务必为Object,而不是Object[] 11 Class c1 = a.getClass();12 if (!c1.isArray()) 13 return null;14 //这里用于获取数组成员的类型15 Class componentType = c1.getComponentType();16 //获取数组的长度。17 int length = Array.getLength(a);18 int newLength = length * 11 / 10 + 10;19 //通过数组成员的类型和新的长度值来创建一个和参数类型相同的数组,20 //并增加他的空间,最后再返回。21 Object newArray = Array.newInstance(componentType,newLength);22 System.arraycopy(a,0,newArray,0,length);23 return newArray;24 }25

  5、在运行时使用反射的对象或动态调用反射之后的方法。

  1)获取域字段和设置域字段:

  

  1 public void testField() { 2 Employee harry = new Employee(“Harry Hacker”,35000,10); 3 Class c1 = harry.getClass(); 4 Field f = c1.getDeclaredField(“name”); 5 //由于name字段有可能是Employee类的私有域字段,如果直接调用会致使JVM 6 //抛出安全异常,为了避免该异常的发生,需要调用下面的语句来得以保证。 7 f.setAccessible(true); 8 Object v = f.get(harry); 9 System.out.println(v);10 }

  2)通过Method的invoke函数动态调用反射后的方法:

  该方式有些类似于C#的委托(delegate)和C++的函数指针。

  

  1 public int add(int param1, int param2) { 2 return param1 + param2; 3 } 4 5 public static void main(String[] args) throws Exception { 6 Class classType = MyTest.class; 7 Object myTest = classType.newInstance(); 8 Method addMethod = classType.getMethod(“add”,int.class,int.class); 9 //如果add为静态方法,这里的第一个参数传null10 Object result = addMethod.invoke(myTest, 100,200); 11 System.out.println(result); 12 }

  6、C++自身并没有提供像Java这样完备的反射机制,只是提供了非常简单的动态类型信息,如type_info和typeid。然而在一些C++的第三方框架类库中提供了类似的功能,如MFC、QT。其中MFC是通过宏的方式实现,QT是通过自己的预编译实现。在目前的主流开发语言中,也只有C#提供的反射机制可以和Java的相提并论。

  六、接口与内部类

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

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

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

  以下为Java的接口和抽象类的定义方式:

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

  在C++中同样存在接口和抽象类的概念,也和Java一样不能被实例化,但是并没有相应的关键字存在,而是以一种潜在规则的方式存在,见如下代码:

  1 //Comparable对象声明的方法中只有纯虚方法存在(析构函数除外),且没有任何成员变量。2 class Comparable {3 public:4 virtual ~Comparable() {}5 //compareTo为纯虚方法6 virtual int compareTo(Comparable& other) = 0;7 }8 9 //Employee对象中存在部分纯虚方法,且可以有成员变量存在。10 class Employee {11 public:12 virtual int compareTo(Comparable& other) { return 0; }13 virtual int backgroud() = 0;14 15 private:16 int _age;17 }

  在C++的实现中,基于接口编程,同时导出C接口的工厂方法对于跨编译器极为重要,该方式比较类似于Windows中的COM技术。

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

  1 class TcpServerTask {2 public:3 virtual void run() {}4 }5 6 class SentObjectTask {7 public:8 virtual void run() {}9 }10 11 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 }7 }8 //深拷贝clone方法,必须clone对象内部所有可变的实例域,其中这些可变类9 //必须全部都实现了自己的clone方法,否则将会跑出异常。10 public class Employee implements Cloneable {11 public Employee clone() throws CloneNotSupportedException {12 //缺省clone完成了域字段的按位浅拷贝。13 Employee cloned = (Employee)super.clone();14 cloned.hireday = (Date)hireday.clone();15 }16 private Date hireday;17 }

  注:数组对象可以通过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 5 public class MyTask implements Runnable {6 public MyTask(int taskID) {7 _taskID = taskID;8 }9 10 public void setOk(bool ok) {11 _ok = ok;12 }13 14 public void run() {}15 }16 17 public static void main(String[] args){18 MyTask t = new MyTask(5);19 Thread thrd = new Thread(t);20 t.setOk(true);21 thrd.start();22 }

  这里的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 6 int testCallback(int a,int b) {7 return a * b;8 }9 10 int main() {11 TestCallback cb = testCallback;12 return testCall(cb,5,6);13 }

  在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;8 }

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

  相关链接:

  Java和C++在细节上的差异(一)

  Java和C++在细节上的差异(二)

不敢接受失败的人,往往是那些追求完美的人,

Java和C++在细节上的差异(三)

相关文章:

你感兴趣的文章:

标签云: