【MMBBi的Java之路】类和对象的理解(二)-this引用和对象的构造方法
文章目录
前言
本篇博客是作为上一篇博客( 【MMBBi的Java之路】关于Java中类和对象的理解)的后续,依然是为了加深我个人对Java中类和对象相关内容的理解所写的一篇博客。
this引用
为什么要有this引用
这里我们先看一段代码
//随便定义一个日期类,包含年月日三个成员变量
public class Date {
public int year;
public int month;
public int day;
//然后通过setDay进行赋值
public void setDay(int year, int month, int day) {
year = year;
month = month;
day = day;
}
//打印日期
public void printfDate() {
System.out.println(year+"/"+month+"/"+day);
}
public static void main(String[] args) {
//new三个日期,并且进行赋值
Date day1 = new Date();
Date day2 = new Date();
Date day3 = new Date();
day1.setDay(2023,5,1);
day2.setDay(2023,5,2);
day3.setDay(2023,5,3);
//接下来打印,按照程序流程,应该输出2023/5/1 2023/5/2 2023/5/3
day1.printfDate();
day2.printfDate();
day3.printfDate();
}
}
按照代码上面这个代码,按理说输出的应该是2023/5/1 2023/5/2 2023/5/3,可是这边实际的输出结果是这样的:
这是因为在setDay这个方法中,形参和Date的成员变量的名字重复了,这个时候计算机就懵逼了,到底是谁给谁赋值?成员变量给成员变量?参数给参数?参数给成员变量?成员变量给参数?这个时候就是形参给形参赋值,一个默认的0。
除此之外还有另外一个问题:三个对象day1,day2, day3 都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,setDate和printDate函数如何知道打印的是那个对象的数据呢?
这个时候就是this引用来解决的问题了。
什么是this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
实际上在我们使用成员方法的时候,都会带上一个隐形的参数this,比如上面的setDay方法,他的实际参数是:
public void setDay(Date this, int year, int month, int day)
this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收。
所以我们碰到形参和成员变量同名的情况下,可以在通过this加 ‘ . ’来引用成员变量,确保我要将形参赋值给成员变量。
public void setDay(Date this, int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public void printfDate() {
System.out.println(this.year+"/"+this.month+"/"+this.day);
}
再次运行程序,可以看到程序输出的是我们想要的结果。
this的使用方法
this除了访问类的成员变量之外,还有另外两种使用方法,分别是:
访问构造方法
访问成员方法
这两种用法接下来会借助对象的构造和初始化来说明。
对象的构造以及初始化
如何初始化对象
我们知道,如果在java内部定义一个变量的时候如果不初始化,否则就会编译失败。
public static void main(String[] args) {
int a;
System.out.println(a);
}
想要通过编译,只要给上面的a附上一个初始的值即可,如果是给对象初始化,我们拿上面的Date类说明。
public static void main(String[] args) {
Date day = new Date();
day.printfDate();
day.setDay(2023, 5, 1);
day.printfDate();
}
我们可以看到,这边通过setDay方法给day对象进行了赋值。这边就引出了两个问题
1:每次对象创建好后都要调用SetDate方法设置具体日期,比较麻烦。
2:局部变量要初始化后才能使用,为什么字段声明之后没有给值依然可以使用?
这就引出了构造方法,可以帮助我们解决第一个问题。
构造方法
构造方法是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
构造方法有四个特性:
我们先来看两个
1. 名字必须与类名相同
2. 没有返回值类型,设置为void也不行
public class Date {
public int year;
public int month;
public int day;
//构造方法的名字必须与类名相同
//一般情况下我们使用public修饰
//并且没有返回值,使用void也不行,加了void之后编译器会报错
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
System.out.println("构造方法被调用了");
}
public void printfDate() {
System.out.println(this.year+"/"+this.month+"/"+this.day);
}
public static void main(String[] args) {
//此处new了一个Date类型的对象(这里我使用的IDEA2022的版本,会提示我使用了构造方法。)
//其他较老的编译器可能不会提示。
Date day = new Date(2023, 5, 1);
day.printfDate();
}
}
编译一下上面的代码,我们可以看到
通过上面的例子,我们看到了在new了一个对象的同时,我们直接通过构造方法进行了赋值。
注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
再来看剩下两个构造方法的特性
3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
4. 构造方法可以重载
如果我们没有进行显性定义的话,编译器会帮我们生成一份默认的构造方法,并且这个默认的构造方法一定是无参的,就是我们最开始写的Date类。
//注意看这里,我们没有定义任何的构造方法,但是依然编译通过了。
public class Date {
public int year;
public int month;
public int day;
public void printfDate() {
System.out.println(this.year+"/"+this.month+"/"+this.day);
}
public static void main(String[] args) {
Date day = new Date();
day.printfDate();
}
}
上述代码中没有定义任何构造方法,编译器会默认生成一个不带参数的构造方法。
但是我们一旦自己写了一个构造方法,那么编译器就不会在帮我们自己生成一个。
我们看下面这段代码
public class Date {
public int year;
public int month;
public int day;
//这是一个无参的构造方法
public Date() {
System.out.println("无参构造方法被调用了");
}
//这是一个有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
System.out.println("有三个参数的构造方法被调用了");
}
public void printfDate() {
System.out.println(this.year+"/"+this.month+"/"+this.day);
}
public static void main(String[] args) {
//创建对象,没有参数
Date day = new Date();
System.out.println("==============================");//分割线
//创建对象,给三个参数
Date day2 = new Date(2023,5,1);
}
}
首先,上述代码中,我们有两个相同名称的构造方法,参数列表不同,这里就构成了方法的重载。
如果我这边没有写那个无参的构造方法,那么这里创建day对象的时候,编译器会报错,无法通过。因为我创建了一个有三个参数的构造方法,编译器不会再帮我们创建一个没有参数的构造方法。
构造方法就暂时说到这里,下面我们可以回到this引用,看看this引用的另外一个使用方法。 访问构造方法。
public class Date {
public int year;
public int month;
public int day;
public Date() {
//System.out.println("无参构造方法被调用了");
//这里注释如果取消掉的话编译会报错,因为通过this引用构造方法,this必须在第一行。
//此处可以通过this引用方访问带有三个参数的构造方法,
this(2023,5,1);
System.out.println("无参构造方法被调用了");
}
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
System.out.println("有三个参数的构造方法被调用了");
}
public void printfDate() {
System.out.println(this.year+"/"+this.month+"/"+this.day);
}
public static void main(String[] args) {
Date day = new Date();
day.printfDate();
}
}
可以看到这里面有两个点:
一个是this引用访问其他构造方法必须是构造方法中的第一条语句。
第二个可以仔细看输出结果,访问完第三个参数的构造方法之后,还会回来继续访问剩下的代码。
此外还有一个需要注意的点就是this引用不能形成环,语法不支持,编译会报错。
this引用的第三个使用方法,访问当期类的成员方法。
public void printfDate() {
System.out.println(this.year+"/"+this.month+"/"+this.day);
this.show();//通过this.来进行访问
}
public void show() {
System.out.println("this访问了show");
}
========================================================================================================
对于第二个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?
解决这个问题,我们需要了解new关键字背后,JVM做了哪些事情。这方便比较深,有兴趣的朋友可以自己去研究一下,这里由于篇幅原因不再细说。(其实我也不知道
总结
this引用
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
- this只能在"成员方法"中使用
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法
对象的引用传递给该成员方法,this负责来接收
构造方法
- 名字必须与类名相同
- 没有返回值类型,设置为void也不行
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
- 构造方法可以重载
- 一旦用户定义,编译器则不再生成。
- 通过this引用访问构造方法,必须是构造方法中第一条语句,并且不能形成环。