【Java】C++和Java的差异

注: 以下内容摘录自Java编程思想一书

  • 最大的差异在于速度,解释过的Java要比C的执行速度慢上约20倍。无论什么都不能阻止Java语言进行编译,一些准实时编译器能显著加快速度,会出现适用于更多流行平台的纯固有编译器,但假若没有那些编译器,由于速度的限制,必须有些问题是Java不能解决的。
  • C++一样,Java也提供了两种类型的注释。
  • 所有东西都必须置入一个类。不存在全局函数或者全局数据,如果想获得与全局函数等价的功能,可考虑static方法和static数据置入一个类里。注意没有像结构、枚举或者联合这一类的东西,一切只有类Class。
  • 所有方法都是在类的主体定义的,所以用C++的眼光看,似乎所有函数都已嵌入inline,但实情并非如此。
  • Java中,类定义采取几乎和C++一样的形式,但没有标志结束的分号,没有class foo;这种形式的类声明,只有类定义。
 class MyClass {
     void myMethod() {
         // ...
     }
 }
  • Java中没有作用域范围运算符::Java利用点号做所有的事情,但可以不用考虑它,因为只能在一个类里定义元素,即使那些方法定义,也必须在一个类的内部,所以根本没有必要指定作用域的范围。一项差异是对static方法的调用,使用ClassName.methodName()。除此之外,package包的名字是用点号建立的,并能用import关键字实现C++#include的一部分功能,#include并不直接映射成import,但在使用时有类似的感觉。例如下面这个语句:
import java.awt.*;
  • C++类似,Java含有一些主类型(基本类型)Primitive type,以实现更有效率的访问。在Java中,这些类型包括boolean、char、byte、short、int、long、float以及double。所有主类型的大小都是固有的,且考虑到移植的问题与具体的机器无关,这肯定会对性能造成一定的影响,具体取决于不同的机器,对类型的检查在Java里变得更苛刻。例如:条件表达式只能是boolean布尔类型,不可使用整数;必须使用像X+Y这样的一个表达式结果,不能仅仅用X+Y来实现副作用。
  • char字符类型使用国际通用的16位Unicode字符集,所以能自动表达大多数国家的字符。
  • 静态引用的字串会自动转换成String对象,和CC++不同,没有独立的静态字符数组字串可供使用。
  • Java增添了三个右移位运算符>>>,具有与逻辑右移位运算符类似的功用,可在最末尾插入零值。>>则会在移位的同时插入符号位,即算术移位。
  • 尽管表面上类似,但与C++不同,Java数组采用的是一个颇为不同的结构,并具有独特的行为,有一个只读的length成员,通过它可知道数组有多大,而且一旦超过数组边界,运行期检查会自动丢弃一个异常。所有数组都是在内存堆里创建的,可将一个数组分配给另一个,其实只是简单地复制数组句柄。数组标识符属于第一级对象,它的所有方法通常都适用于其它所有对象。
  • 对于所有不属于主类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以在堆栈上创建不属于主类型的对象。所有主类型都只能在堆栈上创建,同时不使用new命令。所有主类型的类都有自己的封装器类,所以能够通过new创建等价的、以内存堆为基础的对象。主类型数组是一个例外,它们可像C++那样通过集合初始化进行分配,或者使用new。
  • Java中不必进行提前声明,若想在定义前使用一个类或方法,只需直接使用它即可,编译器会保证使用恰当的定义,所以和在C++中不同,不会碰到任何涉及提前引用的问题。
  • Java没有预处理机,若想使用另一个库里的类,只要使用import命令,并指定库名即可,不存在类似于预处理机的宏。
  • Java用包代替了命名空间,由于将所有东西都置入一个类,而且由于采用了一种名为封装的机制,它能针对类名进行类似于命名空间分解的操作,所以名字的问题不再进入考虑之列。数据包也会在单独一个库名下收集库的组件,只需简单地import导入一个包,剩下的工作会有编译器自动完成。
  • 被定义成类成员的对象句柄会自动初始化成null,对基本数据成员的初始化在Java里得到了可靠的保障,若不明确地进行初始化,它们就会得到一个默认值,零或等价的值。可对它们进行明确地初始化,要么在类内定义它们,要么在构建器中定义。采用的语法比C++的语法更容易理解,而且对于static和非static成员来说都是固定不变的,不必从外部定义static成员的存储,这和C++是不同的。
  • Java里,没有像CC++那样的指针。用new创建一个对象的时候,会获得一个引用,或将其称作句柄。
  String s = new String("hello");

