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)主函数是静态的。