• 【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手册

  • 相关阅读:
    【C++17】std::optional

    【GDB 】GDB基本命令
    【二叉树系列 | 01】二叉树遍历
    【coredump | 01】coredump的阐述
    【C++ Template | 06】std::enable_if和SFINAE
    std::dclval 使用教程
    系统设计实践(03)- Instagram社交服务
    系统设计实践(02)- 文本存储服务
    系统设计实践(01)
  • 原文地址:https://www.cnblogs.com/hellovan/p/13548246.html
Copyright © 2020-2023  润新知