【Java编程思想】一切都是对象

一切都是对象

一切都是对象

数据存储

程序在运行时是如何存储的呢?尤其是内存是怎么分配的。有5个不同的地方可以存储数据:

存储地点

  • 寄存器

    这是最快的存储区,其位于处理器内部。但是数量极其有限。所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。

  • 堆栈

    位于RAM(随机访问存储器)中,通过堆栈指针可以从处理器那里获得直接支持。这也是一种快速有效的存储分配方法,仅次于寄存器。创建程序时,Java 系统必须知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据(如对象引用),但 Java 对象本身的数据却是保存在堆内存的。

  • 堆内存

    一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。编译器不需要知道对象必须在堆内存上停留多长时间,因此用堆内存保存数据更具灵活性。创建一个对象时,只需用 new 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。

  • 常量存储

    常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中。

  • 非 RAM 存储

    数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:

    1. 序列化对象:对象被转换为字节流,通常被发送到另一台机器;
    2. 持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。

    这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。

  • 特例:基本类型存储

    Java 的基本类型的创建并不是通过 new 关键字来产生。通常 new 出来的对象都是保存在堆内存中的,以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法,Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 new 创建变量,而是使用一个“自动”变量。 这个变量直接存储"值",并置于栈内存中,因此更加高效

    Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。

    基本类型 大小 最小值 最大值 包装类型
    boolean Boolean
    char 16 bits Unicode 0 Unicode 216 -1 Character
    byte 8 bits \(-128\) \(+127\) Byte
    short 16 bits \(-2^{15}\) \(+2^{15} -1\) Short
    int 32 bits \(-2^{31}\) \(+2^{31} -1\) Integer
    long 64 bits \(- 2^{63}\) \(+ 2^{63} -1\) Long
    float 32 bits IEEE754 IEEE754 Float
    double 64 bits IEEE754 IEEE754 Double
    void Void

    基本类型有自己对应的包装类型,如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。代码示例:

    char c = 'x';
    Character ch = new Character(c);
    

    或者你也可以使用下面的形式:

    Character ch = new Character('x');
    

    基本类型自动转换成包装类型(自动装箱)

    Character ch = 'x';
    

    相对的,包装类型转化为基本类型(自动拆箱):

    char c = ch;
    
  • 高精度数字

    在 Java 中有两种类型的数据可用于高精度的计算。它们是 BigIntegerBigDecimal。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。这两个类包含的方法提供的操作,与对基本类型执行的操作相似。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以,只不过必须要通过调用它们的方法来实现而非运算符。

    • BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。
    • BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的货币计算。
  • 数组存储

    当我们创建对象数组时,实际上是创建了一个引用数组,并且每个引用的初始值都为 null 。在使用该数组之前,我们必须为每个引用指定一个对象 。如果我们尝试使用为 null 的引用,则会在运行时报错。因此,在 Java 中就防止了数组操作的常规错误。

    我们还可创建基本类型的数组。编译器通过将该数组的内存全部置零来保证初始化。

对象的销毁

  • 作用域

    在 C、 C++ 和 Java 中,作用域是由大括号 {} 的位置决定的。例如:

    {
        int x = 12;
        // 仅 x 变量可用
        {
            int q = 96;
            // x 和 q 变量皆可用
        }
        // 仅 x 变量可用
        // 变量 q 不在作用域内
    }
    

    Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易于阅读。由于 Java 是一种自由格式的语言,额外的空格、制表符和回车并不会影响程序的执行结果。在 Java 中,你不能执行以下操作,即使这在 C 和 C++ 中是合法的:

    {
        int x = 12;
        {
            int x = 96; // 非法
        }
    }
    
  • 对象的作用域

    Java 对象与基本类型具有不同的生命周期。当我们使用 new 关键字来创建 Java 对象时,它的生命周期将会超出作用域。

    {
        String s = new String("a string");
    } 
    // 作用域终点
    

    上例中,引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。只要你需要,new 出来的对象就会一直存活下去。

    那么问题来了:我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?

    答案是:Java 的垃圾收集器会检查所有 new 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。也就是说,我们不必担心内存回收的问题了。你只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。

基本类型默认值

如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。

基本类型 初始值
boolean false
char \u0000 (null)
byte (byte) 0
short (short) 0
int 0
long 0L
float 0.0f
double 0.0d

这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的字段始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。

这种默认值的赋予并不适用于局部变量 —— 那些不属于类的字段的变量。 因此,若在方法中定义的基本类型数据,如下:

int x;

这里的变量 x 不会自动初始化为0,因而在使用变量 x 之前,程序员有责任主动地为其赋值(和 C 、C++ 一致)。如果我们忘记了这一步, Java 将会提示我们“编译时错误,该变量可能尚未被初始化”。 这一点做的比 C++ 更好,在后者中,编译器只是提示警告,而在 Java 中则直接报错。

命名可见性

如果你在两个模块中使用相同的命名,那么如何区分这两个名称,并防止两个名称发生“冲突”呢?在 C 语言编程中这是很具有挑战性的,因为程序通常是一个无法管理的名称海洋。C++ 将函数嵌套在类中,所以它们不会和嵌套在其他类中的函数名冲突。然而,C++ 还是允许全局数据和全局函数,因此仍有可能发生冲突。为了解决这个问题,C++ 使用附加的关键字引入了命名空间

Java 采取了一种新的方法避免了以上这些问题:为一个类库生成一个明确的名称,Java 创建者希望我们反向使用自己的网络域名,因为域名通常是唯一的。因此我的域名是 MindviewInc.com,所以我将我的 foibles 类库命名为 com.mindviewinc.utility.foibles。反转域名后,. 用来代表子目录的划分。

static关键字

类是对象的外观及行为方式的描述。通常只有在使用 new 创建那个类的对象后,数据存储空间才被分配,对象的方法才能供外界调用。这种方式在两种情况下是不足的。

  1. 有时你只想为特定字段(注:也称为属性、域)分配一个共享存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建对象。
  2. 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。

编码规范

Java 编程语言编码规范(Code Conventions for the Java Programming Language)要求类名的首字母大写。 如果类名是由多个单词构成的,则每个单词的首字母都应大写(不采用下划线来分隔)例如:

class AllTheColorsOfTheRainbow {
    // ...
}

有时称这种命名风格叫“驼峰命名法”。对于几乎所有其他方法,字段(成员变量)和对象引用名都采用驼峰命名的方式,但是它们的首字母不需要大写。代码示例:

class AllTheColorsOfTheRainbow {
    int anIntegerRepresentingColors;
    void changeTheHueOfTheColor(int newHue) {
        // ...
    }
    // ...
}

其他可以参考阿里巴巴java手册

posted @ 2020-08-23 09:14  林深处见鹿  阅读(209)  评论(0编辑  收藏  举报