Java教程

Java笔记 —— 创建对象的过程(类加载,具体的初始化赋值过程)

本文主要是介绍Java笔记 —— 创建对象的过程(类加载,具体的初始化赋值过程),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
package test1.demo;
class Person{
    String name;
    int age;
    static String nationality;
    public Person(){}
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public Person(String name,int age,String nationality){
        this.name = name;
        this.age = age;
        Person.nationality = nationality;
    }
    public void show(){
        System.out.println(name+"--"+age+"--"+nationality);
    }
}
public class PersonDemo {
    public static void main(String[] args) {
        Person p1 = new Person("张",12,"中国");
        p1.show();

        Person p2 = new Person("陈",14,"美国");
        p2.show();

        Person p3 = new Person("周",16,"日本");
        p3.show();

        p3.nationality = "法国";
        p1.show();
        p2.show();
        p3.show();
    }
}

针对这段代码,来说一下具体的创建对象的过程

  1. 首先是将类加载到方法区的class内容区,这里有两个类Person.class和PersonDemo.class,会被加载到class内容区。
    当运行程序的时候,需要用到一个类,会先到方法区查找class信息,如果找到了就拿来用。如果没找到,就会像上面这样,将class类文件加载到方法区里面。

具体的加载类的时机,不完整的说可以分为以下两种(其余的情况比较复杂,这里不说):
(1)遇到new关键字的时候,将需要将创建对象的类加载初始化
(2)Java虚拟机启动的时候,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类

满足这些时机时,就会触发这一步,将 .class文件读入内存

  1. 然后是将类的静态成员变量和静态成员方法,加载到方法区的静态区里面
    (1)把.class文件中的所有静态内容加载到方法区下的静态区域内
    (2)静态内容加载完成之后,对所有的静态变量进行初始化,初始化的顺序是
    默认初始化 ——> 显示初始化 ——> 静态代码块
    默认初始化完成后才会进行显式初始化,显式初始化完成后才是静态代码块
    具体的初始化过程与含义往下看

  2. 将类的非静态变量和非静态方法加载到方法区的非静态区域内,这个方法包括构造方法。

2、3步的顺序可以记为:在.class加载到方法区时,先加载父类再加载子类;先加载静态内容,再加载非静态内容
到这里为止,整个类的加载就完成了。

.class文件读入内存,让类加载完之后会生成一个Class类型对象,这个对象会包含类的数据结构,包含所有信息(比如类名,方法,变量等)
这个Class对象用来创建这个类的所有对象(一个类只能有一个Class对象)

  1. 程序开始时,先将main方法加载到栈里面,然后在栈内存中开辟一个新的空间,用来存放新建的引用变量,比如Person p1,这里并不是新建了一个对象,而是新建了一个变量用来指向堆内存中的对象。

  2. 直到遇到new关键字的时候,才真的在堆内存中划分一块空间用来存储对象。
    (1)使用new关键字创建你对象时,会先在堆内存中开辟一块空间。
    (2)给开辟的空间分配一个地址,比如说0x001
    (3)把对象的所有非静态成员变量加载到所开辟的空间下,加载完非静态变量后还有
    空间还有一个静态标记,用来指向方法区中的类的静态变量的地址(一个类的所有对象共享这个类的静态变量)
    空间还会有一个方法标记,指向这个对象所在类的方法在方法区中的地址
    (4)非静态成员变量加载到空间后会默认初始化(默认初始化的默认值:数字为 0,字符为 null,布尔为 false,而所有引用被设置成 null)
    然后调用构造方法,构造方法会被调用栈里面执行,入栈执行时,分为两部分:
    一、先执行构造函数中的隐式三步,三步分别为
    ▶ 1. 执行super()语句(即先初始化父类的构造方法)
    ▶ 2. 对开辟空间下的所有非静态成员变量进行显示初始化
    ▶ 3. 执行构造代码块
    二、执行构造方法中的代码
    一般来说,成员变量最后的值都是调用构造函数时传进去的值,也就是第二步“执行构造方法中的代码”

  3. 对象调用方法时,根据方法标记到方法区中找到方法,然后方法入栈。方法调用完后就会被清除。

  4. 之后的show方法中需要调用对象的成员变量的时候,先根据栈里面的引用变量找到堆内存中的对象空间,然后发现空间里面只有两个实例对象name和age,没有静态变量,此时就会根据静态标记到方法区去找。找到后返回。

  5. 最后还修改了一下静态变量,根据这个路径找到静态变量:引用变量——>堆内存空间——>方法区——>静态变量,修改静态变量后,之前几个对象的静态标记依旧指向的是这个地址。所以修改了静态变量后,所有对象的静态变量结果都会改变。而实例变量属于对象所有,一个对象改变自己的实例变量不会影响到其他的实例对象。

  6. 所有语句结束后,main方法出栈,程序运行完毕