然而,C++引用在创建时必须进行初始化,而且不可重定义到一个不同的位置。但Java引用并不一定局限于创建时的位置,它们可根据情况任意定义,这便消除了对指针的部分需求。在CC++里大量采用指针的另一个原因是为了能指向任意一个内存位置,这同时会使它们变得不安全,也是Java不提供这一支持的原因。指针通常被看作在基本变量数组中四处移动的一种有效手段。Java允许以更安全的形式达到相同的目标。将指针传递给方法时,通常不会带来太大的问题,因为此时没有全局函数,只有类,而且可传递对对象的引用。Java语言最开始声称自己完全不采用指针,后来又声明采用受到限制的指针,但不管在任何情况下,都不存在指针算术。

  • Java提供了与C++类似的构建器Constructor,如果不自己定义一个,就会获得一个默认构建器,而如果定义了一个非默认的构建器,就不会自动定义默认构建器。这和C++是一样的,注意没有复制构建器,因为所有自变量都是按引用传递的。
  • Java中没有破坏器Destructor,变量不存在作用域的问题,一个对象的存在时间是由对象的存在时间决定的,并非由垃圾收集器决定。有个finalize()方法是每一个类的成员,它在某种程度上类似于C++的破坏器,但finalize()是由垃圾收集器调用的,而且只负责释放资源,如打开的文件、套接字、端口、URL等等。如需在一个特定的地点做某样事情,必须创建一个特殊的方法,并调用它,不能依赖finalize()。而在另一方面,C++中的所有对象都会破坏,但并非 Java的所有对象都会被当作垃圾收集掉。由于 Java不支持破坏器的概念,所以在必要的时候,必须谨慎地创建一个清除方法,而且针对类内的基础类以及成员对象,需要明确调用所有清除方法。
  • Java具有方法重载机制,它的工作原理与C++函数的重载几乎是完全相同的。
  • Java不支持默认自变量。
  • Java中没有goto,它采取的无条件跳转机制是break标签或者continue标签,用于跳出当前的多重嵌套循环。
  • Java采用了一种单根式的分级结构,因此所有对象都是从根类Object统一继承的。而在C++中,可以在任何地方启动一个新的继承树,所以最好往往看到保护了大量树的一片森林。在Java中,无论如何都只有一个分级结构,尽管这表面上看似乎造成了限制,但由于每个对象肯定至少有一个Object接口,所以往往能获得更强大的能力。C++目前似乎是唯一没有强制限制单根结构的唯一一种OO语言。
  • Java没有模板或者参数化类型的其它形式,它提供了一些列集合,Vector向量,Stack堆栈以及Hashtable散列表,用于容纳Object引用。利用这些集合,一系列要求可得到满足,但这些集合并非是为实现像C++标准模板库STL那样的快速调用而设计的。
  • 垃圾收集意味着在Java中出现内存泄漏的情况会少得多,但也并非完全不可能,若调用一个用于分配存储空间的固有方法,垃圾收集器就不能对其进行跟踪监视。然而,内存泄漏和资源泄漏多是由于编写不当的finalize()造成的,或是由于在已分配的一个块尾释放一种资源造成的,破坏器在此时显得特别方便。垃圾收集器是在C++基础上的一种极大进步,使许多编程问题消失于无形之中。但对少数几个垃圾收集器力有不逮的问题,它确是不大适合的,但垃圾收集器的大量优点也使这一处缺点显得微不足道。
  • Java内建了对多线程的支持。利用一个特殊的Thread类,可通过继承创建一个新线程。若将synchronized同步关键字作为方法的一个类型限制符使用,相互排斥现象会在对象这一级发生。在任何给定的时间,只有一个线程能使用一个对象的synchronized方法。在另一方面,一个synchronized方法进入以后,它首先会锁定对象,防止其它任何synchronized方法再使用那个对象,只有退出了这个方法,才会将对象解锁。在线程之间,仍然要负责实现更复杂的同步机制,方法是创建自己的监视器类。递归的synchronized方法可以支持运作。若线程的优先等级相同,则时间的分片不能得到保证。
  • 不是像C++那样控制声明代码块,而是将访问限定符public、private、protected置入每个类成员的定义里。若未规定一个明确的限定符,就会默认为default,这意味着同一个包里的其它元素也可以访问它,相当于它们都成为C++的friend,但不可由包外的任何元素访问。类以及类内的每个方法都有一个访问限制符,决定它是否能在文件的外部可见。private关键字通常很少在Java中使用,因为与排斥同一个包内其它类的访问相比,default访问通常更加有用。然而,在多线程的环境中,对private的恰当运用是非常重要的。Java的protected关键字意味着可由继承者访问,亦可有包内其它元素访问。注意Java没有与C++的protected关键字等价的元素,后者意味着只能由继承者访问。
  • 嵌套的类。在C++中,对类进行嵌套有助于隐藏名称,并便于代码的组织,但C++的命名空间已使名称的隐藏显得多余。Java的封装或打包概念等价于C++的命名空间,所以不再是一个问题。Java引入了内部类的概念,它秘密保持指向外部类的一个句柄,创建内部类对象的时候需要用到。这意味着内部类对象也许能访问外部类对象的成员,无需任何条件,就好像那些成员直接隶属于内部类对象一样,这样便为回调问题提供了一个更优秀的方案,C++是用指向成员的指针解决的。
  • 由于存在前面介绍的那种内部类,所以Java里没有指向成员的指针。
  • Java不存在嵌入inline方法,Java编译器也许会自行决定嵌入一个方法,但我们对此没有更多的控制权。在Java中,可为一个方法使用final关键字,从而建议进行嵌入操作。然而,嵌入函数对于C++的编译器来说也是一种建议。
  • Java中的继承具有与C++相同的效果,但采用的语法不同。Java用extends关键字标志从一个基础类的继承,并用super关键字指出准备在基础类中调用的方法,它与当前所在的方法具有相同的名字,然而,Java中的super关键字只允许访问父类的方法,亦即分级结构的上一级。通过在C++中设定基础类的作用域,可访问位于分级结构教深处的方法。亦可用super关键字调用基础类构建器。正如早先指出的那样,所有类最终都会从Object里自动继承。和C++不同,不存在明确的构建器初始化列表,但编译器会强迫在构建器主题的开头进行全部的基础类初始化,而且不允许在主体的后面部分进行这一工作。通过组合运用自动初始化自己来自未初始化对象句柄的异常,成员的初始化可得到有效的保证。
