今天学习了类、对象及相关知识,试着从内存角度分析三者关系,如果有不对的地方请指正
首先得先介绍java中的三个内存区域:
- 栈区
用于存放该线程执行方法的信息(实际参数、局部变量等)。栈属于线程私有,不能实现线程间的共享。栈的存储特性是“先进后出,后进先出”。栈是由系统自动分配,速度快!栈是一个连续的内存空间
- 堆区
堆用于存储创建好的对象和数组(数组也是对象)。JVM只有一个堆,被所有线程共享。 堆是一个不连续的内存空间,分配灵活,速度慢
- 方法区
方法区用来存放程序中永远不变或唯一的内容,如类、静态变量、字符创常量等。JVM只有一个方法区,被所有线程共享。方法区实际是堆中的一个区域,只是用于存储类、常量相关的信息
package cn.sxt.oo;public class zhihu {public static void main(String[]args) {B b = new B();b.printA();b.printB();}}class B{int a = 0;static int b=0;public void printA() {System.out.println(a);}public static void printB() {System.out.println(b);}
}
下面来看这一段代码
1. 类的加载
首先第一步,加载zhihu这一个类,在方法区中存入类信息和类当中的静态变量、静态方法和常量。
总结:类的加载是在类第一次被使用的时候,从代码上看,简单来说就是类名第一次出现的地方。可以是类的第一个对象创建的时候,也可以是通过 “类名.静态变量” 调用类中静态变量的时候。
2.对象创建
完成了class zhihu的加载之后,将会执行main方法,系统会在栈空间中开一个栈帧存放main方法。然后下一句代码是对象b 的构造方法。但是此时并不马上执行构造方法。因为class B尚未初始化,因此在生成对象之前会完成class B的初始化。
初始化class B后,在堆空间中分配内存用于存放对象 b的信息,执行b中属性的显式初始化,并给属性赋数据类型默认的初始值,如:int a 就赋值0;
完成对象空间的分配,属性的显式初始化和初始化赋值之后,才会执行构造方法,此时在栈区中中载入b的构造方法:B b = new B(); 当执行到B b 的时候,main的栈帧中会生成b的局部变量,当执行到new 的时候,将堆中新建的b对象地址赋给栈中的b。
之后构造方法会将方法区中的常量赋值给堆中的变量,之后构造方法执行结束,从栈帧中释放
总结:对象的创建过程为类的初始化、对象空间分配、属性初始化、执行构造方法并将类中的常量赋值给对象。
非静态方法的执行
在执行到 b.printA();时,开新栈帧。printA方法会找到main方法中的对象b,根据对象地址找到堆中的printA()方法。然后找到变量a并输出,执行完成以后该方法的栈帧会被释放
静态方法的执行
执行到b.printB();时,也是一样先开新栈帧。从方法区加载方法p.printB(),然后找到方法区中的静态变量b,并输出。
总结:方法的调用本质上就是地址的传递,如B b = new B(); 本质上是将新创建的b的地址传递给 b;再比如b.printA();即通过b的地址找到printA的方法。当然也可以通过类名去找,如B.printA();
思考:静态方法中能否调用非静态方法或非静态变量
答案显然是不能的,因为类加载的时候不一定有对象,而非静态方法和变量只有对象建立之后才有。因此在静态方法中肯定是没有非静态方法或对象的地址的。那么如何实现静态方法调用非静态变量呢,只需要提供一个地址即可,我们可以在静态方法中建立一个对象,通过建立的对象去找到非静态方法。如以下代码
public void testPrintA(){B b2 = new B():b2.printA();
}
思考:类与类之间能否互相调用
非静态方法肯定是要实例化以后通过 对象名.方法名/变量名 去调用
而静态方法是可以互相调用的,用类名.方法名/变量名 或者 对象名.方法名/变量名调用
class A{ B b;public void printB1(){ B.printB(); } public void printB2(b){b.printB();}
}
思考:this关键字的本质——指向当前对象
this关键字的用法:
- 构造器中用于区分同名的局部变量和成员变量
局部变量是方法执行时存放在栈中的,与成员变量生重名时,this就可以指向堆中的成员变量,这样就区分开了。
- 构造器中调用另一个构造器