总结一下,对象里面的实例属性赋值过程:

默认初始化(null,0)——> 显示初始化(name=“zhang”,age=16)——> 构造代码块 ——> 构造方法

默认初始化
package review;

class Demo1{
    //默认初始化
    public String name;
    public int age;

    //构造方法
    public Demo1(){}

    @Override
    public String toString() {
        return "Demo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class gouzaoDemo1 {
    public static void main(String[] args) {
        Demo1 d = new Demo1();
        System.out.println(d.toString());
    }
}

结果为
在这里插入图片描述

显式初始化,构造代码块,构造方法
package review;

class Demo{
    //显式初始化
    public String name = "zhang";
    public int age = 16;
    //构造代码块
    {
        name = "cao";
        age = 12;
    }
    //构造方法
    public Demo(){}
    public Demo(String name , int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Demo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class gouzaoDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo();
        System.out.println(d1.toString());

        Demo d2 = new Demo("chen",20);
        System.out.println(d2.toString());

    }
}

结果为
在这里插入图片描述

顺带提一下,理解了对象创建过程后再来看这一段代码
package test1;

public class VariableDemo {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        change(a,b);
        System.out.println(a+"--"+b);
    }

    public static void change(int a,int b){
        a = 3;
        b = 4;
    }
}

在这里插入图片描述
因为当a和b是非静态变量的时候,方法内部使用的是新建的局部变量,返回值类型是void,所以局部变量的生命周期会随着方法的调用结束而结束,不会影响到外面的变量。

package test1;

public class VariableDemo {
    static int a = 1;
    static int b = 2;
    public static void main(String[] args) {
        change();
        System.out.println(a+"--"+b);
    }

    public static void change(){
        a = 3;
        b = 4;
    }
}

在这里插入图片描述
调用静态变量的时候,会直接修改方法区里面的静态变量的值,所以外面的静态变量的值也会受到影响。

再拓展一下:
package test1;

import java.util.Arrays;

public class VariableDemo1 {
    public static void main(String[] args) {
        int a = 5;
        int b = 10;
        change(a,b);
        System.out.println(a+"---"+b);
        
        int[] arr = {1,2,3,4,5};
        changeArr(arr);
        System.out.println(Arrays.toString(arr));

    }

    public static void change(int a,int b){
        a = 20;
        b = 30;
    }

    public static void changeArr(int[] arr){
        for(int i=0;i<arr.length;i++){
            if(arr[i]%2==0){
                arr[i] += 10;
            }
        }
    }
}

在这里插入图片描述
会发现都是void类型的方法,change方法没有改变a,b的值,但是changeArr方法改变了arr数组的值

这是因为基本数据类型作为参数传递的时候,传递的是具体的值,本身变量里面的值不参与运行,不会发生变化。基本数据类型的值被调入栈内存中进行运行,方法结束后会随着方法一起消失。栈内存中放着的是局部变量,而变量本身的实体在堆内存中,只在栈里面修改不会影响到堆内存,除非修改void类型,让方法有返回值。

而int[] arr不是基本数据类型,是引用数据类型。引用数据类型作为参数传递的时候,传递的是地址值,所以在方法里面改变的是实际空间里面的值,因此会影响到外面的数据的值。引用数据类型会在堆内存中开辟空间,并执行修改。

这篇关于Java笔记 —— 创建对象的过程(类加载,具体的初始化赋值过程)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!