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

四、枚举:


    枚举的是在Java 1.5SE 中开始支持的,以下为Java枚举的基本概念和应用技巧:
    1.    所有的enum对象均是由class对象作为底层支持的,该对象继承自JDK中的Enum<E>,但是该底层类确实final类,既不能再被其他的类继承。
    2.    枚举的出现完全替代了原有的"public static final"常量表示法,枚举以一种更加合理、优雅和安全的方式替换了原有的方案。其最基本的声明方式如下:

1     public enum Color {
2 RED, BLUE, BLACK, YELLOW
3 }

    3.    Enum<E>中构造函数的原型为protected Enum(String name, int ordinal),自定义的枚举对象会将自身的名字以字符串的形式,同时将自己在整个常量从声明的顺序作为超类构造函数的两个参数传给超类并由超类完成必要的初始化,如:RED枚举常量将调用super("RED",0)。
    4.    枚举中可以定义构造函数、域方法和域字段,但是枚举中的构造器必须是私有(private)的,如果自定义枚举中有了自定义的构造函数,那么每个枚举常量在声明时必须按照自定义构造函数的规则传入参数。枚举对象的构造函数只是在枚举常量对象声明的时刻才调用一次,之后再也不能像普通对象那样通过new的方法创建,见如下代码:

1     public enum Size {
2 SMALL(0.8),
3 MEDIUM(1.0),
4 LARGE(1.2);
5 double pricingFactor;
6 private Size(double p) {
7 pricingFactor = p;
8 }
9 }

    注:枚举常量列表必须写在最前面声明,否则编译器报错。
    5.    可以给自定义枚举添加域方法,见如下代码:

 1     public enum Size {
2 SMALL(0.8),
3 MEDIUM(1.0),
4 LARGE(1.2);
5 private double pricingFactor;
6 Size(double p) {
7 pricingFactor = p;
8 }
9 public double getPricingFactor() {
10 return pricingFactor;
11 }
12 }

    6.    枚举中常用域方法:

 1     public enum Size{
2 SMALL,
3 MEDIUM,
4 LARGE;
5 }
6 public static void main(String[] args){
7 //两种获得枚举类型的方法
8 Size s1 = Size.SMALL;
9 //valueOf的函数原型为<T extends Enum<T>> T valueOf(Class<T> enumType,String name)
10 Size s2 = Enum.valueOf(Size.class, "SMALL");
11 //Size(自定义枚举)的valueOf方法是Java编译器在生成字节码的时候自动插入的。
12 Size s3 = Size.valueOf("MEDIUM");//1
13
14 //结果同上,枚举重载了equals方法
15 System.out.println("Size.MEDIUM.equals(Enum.valueOf(Size.class, \"MEDIUM\")):"+
16 Size.MEDIUM.equals(Enum.valueOf(Size.class, "MEDIUM")));
17
18 //遍历枚举类型中所有的成员,这里应用的Size.values方法和Size.valueOf方法
19 //一样均是编译器在生成字节码的时候自动插入的。
20 for(Size s:Size.values()){//2
21 //ordinal()和name()方法均为Enum提供的方法,返回枚举常量在声明时的构
22 //造函数中自动调用超类构造函数时传入的自身字符串名和在声明列表中的序号
23 System.out.println(s.ordinal()+" "+s.name()+" "+s.toString());
24 }
25 //compareTo方法缺省比较的是枚举常量的ordinal()的返回值。
26 if (s1.compareTo(s3) < 0)
27 System.out.println("Size.SMALL is less than Size.MEDIUM");
28 }

    7.    在枚举中可以声明基于特定常量的类主体,见如下代码:

 1     public enum Size {
2 //Small、ExtraLarge和ExtraExtraLarge均使用自定义的getPricingFactor
3 //方法覆盖Size提供的缺省getPricingFactor方法。
4 Small {
5 @Override
6 public double getPricingFactor() {
7 return 0.8;
8 }
9 },
10 //Medium和Large将使用Size内部缺省实现的getPricingFactor方法。
11 Medium,
12 Large,
13 ExtraLarge {
14 @Override
15 public double getPricingFactor() {
16 return 1.2;
17 }
18 },
19 ExtraExtraLarge {
20 @Override
21 public double getPricingFactor() {
22 return 1.2;
23 }
24 };
25 public double getPricingFactor() {
26 return 1.0;
27 }
28 }
29 public static void main(String args[]) {
30 for (Size s : Size.values()) {
31 double d = s.getPricingFactor();
32 System.out.println(s + " Size has pricing factor of " + d);
33 }
34 }
35 /* 结果如下:
36 Small Size has pricing factor of 0.8
37 Medium Size has pricing factor of 1.0
38 Large Size has pricing factor of 1.0
39 ExtraLarge Size has pricing factor of 1.2
40 ExtraExtraLarge Size has pricing factor of 1.2 */

    8.    枚举在switch语句中的用法,见如下代码:

 1     public enum Color {
2 RED, BLUE, BLACK, YELLOW
3 }
4 public static void main(String[] args) {
5 Color m = Color.BLUE;
6 //case语句中引用枚举常量时不需要再加上枚举的类型名了。
7 switch (m) {
8     case RED:
9 System.out.println("color is red");
10 break;
11 case BLACK:
12 System.out.println("color is black");
13 break;
14 case YELLOW:
15 System.out.println("color is yellow");
16 break;
17 case BLUE:
18 System.out.println("color is blue");
19 break;
20 default:
21 System.out.println("color is unknown");
22 break;
23 }
24 }

    9.    和枚举相关的两个容器EnumMap和EnumSet,声明和主要用法如下。

 1     EnumMap<K extends Enum<K>, V> 
