技术开发 频道

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

        2. Object:

  Java是单根结构的框架,所有的对象都是Object的子类,即使在对象声明时没有进行直接的指定,Java的编译器将会自行搞定这些。C++中没有适当的类作为所有对象的根类,然而在有些类库中可以自行定义,如MFC的CObject等。Java的Object中有3个非常重要的方法equals、hashCode和toString。如果子类中重载了他们中的任意一个方法,同时也建议重载另外两个域方法。

  1) equals: 主要用于判定两个对象是否相等。类的实现者可以根据自己的真实逻辑来重新实现该方法,通用实现规则见下例:

1     public class Employee {
2         //1. 显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。
3         public boolean equals(Object otherObject) {
4             //2. 检测this与otherObject是否引用同一个对象(一种优化)
5             if (this == otherObject)
6                 return true;
7             //3. 检测otherObject是否为null,如果null,则返回false。
8             if (otherObject == null)
9                 return false;
10             //4. 比较this与otherObject是否属于同一个类。
11             //如果子类中的equals语义各不相同,使用下面的getClass方式,精确定义类类型。
12             if (getClass() != otherObject.getClass())
13                 return false;
14             //如果子类中的equal语义和超类完全相同,可以使用instanceof检测即可。
15             //5. 将otherObject转换为相应的类类型变量
16             Employee other = (Employee)otherObject;
17             //6. 现在开始对所有需要比较的域进行比较了。其中使用==比较基本类型,         //使用equals比较对象类型。
18             return name.equals(other.name) && salary == other.salary;
19         }
20     }

  注:数组元素的比较可以调用Arrays.equals方法检测。如果子类中重新定义了equals方法,就要在其中包含调用super.equals(other).

  Java在语言规范中给出了自定义equals方法需要遵守的规则:

  自反性: 对于任何非空引用x,x.equals(x)应该返回true。

  对称性: 对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。

  传递性: 对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。

  一致性: 如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。

  对于任意非空引用x,x.equals(null)应该返回false。

  2) hashCode: 导出一个经过哈希计算后的整型值,Java对hashCode的缺省实现是返回当前对象的存储地址。一下列出String的hashCode实现方式:

1     public int hashCode() {
2         int hash = 0;
3         for (int i = 0; i < length(); ++i)
4             hash = 31 * hash + charAt(i);
5         return hash;
6     }

  注:自定义类型的equals和hashCode定义必须一致,如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。如果打算实现自定义的hashCode方法,推荐使用在对象构造初始化后就不会再改变的域字段作为hashCode的计算因子。否则一旦使用可变域资源作为hashCode计算因子的一部分,将会导致一些隐藏的问题。比如当Employee对象实例存入HashMap中,但是使用者在存入集合之后,修改了某个参数hashCode计算的域字段的值,此后再在HashMap中查找原有对象时由于hashCode已经改变,因此即使该对象已经存入HashMap中,结果是仍然无法找到最初存入的对象了。数组类型的hashCode,可以通过Arrays.hashCode方法计算得出。

  3) toString: Java中比较推荐的实现方式为:

1     public String toString() {
2         return getClass().getName() +
3             "field1 = " + field1 +
4             "field2 = " + field2;
5     }

  注:C#的Framework中也存在一个类似的Object对象,作为C#所有对象(包括自定义对象)的唯一根类,其中也有对应的3个方法equals、hashCode和toString。Effective C#中针对这3个方法提供了一个很好的建议,既如果自定义类重载了这3个方法中任何一个,那么强烈建议该类也重载另外两个域方法。如对equals和toString而言,如果x.equals(y)返回true,那么x.toString.equals(y.toString)也将返回true,反之亦然。针对equals和hashCode域方法还有一种推荐的实现方式,如下:

1     public bool equals(Object other) {
2         return toString().equals(other.toString());
3     }
4    
5     public int hashCode() {
6         return toString().hashCode();
7     }

  3. 包装类和自动打包:

  1) 包装器对象均为不可变对象,如String,既一旦初始化之后其值将不会再被改变。包装器类是final类,不能为继承。

  2) 自动拆包和打包:Integer n = 3; n++; 在执行n++时,Java编译器将自动插入一条拆包指令,然后进行自增计算,最后再将结果打入对象包内。

  3) 自动打包的规范要求boolean, byte, char <= 127, 和介于-128--127之间的short和int被包装到固定的对象中,见如下代码:

1     public void test() {
2         Integer a1 = 1000;
3         Ingeger a2 = 1000;
4         if (a1 == a2)
5             System.out.println(
6                 "This won't be printed out because they are greater than 127.");
7  
8         Integer a3 = 100;
9         Ingeger a4 = 100;
10         if (a3 == a4)
11             System.out.println(
12                 "This will be printed out because they are less then 127.");
13     }

  4) 打包和拆包过程是编译器行为,不是虚拟机行为,是编译器在生成字节码的时候自动插入的指令。

  5) 包装类在容器中的应用。对于Java提供的泛型容器类其类型参数不能是primitive type,如int、float等,如果确实需要添加类似的数据,需要将相应的包装类作为容器类型参数,之后在插入原始类型数据,但是在插入过程中Java的编译器将自动插入打包指令,因此实际插入到容器中的仍然是包装类对象,见如下代码:

1     public static void main(String args[]) {
2         ArrayList<Integer> l = new ArrayList<Integer>();
3         for (int i = 0; i < 10; ++i)
4             l.add(i);
5          
6         for (int i = 0; i < l.size(); ++i) {
7             System.out.printf("The value is %d.\t",l.get(i));
8             System.out.printf("The class name is %s.\n"
9                 , l.get(i).getClass().getName());
10         }
11     }
12     /*    结果如下:
13         The value is 0.    The class name is java.lang.Integer.
14         The value is 1.    The class name is java.lang.Integer.
15         The value is 2.    The class name is java.lang.Integer.
16         The value is 3.    The class name is java.lang.Integer.
17         The value is 4.    The class name is java.lang.Integer.
18         The value is 5.    The class name is java.lang.Integer.
19         The value is 6.    The class name is java.lang.Integer.
20         The value is 7.    The class name is java.lang.Integer.
21         The value is 8.    The class name is java.lang.Integer.
22         The value is 9.    The class name is java.lang.Integer.
23     */

  4. Java函数的变参表示方式:

  PrintStream printf(String fmt,Object...args),其效果相当于 PrintStream printf(String fmt,Object[] args)。在C++中变参的表示方式为int printf(const char* fmt, ...); 其后的缺省参数需要通过C语言中提供的宏VA_LIST来协助完成。

0
相关文章