public class Foo extends Bar {
    public Foo(String msg) {
        super(msg); // calls base constructor
    }
    public baz(int i) { // override
        super.baz(i); // calls base method
    }
}
  • Java中的继承不会改变基础类成员的保护级别,不能在Java中指定public、private或者protected继承,这一点与C++是相同的。此外,在衍生类的优先方法不能减少对基础类方法的访问。例如,假设一个成员在基础类中属于public,而用另一个方法代替了它,那么用于替换的方法也必须属于public,编译器会自动检查。
  • Java提供了一个interface关键字,它的作用是创建抽象基础类的一个等价物。在其中填充抽象方法,且没有数据成员。这样一来,对于仅仅设计成一个接口的东西,以及对于用extends关键字在现有功能基础上的扩展,两者之间便产生了一个明显的差异。不值得用abstract关键产生一种类似的效果,因为不能创建属于哪个类的一个对象。一个abstract抽象类可包含抽象方法,尽管并不要求在它里面包含什么东西,但它也能包含用于具体实现的代码。因此,它被限制成一个单一的继承,通过与接口联合使用,这一方案避免了对类似于C++虚拟基础类那样的一些机制的需要。为创建可进行实例化的一个interface的版本,需使用implements关键字,它的语法类似于继承的语法,如下所示:
 public interface Face {
     public void smile();
 }
 public class Baz extends Bar implements Face {
     public void smile() {
         System.out.println("a warm smile");
 }
  • Java中没有virtual关键字,因为所有非static方法都肯定会用到动态绑定。在Java中,程序员不必自行决定是否使用动态绑定。C++之所以使用了virtual,是由于对性能进行调整的时候,可通过将其省略,从而获得执行效率的少量提升。virtual经常会造成一定程度的混淆,而且获得令人不快的结果。final关键字为性能的调整规定了一些范围,它向编译器指出这种方法不能被取代,所以他的范围可能被静态约束,而且成为嵌入状态,所以使用C++非virtual调用的等价方式,这些优化工作是由编译器完成的。
  • Java不提供多重继承机制,至少不像C++那样。与protected类似,多重继承表面上是一个很不错的主意,但只有真正面对一个特定的设计问题时,才知道自己需要它。由于Java使用的是单根分级结构,所以只有在极少的场合才需要用到多重继承。interface关键字会帮助自动完成多个接口的合并工作。
  • 运行期的类型标识功能与C++极为相似。例如,为获得与句柄X有关的信息,可使用下述代码:
 X.getClass().getName();

为进行一个类型安全的类型转换,可使用:

 derived d = (derived) base;

这与旧式风格的C类型转换是一样的。编译器会自动调用动态类型转换机制,不要求使用额外的语法。尽管它并不像C++“new cast”那样具有易于定位转换类型的优点,但Java会检查使用情况,并丢弃那些异常,所以它不会像C++那样允许不好的类型转换的存在。

 public void f(Obj b) throws IOException {
     myresource mr = b.createResource();
     try {
         mr.useResource();
    } catch (MyException e) {
        // handle my exception
    } catch (Throwable e) {
        // handle all other exceptions
    } finally {
        mr.dispose(); // special cleanup
    }
 }
  • Java的异常规范比C++的出色的多。丢弃一个错误的异常后,不是像C++那样在运行期间调用一个函数,Java异常规范是在编译期间坚持并执行的。除此以外,被取代的方法必须遵守那一方法的基础类版本的异常规范,它们可丢弃指定的异常或者从那些异常衍生出来的其它异常,这样一来,最终得到的是更为健壮的异常控制代码。
  • Java具有方法重载的能力,但不允许运算符重载。String类不能用+和+=运算符连接不同的字串,而且String表达式使用自动的类型转换,但那是一种特殊的内建情况。
 static final int SIZE = 255;
 static final int BSIZE = 8 * SIZE;
  • 由于安全方面的原因,应用程序的编程与程序片的编程之间存在着显著的差异。一个最明显的问题是程序片不允许我们进行磁盘的写操作,因为这样做会造成从远程站点下载的、不明来历的程序可能胡乱改写我们的磁盘。随着Java对数字签名技术的应用,这一情况已有所改观。根据数字签名,我们可以知道一个程序片的全部作者,并验证他们是否已获得授权。Java还会进一步增强程序片的能力。
  • 由于Java在某些场合可能显得限制太多,所以有时不愿用它执行像直接访问硬件这样的重要任务。Java解决这个问题的方案是JNI,允许我们调用由其它语言写成的函数,如CC++。这样一来,我们就肯定能够解决与平台有关的问题,采用一种不可移植的形式,但那些代码随后会被隔离起来。程序片不能调用JNI,只用应用程序才可以。
  • Java提供对注释文档的内建支持,所以源码文件也可以包含它们自己的文档。通过一个单独的程序,这些文档信息可以提取出来,并重新格式化成HTML,这无疑是文档管理及应用的极大进步。
  • Java包含了一些标准库,用于完成特定的任务。C++则依靠一些非标准的、由其它厂商提供的库。这些任务包括:网络、数据库、多线程、分布式、压缩、商贸等。由于这些库简单易用,而且非常标准,所以能极大加快应用程序的开发速度。
  • Java包含了Java Beans标准,后者可创建在可视编程环境中使用的组件。由于遵守同样的标准,所以可视化能够在所有厂商的开发环境中使用。由于我们并不依赖一家厂商的方案进行可视组件的设计,所以组件的选择余地会加大,并可提高组件的效能。除此之外,Java Beans的设计非常简单,便于程序员理解,而那些由不同的厂商开发的专用组件框架则要求进行更深入的学习。
  • 若访问Java句柄失败,就会丢弃一次异常。这种丢弃测试并不一定要正好在使用一个句柄之前,根据Java的设计规范,只是说异常必须以某种形式丢弃。许多C++运行期系统也能丢弃那些由于指针错误造成的异常。
  • Java通常显得更为健壮,为此采取的手段为:对象句柄初始化为null;句柄肯定会得到检查,并在出错时丢弃异常;所有数组访问都会得到检查,及时发现边界违例情况;自动垃圾收集,防止出现内存泄漏;明确、傻瓜式的异常控制机制;为多线程提供了简单的语言支持;对网络程序片进行字节码校验。

你可能感兴趣的:(编程语言)