JavaRush /Java 博客 /Random-ZH /Java 中的赋值和初始化
Viacheslav
第 3 级

Java 中的赋值和初始化

已在 Random-ZH 群组中发布

介绍

计算机程序的主要目的是数据处理。要处理数据,您需要以某种方式存储它。我建议了解数据是如何存储的。
Java 中的赋值和初始化 - 1

变量

变量是存储任何数据的容器。我们看一下Oracle官方的Tutorial: Declaring Member Variables。根据本教程,有几种类型的变量:
  • Fields:类中声明的变量;
  • 局部变量:方法或代码块中的变量;
  • 参数:方法声明中的变量(在签名中)。
所有变量都必须有变量类型和变量名。
  • 变量的类型指示变量代表什么数据(即它可以存储什么数据)。我们知道,变量的类型可以是原始类型(primitives ,也可以是对象类型,而不是原始类型(Non-primitive)。对于对象变量,它们的类型由特定的类描述。
  • 变量名必须是小写,驼峰式。您可以在“变量:命名”中阅读有关命名的更多信息。
另外,如果是类级别变量,即 是一个类字段,可以为其指定访问修饰符。有关详细信息,请参阅控制对类成员的访问

变量声明

所以,我们记住变量是什么。为了开始使用变量,您需要声明它。首先,让我们看一下局部变量。为了方便起见,我们将使用教程点的在线解决方案,而不是 IDE:Online IDE。让我们在他们的在线 IDE 中运行这个简单的程序:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
因此,如您所见,我们声明了一个名为 namenumber和 type的局部变量int。我们按下“执行”按钮,得到错误:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
发生了什么?我们声明了一个变量,但没有初始化它的值。值得注意的是,这个错误不是在执行时(即不是在运行时)发生的,而是在编译时发生的。智能编译器在访问局部变量之前检查它是否会被初始化。因此,由此得出以下陈述:
  • 局部变量只能在初始化后才能访问;
  • 局部变量没有默认值;
  • 局部变量的值在编译时检查。
因此,我们被告知必须初始化该变量。初始化变量就是给变量赋值。然后让我们弄清楚它是什么以及为什么。

初始化局部变量

初始化变量是 Java 中最棘手的主题之一,因为…… 与内存、JVM 实现、JVM 规范以及其他同样可怕和棘手的事情密切相关。但你至少可以在某种程度上尝试弄清楚它。让我们从简单到复杂。为了初始化变量,我们将使用赋值运算符并更改之前代码中的行:
int number = 2;
在此选项中,不会出现错误,并且值将显示在屏幕上。在这种情况下会发生什么?让我们试着推理一下。如果我们想给一个变量赋值,那么我们希望该变量存储一个值。事实证明,该值必须存储在某个地方,但是存储在哪里呢?在磁盘上?但这非常慢,可能会对我们造成限制。事实证明,我们“此时此地”能够快速有效地存储数据的唯一地方是内存。这意味着我们需要在内存中分配一些空间。这是真实的。当一个变量被初始化时,将在分配给java进程的内存中为其分配空间,我们的程序将在该进程中执行。分配给java进程的内存被分为几个区域或区域。其中哪一个将分配空间取决于声明变量的类型。 内存分为以下几个部分:堆、栈和非堆。让我们从堆栈内存开始。Stack翻译为堆栈(例如一摞书)。它是一种 LIFO(后进先出)数据结构。也就是说,就像一摞书。当我们向其中添加书籍时,我们将它们放在最上面,当我们将它们拿走时,我们将拿走最上面的一本(即最近添加的那本)。因此,我们启动了我们的计划。众所周知,Java程序是由JVM,即Java虚拟机来执行的。JVM 必须知道程序应该从哪里开始执行。为此,我们声明一个 main 方法,称为“入口点”。为了在 JVM 中执行,会创建一个主线程(Thread)。当创建线程时,它会在内存中分配自己的堆栈。该堆栈由帧组成。当每个新方法在线程中执行时,都会为其分配一个新帧并将其添加到堆栈顶部(就像书堆中的一本新书一样)。该框架将包含对对象和原始类型的引用。是的,是的,我们的int会存储在栈上,因为…… int 是原始类型。在分配帧之前,JVM 必须了解要在那里保存什么。正是由于这个原因,我们会收到“变量可能尚未初始化”的错误,因为如果它没有初始化,那么JVM将无法为我们准备堆栈。因此,在编译程序时,智能编译器将帮助我们避免犯错误并破坏一切。 (!)为了清楚起见,我推荐一篇超级文章:“ Java 堆栈和堆:Java 内存分配教程”。它链接到一个同样酷的视频:
方法执行完成后,为这些方法分配的帧将从线程堆栈中删除,为此帧分配的内存以及所有数据也将被清除。

初始化局部对象变量

让我们再次将代码更改为更棘手一些:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
这里会发生什么?我们再谈谈吧。JVM 知道它应该从哪里执行程序,即 她看到了主要方法。它创建一个线程并为其分配内存(毕竟,线程需要在某处存储执行所需的数据)。在这个线程中,为main方法分配了一个帧。接下来我们创建一个 HelloWorld 对象。这个对象不再是在栈上创建的,而是在堆上创建的。因为object不是原始类型,而是对象类型。并且堆栈只会存储对堆中对象的引用(我们必须以某种方式访问​​该对象)。接下来,在main方法的堆栈中,将分配帧用于执行println方法。执行完main方法后,所有的frame都会被销毁。如果帧被破坏,所有数据都将被破坏。object对象不会立即被销毁。首先,对它的引用将被销毁,因此没有人会再引用该对象对象,并且不再可能访问内存中的该对象。智能 JVM 对此有自己的机制——垃圾收集器(简称垃圾收集器或 GC)。然后它从内存中删除没有其他人引用的对象。上面给出的链接再次描述了此过程。甚至还有一个带有解释的视频。

初始化字段

类中指定的字段的初始化以特殊方式进行,具体取决于该字段是否是静态的。如果一个字段有关键字static,那么这个字段指的是类本身,如果没有指定static这个词,那么这个字段指的是这个类的一个实例。让我们看一个例子:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
在此示例中,字段在不同时间初始化。number 字段将在 HelloWorld 类对象创建后初始化。但是count字段会在Java虚拟机加载类时被初始化。类加载是一个单独的主题,因此我们不会在这里混合它。值得一提的是,当类在运行时已知时,静态变量就会被初始化。这里还有更重要的事情,你已经注意到了这一点。我们没有在任何地方指定该值,但它有效。确实如此。作为字段的变量,如果没有指定值,则会使用默认值进行初始化。对于数值,浮点数为 0 或 0.0。对于布尔值来说这是错误的。对于所有对象类型变量,该值将为 null(我们稍后会讨论这一点)。看来,为什么会这样呢?但因为对象是在Heap(堆中)中创建的。该区域的工作是在运行系统中执行的。而且我们可以在运行时初始化这些变量,不像堆栈,必须在执行之前准备好内存。这就是 Java 中内存的工作原理。但这里还有一个特点。这个小片段触及了记忆的不同角落。我们记得,在堆栈内存中为 main 方法分配了一个帧。该帧存储对堆内存中对象的引用。但是 count 存储在哪里呢?我们记得,在堆中创建对象之前,立即初始化该变量。这是一个非常棘手的问题。在Java 8之前,有一个称为PERMGEN的内存区域。从 Java 8 开始,这个区域发生了变化,称为 METASPACE。本质上,静态变量是类定义的一部分,即 它的元数据。因此,它存储在元数据存储库 METASPACE 中是合乎逻辑的。MetaSpace属于同一个Non-Heap内存区域,并且是它的一部分。同样重要的是要考虑变量声明的顺序。例如,这段代码有一个错误:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

什么是空

如上所述,对象类型的变量,如果它们是类的字段,则会被初始化为默认值,并且该默认值为 null。但是Java中的null是什么?首先要记住的是原始类型不能为 null。这都是因为 null 是一个特殊的引用,它不引用任何地方、任何对象。因此,只有对象变量可以为 null。第二件需要理解的重要事情是 null 是一个引用。我参考的也有它们的重量。关于这个主题,你可以阅读 stackoverflow 上的问题:“ Does null variable require space in memory ”。

初始化块

当考虑变量的初始化时,不考虑初始化块将是一种罪过。它看起来像这样:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
输出顺序为:静态块、块、构造函数。正如我们所看到的,初始化块在构造函数之前执行。有时这可能是一种方便的初始化方法。

结论

我希望这个简短的概述能够提供一些关于它的工作原理和原因的见解。#维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION