面向对象—this关键字的使用

一.类和对象的概述

类:描述生活中的一类事物,是一类事物所具有的共性内容。比如人类,动物类,车类。
对象:基于这个类的实体(实例),比如人类中的某一个具体的人,张三就是一个具体的对象,一个具体实例。他有自己的属性(年龄,性别,身高,地址等等),行为(吃饭,睡觉,学习等等),通过实例就可以调用行为功能。

注意:类是一些相关属性与行为的集合,是对象的抽象描述,对象是类的实例。先要使用类来描述事物,才可以使用类来创建对象。

二.构造函数

 

1.构造函数的缘由

对于人这个事物而言,一般人在出生之后就会有名字和年龄,那么这个事物一旦被创建就会有自己特定的属性。
创建一个对象的时候,如果需要创建好之后,就必须拥有一些属于自己本身特有的数据,而不是后面通过其他方式去修改,那么也就是说在创建对象的过程中,就应该把这些数据赋值完成。

2.构造函数作用

构造函数就是使用关键字new创建对象时,JVM会自动调用的函数,用来对对象成员变量进行初始化。

可以说没有构造函数就没有对象,构造函数是创建对象的。

3.构造函数执行时机

①javac命令,启动编译器,检查基本语法错误。然后编译源程序生成可被虚拟机执行的字节码文件(也就是class文件)

②java命令,启动JVM,加载class文件到方法区,找到程序入口(main方法),main方法进栈。
③ 通过new关键字,在堆内存开辟内存空间,对对象所在类的成员变量进行默认初始化,然后调用构造方法(无参构造调用无参,有参构造调有参构造)
④ 执行构造函数的隐式三步(super对父类进行初始化、对对象成员变量显示初始化、执行构造代码块)
⑤ 执行构造函数中的其它代码,其它代码执行完毕,构造函数出栈,对象创建完毕。
⑥ 把对象的内存地址值返回给栈内存开辟的引用变量空间。

4.构造函数与一般函数的区别:

4.1.时间不同:
①构造函数在对象创建过程中执行,当对象创建完毕,构造函数就结束了。
②一般函数是在对象创建完毕之后,通过对象的引用(对象堆内存地址值)来调用,什么时候使用,什么时候调用。
4.2.调用次数不同
①构造函数只要在new对象的时候,会被调用,一旦对象创建完成,我们不能手动去调用构造函数。
②一般函数可以通过对象随意的调用,没有次数限制。
4.3.互相调用问题
①在构造函数中可以调用一般的函数,但是一般函数不能调用构造函数
4.4.调用方式不同
①构造函数是在创建对象时,由JVM自动调用
②一般函数是在对象创建完毕,通过对象调用

5.构造函数注意事项

①构造函数是用来创建对象的,它不需要书写返回值
②构造函数的名字必须和当前类名一致。
③参数列表,可以和一般函数的参数列表一样。(构造函数是以重载方式存在的)

默认构造函数:当我们写一个类的时候,在这个类中不写任何的构造函数,那么在使用javac编译java源代码之后,生成class文件中,会自动添加一个没有参数的构造函数。把这个构造函数称为默认的构造函数。

注意:如果自己没有写无参构造,那么编译器会在class文件自动添加一个无参构造,如果自己写了构造函数(无论是有参还是无参构造),那么编译器不会再class文件添加默认的构造函数。

三.this关键字原理

由于一个类中可以产生多个对象,那么我们就要知道类中的构造函数到底是哪个对象所调用的,我们同时也要知道当前构造函数是给堆内存中哪个地址中的哪个对象的成员初始化值,所以在当前构造函数进入栈内存的时候,会在栈内存中的构造函数中生成一个隐式的this引用变量,来记录当前这个构造函数是被哪一个对象调用。

1.this关键字的作用

1.1. 同一类中构造方法之间相互调用

一般方法调用:方法名(参数列表)。
构造方法调用: this(参数列表), 表示调用本类的其它构造

1.2. 不能相互嵌套调用,否则会是死递归。

1.3. this语句必须在构造的第一行

当有this(参数列表)的时候,就没有隐式super()了,因为初始化只能有一个,而且他俩不能公用,因为都需要放在语句的第一行,

父类的构造只要子类访问,完成父类的初始化就行(不管访问的是父类的有参还是无参构造)。

1.4. 记录调用函数的对象地址值

this和对象有关,与类无关,谁调用this保存那个对象的地址值。

1.5. 区分局部变量和成员变量

只要是对象调用(不管是间接还是直接),就有隐式this,来记录对象的地址值。只有保存了地址值,才能赋值调用等其他操作。
静态方法中没有this,this是和对象有关的,而静态方法是和类有关的。

2.使用this调用构造函数时候注意问题

2..1. 构造函数之间不能相互嵌套使用,这样就导致无限制调用构造函数,导致死递归的问题。

2.2. this调用构造函数,必须放在构造函数的第一句。

我们通过this调用其他构造函数的目的就是希望通过其他的构造函数完成初始化动作,因此要求其他构造函数的初始化必须在本构造函数之前先完成初始化。
this:这个的意思,谁调用this就记录该对象的堆内存地址值。


注意:不管是构造函数还是一般函数,因为都要使用对象,所以只要通过对象调用,那么在这个函数中都有一个this引用变量存在,记录当前调用这个函数的对象堆内存地址值。
 

四.案例代码

1.Student04类

package cn.jason01;
/**
 * 这是演示构造函数和this关键字的用法
 * @author Jason 
 */
public class Student04 {
	private static String name;
	private static int age;
	private int number;

	public Student04() {
		// recursive constructor invocation Student03
		// 递归结构调用Student03,那么会是死递归,调用只能调用有参的
		// this();
		this("林青霞", 27);
		System.out.println("⑤ 无参构造...this的地址值:  " + this);
		show();
		this.show();
	}

	// 构造两个参数
	public Student04(String name, int age) {
		super();
		this.name = name;
		this.age = age;
		System.out.println("① 有两个参数构造...this的地址值:" + this);
		System.out.println("② 隐含this调用成员变量: " + name + "  " + age);
		System.out.println("③ this调用成员变量:        " + this.name + " " + this.age);
		System.out.println("④ 类名调成员变量:      " + Student04.name + " " + Student04.age);
	}

	// 构造三个参数
	public Student04(String name, int age, int number) {
		this(name, age);
		this.name = name;
		this.age = age;
		this.number = number;
		System.out.println("有三个参数构造...this的地址值:" + this);
		System.out.println("this调用成员变量 :" + name + " " + age + " " + number);
	}

	public void show() {
		System.out.println("⑥ show方法...this的地址值:" + this);
	}
}

Student04Test测试类

package cn.jason01;
/**
 * 这是测试构造函数和this关键字的类
 * @author Jason *
 */
public class Student04Test {
	public static void main(String[] args) {
		System.out.println(".................构造函数以及this关键字的用法.................\n");
		System.out.println("...................创建对象,使用无参构造....................");
		Student04 s = new Student04();
		System.out.println("..................无参构造完毕,创建对象完毕...................\n");
		System.out.println("...................创建对象,使用有参构造....................");
		Student04 s1 = new Student04("林青霞", 27, 123456);
		System.out.println("..................有参构造完毕,创建对象完毕...................\n");
	}
}


输出结果:

.....................................构造函数以及this关键字的用法..................................

.........................................创建对象,使用无参构造.........................................
① 有两个参数构造...this的地址值:cn.jason01.Student04@22aed3a5
② 隐含this调用成员变量: 林青霞 27
③ this调用成员变量: 林青霞 27
④ 类名调成员变量: 林青霞 27
⑤ 无参构造...this的地址值: cn.jason01.Student04@22aed3a5
⑥ show方法...this的地址值:cn.jason01.Student04@22aed3a5
⑥ show方法...this的地址值:cn.jason01.Student04@22aed3a5
......................................无参构造完毕,创建对象完毕.......................................

.........................................创建对象,使用有参构造.........................................
① 有两个参数构造...this的地址值:cn.jason01.Student04@3c9076d
② 隐含this调用成员变量: 林青霞 27
③ this调用成员变量: 林青霞 27
④ 类名调成员变量: 林青霞 27
有三个参数构造...this的地址值:cn.jason01.Student04@3c9076d
this调用成员变量 :林青霞 27 123456
......................................有参构造完毕,创建对象完毕......................................

2.内存图解



注意事项:

静态变量在静态区开辟空间存储数据,先给静态成员变量默认初始化,当所有的静态成员变量默认初始化完毕,JVM又会重新开始给每一个静态变量重新显示初始化(赋值初始化),这时类才加载完成。

内存图中凡是箭头指向静态区的表示:先在堆内存找,没有就在方法区找所在的类,然后在静态区找成员变量,这里并没有引用指向。只有关键字new出来的对象才有地址引用。

3.内存图详解

   ① java命令启动JVM,JVM先加载mian函数所在的类,加载Student04Test.class字节码文件到方法区,静态区加载到静态区,非静态加载到非静态区。

②当Student04Test.class字节码文件加载完成时,程序向下执行,从方法区中把mian方法加载到栈内存,然后向下遇到Student04这个类,JVM先在方法区中找,没有就加载Student04.class字节码文件到方法区。

③④因为Student04.class中有静态成员变量,静态成员变量和成员方法、静态代码块都加载到静态区中,然后才对静态成员变量开辟空间,进行默认初始化。当所有静态成员变量默认初始化完成后,JVM又重新对静态成员变量进行显示初始化。当显示初始化完成后,所有的类文件才加载完成。

⑤创建Student04的对象,创建对象先执行右边,先在堆内存开辟空间,比如十六进制的地址值是0x12,然后对Student04的成员变量进行默认初始化,即int number=0。

⑥然后JVM自动调取Student04相对应的构造函数,那么Student04的无参构造进栈,因为有this("林青霞",27),所以就没有super,this和super不能共存在一个方法中。

⑦那么通过this("林青霞",27)访问Student04的带参构造,这时Student04的带参构造也有隐式this记录了当前对象的地址值,因为是this("林青霞",27)访问Student04的带参构造,所以间接把地址值0x12给了Student04的带参构造。把林青霞,27传参给了有参构造的局部变量name和age,因为成员变量name和age是静态static修饰,所以在堆内存中没有找到,就会去方法区中找,并且修改其值。当完成这些之后,Student04的有参构造执行完毕,出栈,就从栈内存消失。

⑧然后回到Student04的无参构造执行下一行代码show方法,那么show方法从方法区中加载到栈内存,隐式this机路地址值0x12,并且指向堆内存。

⑨show执行完毕,出栈,从栈内存消失。

⑩student04的无参也执行完毕,出栈,从内存中消失。

⑪这时student04的构造函数执行完毕,Student04的对象创建完毕。执行创建对象的左边,在main方法中给Student04开辟空间,空间名称为引用变量s,在吧地址值返回给s。

⑫main方法出栈内存,完成。

4.图解归纳

1.从上图结果可以看出,只要是对象调用的函数(方法)就存在隐式的this,用来记录哪个对象的地址值。

2.当函数(方法)中存在局部变量和成员变量同名时,使用this就指的是成员变量,否则根据就近原则,局部变量在栈内存开辟空间,把传过来的值又覆盖自己的值,成员变量根本就没有赋值,所以没意义。

猜你喜欢

转载自my.oschina.net/u/3842696/blog/1801740