Java基础7--构造函数--this--static内存详解

7-1,构造函数-概述

1,特点:

(1)函数名与类名相同。

(2)不用定义返回值类型。

(3)没有具体的返回值。

2,构造函数

构造函数就是构建创造对象是调用的函数。

3,作用

可以给对象进行初始化,即对象一创建里面就包含一些内容。

4,示例:

class Person{
	private String name;
	private int age;
	Person(){	//定义一个空参构造函数
		Sop("Person run ...");
	}
	public void speak(){
		Sop(name+"::"+age)
	}
}
class Demo {
	public static void main(String[] args) {
		Person p = new Person();
	}
}

7-2,默认构造函数

1,创建对象都必须通过构造函数初始化。

2,一个类中如果没有定义过构造函数,那么该类中一定会有一个默认的空参构造函数,如果在类中定义指定的构造函数,那么类中的默认的构造函数就没有了。

3,构造函数是用来给对象初始化的,没有初始化的对象绝对不能用。

 

7-3,一般函数和构造函数的区别

区别:

(1)构造函数:对象创建时,就会调用与之对应的构造函数,对对象进行初始化。

一般函数:对象创建后,需要函数功能时才使用。

(2)构造函数:对象创建时会调用,只调用一次。

一般函数:对象创建后,可以被调用多次。

 

7-4,构造函数-重载

1,什么时候定义构造函数?

在描述事物时,该事物已存在就具备一些内容,这些内容都定义在构造函数中。

2,重载:函数名相同,参数列表不同。

示例:

class Person {
	private String name;
	private int age;
	Person() {		//函数1
		name = "baby";
		age = 1;
	}
	Person(String n) {	//函数2
		name = n;
	}
	Person(String n,int a) {//函数3
		name = n;
		age = a;
	}
	public static void main(String[] args) {
		Person p1 = new Person();		//调用函数1
		Person p2 = new Person("zhangsan");	//调用函数2
		Person p3 = new Person("lisi",20);	//调用函数3
	}
}

3,注意,形如:

Person(int a,String n);

Person(String n,int a);

的两个函数是重载形式。

 

7-5,构造函数-内存图解

Person p = newPerson("lisi",20);

p.speak();

以上两句在内存中表示为:


步骤:

(1)从main函数开始,main函数进栈,局部变量p进栈。

(2)在堆中建立p的实体,分配地址,给变量默认初始化。

(3) new Person("lisi",20);调用了Person的构造函数,把实参传入,Person(String,int)函数进栈。

(4)把传入的参数赋值。

(5) Person p = newPerson("lisi",20);执行完毕,把地址值赋给栈中的p,p指向堆中的实体。

(6)Person(String,int)执行完毕,弹栈。

(7)p.speak();调用speak方法,speak()进栈。

(8)执行speak中的输出语句。

(9)speak()执行完毕,弹栈。

 

7-6,构造函数-细节

1,构造函数:Person(String n){name=n;}

与一般函数public void setName(String n){name= n;}

不冲突,构造函数在创建对象的一开始的时候就赋值,只调用一次,值不能再改,一般函数可以调用多次,可以在后期对值进行改变。

2,构造函数中可以调用一般函数,一般函数中一般不调用构造函数,构造函数是用来初始化对象的,若非要在一般函数中调用构造函数,需要新创建一个对象。

3,Person(){}与voidPerson(){}不一样,前者为构造函数,后者为一般函数,构造函数加上修饰符就变成了一般函数,一般函数可以与类名相同。

4,若Person类中已经定义了构造函数,这时默认构造函数已经被覆盖,若只有一个void Person(){},而main中有Person p = new Person();则编译失败,因为voidPerson(){}是一个一般函数主函数无法找到Person(){}给对象初始化,所以报错误。

5,构造函数中是有return语句的,return语句是用来结束函数用的,若return后面还有语句,则编译报错,因为return后面的语句不会被执行到。

 

7-7,关键字-this使用场景-内存图解

1,this表示这个对象的***,比如this.name表示这个对象的name属性。

2,场景:当成员变量和局部变量重名,可以用关键字this来区分。

3,this:代表对象,代表当前对象。

This就是所在函数所属对象的引用。

简单说:哪个对象调用了this所在的函数,this就代表哪个对象。

例如:

class Person {
	private String name;
	Person(String name) {
		this.name = name;
	}
	public static void main(String[] args) {
		Person p = new Person("zhangsan");
	}
}

上例中的this指代的就是p对象,this.name指向的是堆内存中这个对象的name属性。

 

4,内存图解:

class Person{
	private String name;
	private int age;
	Person(String name) {
		this.name = name;
	}
	public void speak() {
		sop(name + ":" + age);
	}
}
class ThisDemo {
	public static void main(String[] args) {
		Person p = new Person("zhangsan");
		p.speak();
	}
}

