作者:困了电视剧
专栏:《JavaSE语法与底层详解》
文章分布:这是一篇关于Java核心知识之一的类和对象的文章,在本篇博客中我会介绍类和对象的底层逻辑,为后面的继承多态等相关知识做铺垫,如果对你有帮助可订阅我的专栏,我会在未来更新更多有价值的文章。
目录
认识面向对象
面向对象和面向过程的做法与比较
认识类
类的定义
类在程序中的定义格式
类的实例化
实例化的对象在空间中的存储
this引用
需求
什么是this引用
this的特性
this是一个隐藏的参数
对象的构造和初始化
局部变量中引用类型和基本类型的初始化
如何初始化对象
默认初始化
就地初始化
Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。面 向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人 们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。
我们用一个栗子来了解什么是面向对象,以洗衣服来举例:
对于现象过程来说,洗衣服就是
先找到要洗的脏衣服——》将脏衣服放入洗衣机——》往洗衣机里面加入洗衣液——》打开洗衣机开关——》将洗好的衣服拿出来——》将衣服放到甩干机中——》打开甩干机——》将甩干的衣服拿出来——》晾晒
面向过程就是那种将一件事情拆分成很细节的小事并解决的方法
而对于面向对象,洗衣服的过程可以理解为:
我让别人找到要洗的脏衣服——》将脏衣服给另一个人,然后他一会拿给我洗好的衣服——》然后我再把洗好的衣服给另一个人,然后他一会拿给我已经甩干的衣服——》然后我将甩干的衣服给另一个人,过一会他将晒好的衣服给我。
图解为:
由这个过程我们可以知道面向过程是我关心每一个步骤,而面向对象则是将每一个解决问题的步骤进行封装,然后用这些封装好的东西进行问题的解决,省去了很多细节,使程序更加清晰明了。
所以说“用面向对象的思想来涉及程序,更符合人 们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。”
注意:面向过程和面相对象并不是一门语言,而是解决问题的方法,没有那个好坏之分,都有其专门的应用场景。
类是用来对一个实体(对象)来进行描述的,主要描述该实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干 啥),描述完成后计算机就可以识别了。
举个栗子:类就像一栋楼的设计图纸,他记录了一栋楼要被建造所需的所有材料和设计方法,通过这个设计图纸我们可以建造很多栋不同的楼房。
//类主要依托对象进行描述(造楼,图纸)
类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类 具有哪些功能,称为类的成员方法。
class A{
//成员变量
int a;
String b;
//成员方法
void method(){}
}
定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自 带的内置类型,而类是用户自定义了一个新的类型,它们都是类(一种新定 义的类型)有了这些自定义的类型之后,就可以使用这些类来定义实例(或者称为对象)。 用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
由此可以看出类是一个引用类型,他在内存中的创建存储等遵循引用变量的规则。
用上述的楼盘图纸的例子来讲就是,类的实例化就是用那张图纸建立楼房(对象)的过程。
类是一个引用型变量,所以在对类进行实例化,并创建对象的时候,其在内存中的存储遵循引用型变量的规则:
public class Javabit_Code{
public static void main(String[] args) {
A a1=new A();
A a2=new A();
}
}
class A{
//成员变量
int a;
String b;
//成员方法
void method(){}
}
以这段代码举例,他在执行过程中的内存分配如图:
栈中存储的是对象在堆中的地址,每实例化一个对象,成员变量就会被分配空间然后放到这个对象中,并且由于是 成员变量,所以会被赋上默认值。
这时候有的同学可能会问了:我类中定义的方法在哪,他被存储在哪里了,我在这篇博客(https://blog.csdn.net/m0_62815572/article/details/127887112?spm=1001.2014.3001.5501)中提到过Java对内存的分配,在Java分配的内存空间中,有一个内存空间的名字叫做方法区,所以对于类中定义的方法(甚至包括整个类),他们的代码被编译后,都被存储在方法区中,然后在未被调用的时候,这些方法和类几乎不占内存,即内存空间不会给他们分配空间,他们仅仅作为一段被编译后的二进制数字被存储在内存中,当被调用时,方法会在栈中开辟一块栈帧,这才会被分配内存空间。
还用上述的例子,如果将类看成是图纸,那图纸本身并不占什么空间,他所有的只是上面的信息,而对象就是建造的楼房,他是占“空间”的
public class Javabit_Code{
public static void main(String[] args) {
Data a1=new Data();
Data a2=new Data();
}
}
class Data{
//成员变量
int year;
int month;
//成员方法
public void setA(int y,int m){
year=y;
month=m;
}
}
我想给我的成员变量year进行赋值,我用y作为形参进行赋值,但是当我需要赋值的变量变多时,就会出现一个问题,那就是我无法分清我的形参对应我的哪一个成员变量。
这时候,就需要一个解决方法,想来想去发现直接将形参定义成和成员变量一样的变量名,这样不就不会搞乱了吗,但是,如果两个都用同一个变量名那我又怎样进行区分呢,于是this便应运而生了。
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该 引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
public class Javabit_Code{
public static void main(String[] args) {
Data a1=new Data();
Data a2=new Data();
}
}
class Data{
//成员变量
int year;
int month;
//成员方法
public void setA(int year,int month){
this.year=year;
this.month=month;
}
}
1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
2. this只能在"成员方法"中使用
3. 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
看到这里,我们有两个疑问:
1.当我用一个类实例化多个对象的时候,程序是如何判段我要对哪个对象的变量进行操作的。
2.this后面有一个".",说明这个this是一个引用类型,那这个引用类型是从哪里来的,明明我的形参并没有定义任何this。
为了解决这两个问题我们先做一个测试。
public class Javabit_Code{
public static void main(String[] args) {
Data a1=new Data();
Data a2=new Data();
}
}
class Data{
//成员变量
int year;
int month;
//成员方法
public void setA(Data this,int year,int month){
this.year=year;
this.month=month;
}
}
我们在setA方法中新加了一个参数Data this,然后我发现在我加之前和加之后这个程序没有任何的变化
这时我们便可以进行一个合理的猜测——由于程序需要一个this来找到我们需要赋值的成员变量,但是当程序员每一次使用类中的方法时都传这样的一个参数未免太麻烦了,所以,Java的设计团队在设计this时,将这个形参进行了隐藏,使程序员不需要专门传这个参数,在代码上也不用专门地进行显示,当调用方法时程序会默认自动生成,并且指向成员变量!
但是,这个this仅仅存储的是成员变量的地址吗?
我们将这个程序debug一下
当程序走到setA方法中时,这个this的地址和a1的地址一样,此时,所有的问题都解决了。
当一个类多次实例化时,为什么程序能找到 我需要的那个对象,是因为this,this存储了我要找的那个对象的地址,他作为一个默认的隐藏的参数被传进了方法中,然后方法找到我所指定的对象再对其进行操作。
在了解对象的构造和初始化之前我们需要先记住一个规则,那就是凡是在Java方法内部定义一个局部变量时,都需要进行初始化。
在Java程序运行的过程中,局部变量的定义都是在栈上的,也就是说凡是在栈上定义的局部变量都需要进行初始化。
我们用这张图来举例子,a是一个基本类型,a1和a2都是引用类型,如果我仅仅定义但并不进行初始化,我则无法使用他。
编译会报错,所以我只有进行初始化后才能使用,那问题 又来了,该怎样进行初始化?
我们通过new这个关键字,可以在堆区中为对象开辟一块空间,从而达到初始化对象的目的。
那么问题又来了,对于栈区上的局部变量来说,如果我不对其进行初始化,那我就无法对其进行使用,但上图的代码中我对于类中的成员变量也没有进行初始化呀,但为什么程序不仅不报错还输出了0呢?这和new有很大的关系。
这是一个很简单的初始化对象的语句,但在这条简单的语句后,对jvm的一连串的操作。
1. 检测对象对应的类是否加载了,如果没有加载则加载
2. 为对象分配内存空间
3. 处理并发安全问题,比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
4. 初始化所分配的空间,即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,这里初始化的值为每种类型默认初始化的值。
5. 设置对象头信息(关于对象内存模型后面会介绍)
6. 调用构造方法,给对象中各个成员赋值
在声明成员变量时,就直接给出了初始值。其余和默认初始化一致。
以上就是本篇博客的全部内容,这篇博客主要介绍的是类在初始化的过程中的底层运行逻辑,我会在下篇关于类和对象的博客中肝出封装和代码块相关的内容,如有疏漏,欢迎指正!