三、继承:
1. Java和C++在对象继承方面的主要差异:
对象的继承性是所有面向对象语言都支持的面向对象特性之一,Java和C++作为两个重要的面向对象开发语言在此方面有着较多的相似性,但是在有些概念的表示方式上还是存在着一定的差异,先列举如下:
1) 对象继承的关键字,Java中采用extents关键字,如class DeriveClass extends BaseClass, 在C++中则使用(:)冒号表示类之间的继承,如class DeriveClass : public BaseClass。
2) Java的继承方式中不存在public,protected和private,其表现行为和C++中的public继承完全一致。
3) 在有些情况下,子类中的方法需要显式的调用超类中的方法实现,特别是当子类中也存在同样方法签名的实现时,如果没有明确的指出需要调用超类的方法,Java的编译器会将子类当前的方法列为本次调用的候选方法,见如下代码:
2 public double getSalary() {
3 double baseSalary = getSalary();
4 return baseSalary + bonus;
5 }
6 }
以上代码中的getSalary()方法将会递归的调用其自身,而开发者的实际用意是调用超类中的getSalary方法,由于超类和子类中具有相同签名的该方法,因此编译器在此时选择了子类中的getSalary。其修改方式如下:
2 public double getSalary() {
3 double baseSalary = super.getSalary();
4 return baseSalary + bonus;
5 }
6 }
加上关键字super明确的指出要调用超类中的getSalary方法。在C++中的实现方式为BaseClass::getSalary(),既在方法签名的前面加上父类的名字和两个连在一起的冒号(::)。
2 public:
3 double getSalary() {
4 double baseSalary = BaseClass::getSalary();
5 return baseSalary + bonus;
6 }
7 }
4) Java中所有未声明为final的方法都视为可以继承的虚方法。在C++中,尽管没有此类限制,但是在实际的应用中还是存在一些潜在的技巧以达到此效果。对于C++类中声明的公有成员方法,如果该方法未声明为virtual,既虚函数,则暗示该类的子类实现者不要在子类中覆盖(override)该方法。
5) Java中不支持多重继承,不仅有效的避免了C++因多重继承而带来的一些负面影响,与此同时,在Java中可以通过继承(extends)单个父类和实现(implements)多个接口的方式更好表达该类设计意愿。
6) Java中如果子类和超类同时包含具有相同签名的公有域方法,那么在子类中将覆盖超类中的域方法。这其中的方法签名只是包括方法名和参数列表,既参数的个数和类型,函数的返回值不包含在方法签名中,但是在Java中针对该种方法覆盖的返回值还是存在一定的限制,既子类中的返回值的类型,或者与超类中该方法的返回值类型相同,或者为其返回类型的子类。C++中没有此类返回值类型的限制。但是Java的此类限制也会带来一些潜在的迷惑和危险,见如下代码:
2 public Employee[] getBuddies() { ... }
3 }
4
5 class Manager extends Employee {
6 public Manager[] getBuddies() { ... }
7 }
8
9 public static void main(String[] args) {
10 Employee[] m = new Manager().getBuddies();
11 //在Java中子类的数组在复制给超类的数组时不需要显式的转换,就像
12 //子类的实例赋值给超类的实例一样,也不需要任何显式的转换。
13 //赋值之后e和m指向相同的内存地址,同样e[0]和m[0]也指向相同的实例。
14 Employee[] e = m;
15 //本次赋值合法也不会引发任何异常,但是会导致一个潜在的问题,既
16 //m[0]的对象已经被悄悄的改变了,指向了Employee的另外一个子类。
17 e[0] = new OtherEmployee();
18 //此时再调用m[0]中Manager定义的域方法时将会引发Java的运行时异常。
19 m[0].setBonus(1000);
20 }
7) Java中的final类,如果某个自定义类型被加入final关键字,则表示该类将不能被继承,否则会直接产生编译错误。在C++中没有特殊的关键字类完成此类限制,然而在实际的应用中也同样存在一些潜在的技巧协助开发者来进行此类限制的甄别。如将父类中的析构函数不设置为虚函数,此方法则间接的暗示子类的实现者要留意,如果仍然继承该父类,那么在实现多态时,如BaseClass* c = new DeriveClass,如果之后需要释放c变量的内存资源时 delete c, 此时由于父类中的析构函数并不是虚函数,因此此次调用将只会执行父类的析构函数,而不会调用子类的析构函数,最终导致类分割所带来的一些潜在错误或资源泄漏。
8) 内联方法,在C++中有特殊的关键字inline用于帮助编译器来推断是否需要将该方法编译成内联方法,以提高运行时的效率。在Java中没有此类关键字,而是通过编译器的一连串推演,最终决定该域方法是否可以编译成内联方法,主要候选方法为简短、被频繁调用且没有真正被子类覆盖的域方法。
9) 超类到子类的强制类型转换。在Java中可以通过直接强转的方式来转换,如Manager m = (Manager)e。如果装换失败将会引发运行时异常ClassCastException,因此很多情况下为了避免此类异常的发生,需要在强转之前先进行判断,如if (e instanceof Manager) { ... }, 如果条件为真,装换将顺利完成。在C++中也可以采用这样的直接强转方法,但是即使类型不匹配程序也不会在强转是引发任何异常,而是在后面针对该变量的使用时才会导致错误的发生。在C++中存在dynamic_cast关键字,如dynamic_cast和dynamic_cast,前者为基于指针的转换,如果转换失败返回变量为NULL,而后者则会引发异常。
10) 抽象类:在Java中如果class被定义为abstract class,该类将不能被实例化,如果子类未能完全实现超类中所有的抽象方法,那么子类也将会被视为抽象类。C++中没有特殊的关键字来表示抽象类,而且通过将类中的一个或多个方法定义为纯虚方法来间接实现的,见如下C++代码,其中的first和second均为纯虚方法,既在方法的尾部添加" = 0 "。
2 public:
3 virtual void first() = 0;
4 virtual void second() = 0;
5 virtual void third();
6 }
11) protected关键字在Java和C++中针对域方法和域字段的访问方式存在着不同的限制级别,相同之处是protected的方法和字段都可以被子类直接访问,不同之处是Java中相同包中的类也可以直接他们。C++自身并不存在包的概念,然而即便是相同名字空间内的对象也不能直接访问。