步骤:

(1)从main函数开始,main函数进栈,局部变量p进栈。

(2) new Person("zhangsan");在堆内存中开辟空间,分配地址,name和age变量进入堆中的对象,默认初始化为null和0。

(3)Person(name)构造函数进栈。

(4)把"zhangsan"传入,赋给局部变量name。

(5)函数进入栈会自动带有一个this引用,因为是p调用Person(),所以this的值为新创建对象的地址0X0078。

(6)Person中的this指向堆中的0X0078。

(7)this.name指向的是0X0078中的name,给此name赋值为"zhangsan"。

(8)Person(name)执行完毕,弹栈。

(9)p = newPerson("zhangsan");把0X0078赋给main中的p。

(10)speak()函数进栈,并自带一个this引用。

(11)p.speak();p调用speak,把p的地址给this。

(12)执行完输出弹栈。

说明:其实每个函数中都自带一个this,只不过不重名时不用区分,重名时用this区分开来,this都指代当前对象的地址。

如:sop(name + age);的标准写法是:sop(this.name + this.age);

 

7-8,this关键字使用场景及细节

1,在构造函数中调用构造函数

Person(String name) {
	this.name = name;
}
Person(String name,int age) {
	this(name);	//this代表对象,给对象初始化,调用的是同类中上面的那个构造函数
	this.age = age;
}

this也可以用于在构造函数中调用其他构造函数。

注意:只能定义在构造函数的第一行,因为初始化动作要先执行。这说Java的语法,不这样写会报错。

2,示例代码内存图解:

class Person{
	private String name;
	private int age;
	Person() {
		name = "baby";
		age = 1;
		sop("Person run...");
	}
	Person(String name) {
		this.name = name;
	}
	Person(String name,int age) {
		this(name);
		this.age = age;
	}
	public void speak() {
		sop(this.name + "-" + this.age);
	}
}
class ThisDemo {
	public static void main(String[] args) {
		Person p = new Person("zhangsan",30);
		p.speak();
	}
}

步骤:

(1)从main函数开始,main函数进栈,局部变量p进栈。

(2) newPerson("zhangsan",30);在堆中开辟内存空间,分配地址,name和age默认初始化为null和0。

(3)传参数,Person(name,age)进栈,自带this引用,指向堆内存0X0067,局部变量name,age传参数赋值。

(4)this(name)调用Person(name),自带this引用,指向堆内存0X0067,局部变量name赋值。

(5)this.name = name;指Person(name)中的name赋给堆中实体的成员变量name。

(6)Person(name)运行完弹栈。

(7)执行Person(name,age)中的this.age=age;给堆中的成员变量赋值。

(8)Person(name,age)运行完弹栈。

(9)把0X0067赋给main中的p。

 

3,避免程序出现以下情况:

class Person{
	private String name;
	private int age;
	Person() {
		this("haha");//2.调用Person(name)   4.调用Person(name)....
		name = "baby";
		age = 1;
		sop("Person run...");
	}
	Person(String name) {
		this();//3.调用Person()    5.调用Person()....
		this.name = name;
	}
	public static void main(String[] args) {
		new Person();  //1.给Person初始化,调用Person()
	}
}

上述代码是Person()和Person(name)之间的来回调用,导致栈内存溢出。

 

7-9,this应用

程序:判断是否是同龄人

public Person {
	private String name;
	private int age;
	Person(){}
	Person(String name,int age) {
		this.name = name;
		this.age = age;
	}
	public static void main(String[] args) {
		Person p1 = new Person("aa",20);
		Person p2 = new Person("bb",30);
		boolean b = p1.compare(p2);
		System.out.println(b);
	}
	public static boolean compare(Person p) {
		/*方法一
		if(this.age == p.age) {
			return true;
		} else {
			return false;
		}
		*/
		//方法二
		return this.age == p.age;
	}
}

7-10,static关键字-数据共享

用static修饰的数据是可以被共享的数据。

如:String name;

    staticString country = “cn”;

由于每个人的name是不一样的,但是country一样,都是cn,如果不将cn定义为static的,当每创建一个对象,每个对象中都会有cn,浪费内存空间。

可以把country定义为static的,这样所有的对象都可以共享这个数据,哪个对象需要,调用即可。

 

7-11,static的特点

1,用static修饰的成员先于对象存在,存在于类中,可以用类名直接调用。例如Person.country;

2,不能所有都定义成static的,有些可以被共享,但是有一些是每个对象特有的,不能被共享。

3,特点:

(1)static是一个修饰符,用于修饰成员。

(2)static修饰的成员被所有的对象所共享。

(3)static优先于对象存在,因为static修饰的成员随着类的加载就已经存在了。

(4)static修饰的成员多了一种调用方式,可以直接被类名调用,格式:类名.静态成员。

(5)static修饰的数据是共享数据,对象中存储的是特有数据。

 

7-12,成员变量和静态变量的区别

class person{
	String name; //成员变量,实例变量
	static String country = "cn"; //静态变量,类变量
	......
}

区别:

(1)两个变量的生命周期不同。

成员变量:随着对象的创建而存在,随着对象的被回收而释放。

静态变量:随着类的加载而存在,随着类的消失而消失。

(2)调用方式不同。

成员变量:只能被对象调用。

静态变量:可以被对象调用,也可以被类名调用。

(3)别名不同。

成员变量:也成为实例变量。

静态变量:也成为类变量。

(4)数据存储位置不同。

成员变量:数据存储在堆内存的对象中,所以也叫对象的特有数据。

静态变量:数据存放在方法区(共享数据区)的静态区,所以也叫对象的共享数据。

 

7-13,static的注意事项

1,注意事项:

(1)静态方法只能访问静态成员。(非静态既可以访问静态,又可以访问非静态)。

(2)静态方法中不可以使用this或者super关键字。(因为静态先加载,没有对象,不可以用this)

(3)主函数是静态的。

2,main()是静态的,不可以直接调用非静态的方法,可以通过创建对象调用。

非静态变量在使用的时候前面省略了this,静态变量在使用时前面省略的是类名。

class Person{
	public static void main(String[] args) {
		run();//run必须是静态的,否则报错
	}
	public static void run() {
		System.out.println("run...");
	}
}

7-14,main函数详解

1,public static void main(String[] args)

主函数的特殊之处:

(1)格式是固定的。

(2)被JVM识别和调用。

public : 因为权限必须最大。

static : 程序从main函数开始执行,不需要对象调用main函数,其实是直接被主函数的类名调用的,所以必须是静态

例如要运行Person类,一般我们在命令行输入java Person执行,其实JVM调用的是java Person.main。

void : 主函数没有具体的返回值。

main : 函数名,不是关键字,只是一个JVM识别的固定的名称。

String[] args : 这是主函数的参数列表,是一个字符串数组类型的参数。

 

2,调用main函数的时候也要向String[]args传递实参,是由JVM传递的。

可以使用System.out.println(args);验证,结果为:

