Java究竟是不是纯种的面向对象?

Java——是否确实的 “纯面向对象”?让我们深入到 Java 的世界,试图来证实它。

在我刚开始学习 Java 的前面几年,我从书本里知道了 Java 是遵循 “面向对象编程范式(Object Oriented Programming paradigm)”的。在 Java 世界内一切都是对象,甚至包括字符串(String)这些都是对象(在 C 语言中,字符串是字符数组),那时候,我认为 Java 是一种面向对象的语言。

但是在后来,我在互联网站上陆续看到不少开发者说 “Java 实际上不是纯粹的面向对象,因为并不是所有的东西在 Java 世界都是一个对象”。他们很多的论点都可以概括为以下两点:

  • 所有的静态内容( static 关键修饰的变量和方法)不属于任何对象,所以这些是非对象的东西。
  • 所有基本类型(char,boolean,byte,short,int,long,float,double)都不是对象,因为我们不能做类似正常对象的所具有的操作(例如:使用“.”来访问对象的属性和方法)。

在那时,由于个人知识经验储备有限,我又很容地相信上面的论点,并且也开始认为 “Java 不是纯粹的面向对象编程语言”。

到了更后来,在我的一次 JVM 学习过程中,我有了新的发现:

JVM 在创建对象的时候,实际上会创建两个对象:

  • 一个是实例对象。
  • 另一个是 Class 对象。该 Class 对象在 JVM 内仅仅会装载一次,该类的静态方法和静态属性也一同装载,JVM 使用该 Class 对象来创建具体的实例对象(如上面的对象)。

例如,在下面的 Java 语句中,将有两个对象被创建:

Employee emp = new Employee ();

一个是实例对象 emp ;另一个则是 Class 对象,我们可以通过 Employee.class 引用到它;这个 Class 对象拥有所有的这个类定义的静态变量和静态方法,同时,如果我们访问通过 emp 对象来访问静态内容,会发现它其实指向的对象就是 Employee.class 。

这也揭开了另一个迷:为什么静态内容在一个对象中(不管是 emp 还是 emp2)改变了,在另一个对象中也同时改变,因为这两个对象改变的都是在 Employee.class 同一个对象里面的内容。

现在,上面说到的第一个论点我们要取消了。因为,静态内容确实被证实属于一个对象。

但是我们还要确认第二个论点:正如早前提到的,原始类型在 Java 中不是对象,它们无法做类似对象的操作。为了解决这个问题,Java 官方为每一个原始类型推出了对应的包装类(比如:Integer 对应 int,Long 对应 long,Character 对应 char),所以,其实现在我们可以为原始类型创建一个包装对象,同时对它们做对象相关的操作。并且,由于自动拆装箱,我们可以把一个原始类型值赋值给它对应的包装类的引用。但是我们仍然不能对这些原始类型做对象的操作——我们需要创建对应包装类的对象。

例如:

Integer obj = new Integer (5); // here we can do i.toString ();
int i = 5; // but we can't do i.toString () here

到目前为止,从一个最终用户的角度上来看的,我们可以确认 “原始类别不是对象”。( Java 开发人员是 Java 的最终用户,因为我们正在使用它,而不是创造它 )。

如果站在 JVM 的视角,会有新的发现:

其实,在 JVM 看来它把所有的 “原始类型” 都是当作对象处理” ,要证明这一点可以通过 Class 类的源代码或者 Javadoc 中 Class 类的说明。

根据 java.lang.Class 类的源代码,该类的注释是:

Java 官方描述:

Class 类的实例表示正在运行的 Java 应用程序的类和接口。像枚举是一种类和注解则是一种接口。每个数组也属于被反射作为由具有相同的元素类型和尺寸的数目的所有阵列共享一类对象的类。原始的 Java 类型(boolean, byte, char, short, int, long, float, and double)和关键字 void 也表示为 Class 对象。

同时也根据 Javadoc 中对 Class.isPrimitive ()方法的定义,来判断

Java 官方描述:

public boolean isPrimitive ()

判断指定的 Class 对象是否代表一个基本类型。

一共有 9 种设定好的 Class 对象来表示对应的基本类型和 void 关键字。这些对象都是由 JVM 创建的。…

return

当且仅当该类表示一个真正的基本类型

以上都说明,在 JVM 内部,其实原始类型就是对象。

当你打开 Javadoc 对 Class 类的定义中,通过 “CTRL+F ” 查找关键字 “primitive”, 将会发现证据在表面 “在 JVM 里,它把基本类型当作对象来处理的”。

我们可以再来看一个例子: Integer.TYPE,在这部分文档清晰记录着:

Java 官方描述:

public static final Class<Integer> TYPE

The Class instance representing the primitive type int.

以上都说明,在 JVM 内部,其实原始类型就是对象。

那么,既然说 “JVM”会为所有的基本类型创建一个对象,那我们为什么还那么常用 “原始类型”, 而不是直接使用对应的包装类对象呢?

这是因为,为 “原始类型” 创建的对象,在 JVM 内部是很轻量级的,相对与我们直接创建的对应包装类对象做了许多优化; 也正因为轻量的缘故,这些原始类的功能就比较少(例如我们不能调用其内部的方法,因为他们内部已经优化成没有方法了)

使用实际的例子来说明,为什么我们更应该使用 “原始类型”:

“原始类型”有更快的速度(例如,下面的代码执行,在我们的机器上需要 9 秒,但当我把 Long 改成 long 之后,0 秒内就完成了)

public static void main (String[] args) {
    long millis = System.currentTimeMillis ();
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println (sum);
    System.out.println ((System.currentTimeMillis () - millis) / 1000);
}

“原始类型”允许我们直接使用 “==”来进行比较

new Integer (3) == new Integer (3); // false
new Integer (100) == new Integer (100); // false
Integer.valueOf (5) == Integer.valueOf (5); //true
Integer.valueOf (200) == Integer.valueOf (200); //false

我们注意看第四句,输出结果确实为 “false” 。这个是因在 [-128; 127] 这个区间的 265 个整数会被 JVM 缓存存放, 所以在这个区间, JVM 返回相同的对象;然而,超出这个区间, JVM 就不再有缓存了,将会创建新的对象,所以结果是不等的。

所以总结一下是: 在 JVM 内部,原始类型就是被当作对象来处理的。但是我们开发者直接把 “原始类型” 当作对象使用,开发者应该使用对应的包装来。

以上就是为什么我说 “ Java 确实是一个纯粹的面向对象语言 ”的证实过程。如果你们对这个有什么其他的观点,请在评论留言,一起讨论。

本文文字及图片出自 www.codeceo.com

余下全文(1/3)
分享这篇文章:

请关注我们:

共有 1 条讨论

  1. 在c#里面,你可以这么写: 1.ToString()。
    但是在java里就不能这么些。c#也不像java那样一个基础类型int,一个包装integer。但是性能却一点也不比java差。
    java把一个int设计成两个类型就是一个败笔。如果你还没有发现这个一个败笔,还在自圆其说。说明你还没有真正的了解java和什么是面向对象。
    另外功能少跟轻量之间没有必然的联系。不能说一个对象上没有方法它性能就高。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注