2 EnumSet<E extends Enum<E>>
3 //Code Example 1
4 public enum State {
5 ON, OFF
6 };
7 public static void main(String[] args) {
8 //EnumSet的使用
9 EnumSet stateSet = EnumSet.allOf(State.class);
10 for (State s : stateSet)
11 System.out.println(s);
12
13 //EnumMap的使用
14 EnumMap stateMap = new EnumMap(State.class);
15 stateMap.put(State.ON, "is On");
16 stateMap.put(State.OFF, "is off");
17 for (State s : State.values())
18 System.out.println(s.name() + ":" + stateMap.get(s));
19 }
20
21 //Code Example 2
22 public enum Size {
23 Small,Medium,Large
24 }
25 public static void main(String args[]) {
26 Map<Size, Double> map = new EnumMap<Size, Double>(Size.class);
27 map.put(Size.Small, 0.8);
28 map.put(Size.Medium, 1.0);
29 map.put(Size.Large, 1.2);
30 for (Map.Entry<Size, Double> entry : map.entrySet())
31 helper(entry);
32 }
33 private static void helper(Map.Entry<Size, Double> entry) {
34 System.out.println("Map entry: " + entry);
35 }

    10.    Java枚举和C++枚举的主要区别为两点,一是C++中的枚举中只能定义常量,主要用于switch子句,二是C++中的枚举常量可以直接和数值型变量进行各种数学运算。

五、反射:

    1.    Java的反射机制主要表现为四点:
    1)    在运行中分析类的能力;
    2)    在运行中查看对象;
    3)    实现数组的操作代码;
    4)    利用Method对象,这个对象很像C++中的函数指针。
    注:Java的基于反射的应用主要用于一些工具类库的开发,在实际的应用程序开发中应用的场景较少。 
    2.    获取对象的名称(字符串形式) vs 通过对象的名称(字符串形式)创建对象实例,见如下代码:

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

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

 1     public static void main(String args[]) {
2 String className = "java.util.Date";
3 try {
4 Class c2 = Class.forName(className);
5 //找到只接受一个long类型参数的构造器
6 Constructor cc = c2.getConstructor(long.class);
7 long ll = 45L;
8 //将该Constructor期望的指定类型(long)的参数实例传入并构造Date对象。
9 Date dd = (Date)cc.newInstance(ll);
10 System.out.println("Date.toString = " + dd);
11 } catch (Exception e) {
12 e.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.Number
81 {
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为静态方法,这里的第一个参数传null
10 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的相提并论。



posted @ 2011-08-07 09:21  OrangeAdmin  阅读(1697)  评论(0编辑  收藏  举报