[Ljava.lang.String@c17164

可以说明是一个String的数组。

默认这个数组的长度是0。

JVM向String[] args传递的是newString[0]。

 

3,我们在运行程序的时候,可以给main函数传递参数,程序中的任何结果最终都要转成字符串显示在屏幕上。

 

4,运行程序时传值,命令行中,在使用java命令时传递。

java Person haha hehe xixi 用空格分隔,作为数组中的元素传递给args。

 

5,String[] 中的args可以变化,不是固定的,args是arguments的简写。

 

6,public static void main(String[] args)

 

    publicstatic void main(int[] x)

二者不冲突,是重载的形式。

如果将后面的int[] x变为String[]x,则冲突。

 

7-15,static关键字-程序内存详解

以下面程序为例:

class Person{
	private String name;
	private int age;
	static String country = "cn";
	public Person(String name,int age) {
		this.name = name;
		this.age = age;
	}
	public void show() {
		System.out.println(Person.country + ":" + this.name + ":" + this.age);
	}
	public static void method() {
		System.out.println(Person.country);
	}
}
class StaticDemo{
	public static void main(String[] args) {
		Person.method();
		Person p = new Person("Java",20);
		p.show();
	}
}

当在命令行用java StaticDemo运行这个程序时,这个类就会被加载进内存,需要进行空间的开辟,进入方法区,方法区分为静态区和非静态区两个区域。


步骤:

(1)当执行StaticDemo时,StaticDemo类就加载进内存,并开辟空间,默认构造函数StaticDemo()也加载进内存,加载进内存的方法区的非静态区。

(2)接着main函数也进入内存,进入内存方法区的静态区(静态区的方法和非静态区的方法都是被共享的,唯一的不同是封装的数据不同),非静态区的所有成员都有一个this所属,因为非静态区的方法都被对象调用,而静态区的方法都是所属自己的类名。

(3)当java StaticDemo,回车时,函数加载完毕,StaticDemo直接用类名调用主函数,这时main函数进栈,main函数中的所有代码都在静态区的static main()中。

(4)运行Person.method();时用到了Person类,这时Person加载,这时JVM会在classpath下找是否有Person.class文件,若没有则在当前默认目录下找,找到后就会将Person.class加载进内存,Person类中的构造函数Person(name,age)以及其中的代码和void show()方法以及其中的代码都加载进非静态区;Person中的静态方法method()和静态变量country加载进静态区,country默认初始化为null,因为给他赋值为”cn”,所以马上显示初始化为cn,现在country=cn。

(5)Person.method();是用类名调用,说明method()是static的,调用时直接在静态区的Person类中找method方法,找到后,method方法进栈,注意此方法是没有this所属的,只有非静态区持有this。

说明:method方法在方法区,为何进栈执行?

因为方法区是方法的存放区,也叫方法表,所有的方法都在这里。而栈是运行区,因为方法内可能会有局部变量,局部变量是需要在栈内存中开辟空间存储的,而且运行完局部变量要释放,所以要到栈中运行。但System.out.println()方法不会进栈执行,这个方法是用于输出的,没有局部变量。栈中只存放方法的局部变量,进栈后method方法开始执行,由于只有一个System.out.println()方法,所以直接到静态区的method中找到System.out.println()执行,有局部变量时才在栈中开辟空间。

method方法的sop中有Person.country,这时直接从静态区的Person类中找到country,并且输出对应的值。

(6)method方法运行完毕,弹栈。

(7)执行Person p = new Person("Java",20);局部变量p进栈。

(8)在堆中为p对象开辟空间,分配地址,成员变量name,age进入内存,并默认初始化为null和0,然后执行构造函数初始化,此时构造函数Person(name,age)进栈,给对象中的数据初始化,函数中持有一个this引用,并且值为0X0078,因为0X0078的对象在调用它,局部变量name被赋值为Java,age被赋值为20,因为Person(name,age)进栈并且初始化完毕,就开始执行方法区中的代码(Person(name,age)方法的代码),因此this代表0X0078,所以通过这个地址找到堆中的name并赋值为Java,将20赋值给age。

(9)Person(name,age)运行完毕,弹栈。

(10)new Person("Java",20);初始化完毕,把地址0X0078赋给p,p指向堆内存。

(11)p.show();show方法进栈,因为show是非静态的,自带一个this引用,this指向0X0078。show中有一个sop,sop中的country在编译完后已经有类名所属,直接在静态区中的Person类中调用,name和age前边都省略了this,this是show中的this,代表0X0078,所以打印0X0078中的name和age,即Java和20,然后输出。

(12)输出完毕,show方法执行完毕,弹栈。

(13)执行到main函数中的return语句,main函数结束,main函数弹栈,程序执行结束,JVM结束。

7-16,static关键字什么时候用?

静态什么时候用?

1,静态变量

当分析对象中所具备的成员变量的值都是相同的,这时这个成员就可以被静态修饰。只要数据在对象中都是不同的,就是对象的特有数据,必须存储在对象中,是非静态的。如果是相同的数据,对象不需要做修改,只需要使用即可,不需要存储在对象中,定义成静态的。

2,静态函数

函数是否用静态修饰,就参考一点,就是该函数的功能是否有访问到对象中的特有数据。简单点说,从源代码看,该功能是否需要访问到非静态的成员变量,如果需要,该功能就是非静态的,如果不需要,就可以将该功能定义成静态的。当然,也可以定义成非静态的,但是非静态需要被对象调用,而创建对象调用非静态的没有访问特有数据的方法,该对象的创建是没有意义的。

 

7-17,static静态代码块

静态代码块是随着类的加载而执行的,而且从这个类加载到消亡,只执行一次。

作用:可以给类进行初始化。

格式:

class Person {
	static {
		...
	}
}

应用:不是所有的类都是通过构造函数初始化的,如果一个类中的方法全是静态的,那就不需要创建对象,不创建对象就没有new的过程,不new就无法对构造函数初始化,而这时就可以用静态代码块对类进行初始化。

 

7-18,构造代码块

1,构造代码块定义在类中,它可以给所有对象进行初始化,也就是说,每次new一个对象的时候,它都会执行。

2,构造代码块与局部代码块的区别

构造代码块定义在类中,局部代码块定义在方法体中。

3,格式:

class Person {
	{
		//构造代码块...
		//抽取出所有对象共性的初始化内容,定义在这里。
		//每次创建一个Person对象,这个代码块都会执行。
	}
	...
}

4,程序执行顺序

静态代码块-->构造代码块-->构造函数

 

7-19,总结

1,被static修饰的成员具备以下特点:

(1)随着类的加载而加载。

(2)优先于对象而存在。

(3)被所有对象共享。

(4)可以直接被类名调用。

 

2,static使用时注意:

(1)静态方法只能访问静态成员。

(2)静态方法中不可以写this,super关键字。

(3)主函数是静态的。


  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值