一、关于Day9的重点知识复习
1. 类和对象的关系
(1)类是模板,规定了一类对象所共同具备的属性和行为,class声明类
(2)对象是类的具体实例,根据类来产生对象,当对象产生时,类中的成员变量才有初始值。同一个类的不同对象之间,属性的个数,类型都相同。但是属性值不同,表达出来的行为就不同。new关键字产生对象,new出来的对象都在堆中存储
//引用部分 = 新生成的对象
Person per = new Person();
2. 构造方法:为对象做初始化的
(1)构造方法名称和类相同,没有返回值类型
(2)构造方法可以重载(重载方法的形参个数不同,类型相同,因为一个类的属性的类型是定义好的)
(3)当类中没有明确定义构造方法,编译器会生成默认的无参构造;若明确定义了构造方法,默认的无参构造就不再产生
3. 对象初始化的三步:
(1)类加载
(2)为对象开辟空间(到底开在哪,开辟了多大空间,由JVM进行内存管理,程序员不可见)
(3)为对象属性赋初始值(类型默认值或定义的的初始值)
二、this关键字
1. this修饰成员变量
(1)发现问题:下图中的类中的一个参数的有参构造函数只有一个参数且为String类型,对于使用者来说就有歧义,这个构造方法到底是给name赋值还是给color赋值
(2)对于上述问题解决的一个坑:如下图所示,如果将构造函数的形参名称修改为为和其在类中对应赋值的成员变量名称相同,则赋值结果为null,也就是没赋值。
原理:编译器的就近匹配原则
编译器的就近匹配原则:程序中使用一个变量或调用一个函数的时候编译器查找变量或者函数有优先级。优先查找距离调用位置最近的变量或方法在哪,找到了就直接调用
如图,构造函数中的name = name是两次变量的调用,根据就近原则,则两个name都是取自构造函数的形参,因此构造函数结束后,类中的name属性初始值为null
(3)通过this.属性名称:直接从类中寻找同名变量,打破就近匹配原则
原理:this.name = name这个语句,第一个从类中寻找也就是对应类中属性name,而第二个name依旧遵循就近匹配原则是构造函数的形参,因此赋值成功
2. this修饰方法
2.1this修饰成员方法:表示调用类中的成员方法
(1)语法:this.方法名称
(2)实例
(3)注意1:由于fun函数没有形参,因此根据就近原则下列的name和age匹配的就是当前类的属性name和age,在编译后的class文件中编译器会自动添加this,也就是this.name和this.age
class文件:
(4)注意2:在本例子中,在test函数中调用fun函数加不加this效果相同都能打印(不加this的话,在编译的时候会加上)。
class文件:
(5)注意3:当有继承关系之后,明确调用的是当前类的成员方法,这时候一定要加上this,写成this.方法名称的形式。
补充知识:快捷键生成构造函数:
步骤一:alt+ins,然后选择第一个Constructor
步骤二:选择需要构造函数的参数,选择好后点ok即可生成所需的构造函数
(Ctrl + A是全选,Ctrl + 鼠标点击是可以决定选择哪些,右下角的选择None是一个都不选也就是无参构造,被选上的属性会呈现蓝色高亮)
2.2this调用构造方法
(1)语法:this(参数); //表示调用构造方法(根据参数的个数和名称来决定调用的是哪一个构造方法)
(2)两个问题
A. 问题1:成员方法中能否调用构造方法
B. 问题2:构造方法之间能否互相调用
解析:构造方法是为了产生对象,因此构造方法只能通过产生对象时,由编译器来自动调用,为成员变量赋初始值。因此问题1错误。
一组构造方法就是为了不同成员变量赋值的,因此可以互相调用。问题2正确
结论:只能在构造方法之间调用构造方法,不能再成员方法中调用构造方法
(3)实例
A. 应用场景1:要求不管调用哪个构造函数,都需要输出一行星号
方案1:缺点:拥有很多的重复代码System.out.println("*********************");
方案2:对于方案1的修改。如上图代码所示,一个参数的构造方法可以看作是,调用了无参构造然后再this.name = name。两个参数的构造方法可以看作是,调用了一个参数的构造方法,然后this.age = age; 。具体修改如下,利用this(参数)来表示调用构造方法
(4)注意1:this构造方法调用必须写在构造方法首行
原因:
(5)注意2:this的构造方法调用不能成环(出现环就没用终止条件,成了死循环)
下图中如果调用两个参数的构造函数,则左边是正确的代码,结构是线性结构;而右边的是错误的代码,其中出现了环,也就是红色的部分。
(6)注意3:在一个构造函数中最多只能调用一个其他的构造方法,不可以调用多个其他的构造方法
3. this表示当前对象引用(了解即可)
(1)含义:当前是是通过哪个对象来调用的方法或属性,this就指代谁
(2)例子:
A. 代码含义:
在类中定义的成员方法test中直接打印this
在主方法中创建per和per1两个类,然后分别打印per和per1,毕竟分别调用per.test()和per1.test()两个函数
B. 结果:发现per和per.test()的值相同,per1和per.test()的值相同(注:这里的per和per1打印出来的是对象的相对地址,具体的物理地址只有JVM知道)
C. 结论:当前是是通过哪个对象来调用的方法或属性,this就指代谁
三、static关键字(看见static关键字就与对象无关)
注:static表示静态,与类相关,与具体的对象无关
1. static修饰属性,称之为类属性,静态属性,存放在JVM的方法区
(1)使用场景:当某一个类的所有对象的某一个属性,属性值都相同,则该属性就应该设计为类属性和类强相关,和具体的对象无关,比如下例中的country属性
(2)语法:static 变量类型 变量名称
(3)内存结构:
A. 类属性保存在JVM的方法区
B. Japanese类的所有对象默认共享类属性的内存区域
(4)注意1:类属性和类强相关,没有对象也能用
(5)注意2:Japanese类的所有对象默认共享类属性的内存区域,因此也可以通过对象来用
(6)注意3:Japanese类的所有对象默认共享类属性的内存区域,因此修改这个共享的类属性,其他对象也可见(操作的实际上是同一块内存区域)
(7)注意4:java中不支持在任何方法(包括main方法,成员方法)中定义静态变量!在方法中定义的一律都是局部变量,都在栈帧中存储!例如:不能在成员方法(只能通过对象来调用成员方法)中定义静态变量(不产生对象,可以直接通过类调用,与前者矛盾)。
如果在方法中定义静态变量,则会编译出错,例如下面代码
(8)一个坑
结论:定义一个对象引用值为null的时候,只有调用其成员方法或成员变量的时候才会抛出NPE异常,而调用其静态变量或静态方法则不会报错
例子:虽然j1不指代任何的Japanese的对象,但是j1仍然是Japanese类的引用,因此通过j1.country等同于Japanese.country
2. static修饰方法——类方法,和类相关,直接通过类名称调用,与对象无关
(1)语法:public static 函数类型 函数名
(2)应用场景:
(3)例子:
3. static修饰的静态域(静态属性,静态方法)和成员域(成员属性,成员方法)之间的关系(重中之重)
(1)两个问题:
(2)结论:不能在静态域中访问成员域(成员方法或者成员变量);在成员方法中能调用静态属性或静态方法(但是不推荐,一般直接使用类名称来访问静态域)
(3)例子:
A. 静态方法中使用成员变量,直接报错
B. 成员方法中调用静态属性,可以
(4)注意:因为main函数是静态方法,因此要在主方法中调用其他函数,那么这个其他函数也一定是静态方法
(5)注意2:static不能修饰外部类(*java中直接定义的,没有从属于某一个类的类是外部类),但是能够修饰内部类(在一个类的内部的类是一个内部类)
4. static变量的初始化:静态变量一般就地初始化或者在静态代码块中初始化
(1)就地初始化:定义静态变量时进行初始化操作
(2)代码块:使用{}定义的一段代码称之为代码块,根据代码块出现的位置以及关键字的不同,分为一下四种代码块
A. 普通代码块:直接定义在方法内部,不加任何修饰符定义的代码块成为普通代码块——>解决变量重名的问题
注意:如果这个变量a定义在普通代码块之前则会报错,因为第一个int a = 30;相当于已经定义了一个a,但是还没销毁,在下一个代码块中定义另一个a就会报错。而上述的代码是相当于在代码块中先定义了一个x然后销毁,再定义下一个x
B. 构造块:直接定义在类中,使用{}的代码块称之为构造块
构造块特点:每当产生对象时就会默认调用构造块,构造块优先于构造方法执行,多个构造块就会从上至下按先后顺序执行
原理:
class文件:
C. 静态代码块:使用static修饰,直接定义在类中的代码块成为静态代码块(静态代码块在类加载的时候执行,因此优先于构造方法和构造块和普通代码块)
a. 特点:静态代码块在类加载时执行依次,和具体产生对象无关。若有多个静态代码块,按照代码的书写顺序依次执行
b. 作用场景:一般用于初始化静态变量(打印类加载信息,)
c. 实例
注意1:如上图中的调用c2对象的时候,Demo这个类还并未加载到JVM中,而需要打印Demo.i的时候需要加载Demo这个类,因此静态代码块执行。(有的类并不是一开始就加载到JVM中的,而是需要使用的时候再加载到JVM中)
d. 实例2:静态块只会在类加载的时候有且执行一次
e. 特殊的静态代码块:主类中的静态代码块还会优先于主方法执行
原理:main存在于主类中,要执行主方法首先要加载主类到JVM中,当主类一加载,主类中的静态块就会自动执行
f. 一个坑
结论:如果要通过静态代码块给静态变量初始化,则直接以 (静态变量名称 = 值)的形式。如果在静态代码块中用int 变量名 a = 值则是创建了一个局部变量 (生命周期在静态代码块内部)
D. 同步代码块(多线程部分讲解,使用关键字sychronized修饰的代码块成为同步代码块)
注意:构造方法一般初始化的都是成员属性,构造方法是产生对象时为对象初始化的