面向对象笔记
一、 对象在内存中的存放方法以及被调用过程
-
class文件
首先被加载到方法区
中的class文件内容
区域,类的成员变量
和成员方法
自然也随着类的加载而被加载到方法区。 - 创建对象的时候,首先在栈中开辟空间,用于存放对对象的引用地址(即
Phone p
的地址); - 在堆中开辟空间存放对象实体(即
new Phone()
),同时会给该对象的属性分配内存空间来存放该对象的属性值,即会把方法区中该类的成员变量复制一份到堆内存中(首先是默认值,然后才是调用者赋给的值),用于存放该对象的属性值,但该类的方法就不会被复制到堆中,而是通过堆中一个指针的引用指向方法区中该方法。(其实这一点很好理解,每个对象的属性值不一样,但是每个对象的行为却是一样的,因此每个对象需要有自己的成员变量来保存自己的属性值信息,而方法不用,只用调用同一个方法即可。) - 在调用对象的成员变量时,实际上是给该成员变量进行复制,因此是在堆中执行的。
- 在调用对象的成员方法时,该方法会被加载到栈中(实际上在栈中有个方法栈专门用来保存对方法的引用),该方法被调用完成后,立刻就被销毁了,因此对成员方法的调用发生在栈内存中。
注意:栈的特点是先进后出,画图的时候因从下往上,这里只是为了方便解释,画图的时候从上往下。
-
一个对象的引用图解
-
两个对象的内存图解
注意到:
- 两个对象分别有自己的成员变量空间;
- 两个对象通过地址指向方法区的同一个地址,即成员方法的地址;
-
三个对象的内存图解
注意到:两个引用指向同一个对象时,实际上是对同一个对像进行操作
二、成员变量、局部变量、静态变量的区别
(一) 成员变量和局部变量的区别
- 在类中的位置不同
- 成员变量:在类中方法外
- 局部变量:在方法定义中或方法声明上
- 在内存中的位置不同
- 成员变量:在堆内存中(对象的实体在堆内存中,成员变量是跟随着对象的实体,因此也就在堆内存中)
- 局部变量:在栈内存中(局部变量在方法内部,方法在被调用的时候会被加载到栈中,因此局部变量存在于栈内存中)
- 生命周期不同(在内存中的位置不同,生命周期自然不同)
- 成员变量:随着对象的创建而存在,随着对象的消失而消失;
- 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失;
- 初始化值不同(在内存中的位置不同,初始化值自然不同)
- 成员变量:有默认初始值;
- 局部变量:没有默认初始化值,必须定义、赋值,然后才能使用;
(二)成员变量和静态变量的区别
- 所属不同
- 成员变量:属于对象,也叫
实例变量(对象变量)
; - 静态变量:属于类,也叫
类变量
;
- 成员变量:属于对象,也叫
- 内存中位置不同
- 成员变量:随着对象的创建而存在于
堆内存
中; - 静态变量:随着类的加载而存在于方法区的
静态区
;
- 成员变量:随着对象的创建而存在于
- 生命周期不同
- 成员变量:随着
对象
的创建而存在,随着对象的消失而消失; - 静态变量:随着
类
的加载而加载,随着类的消失而消失;
- 成员变量:随着
- 调用不同
- 成员变量:只能通过对象名来调用;
- 静态变量:即可以通过类名来调用,也可以通过对象名来调用;
(三)静态变量和局部变量的区别
- 这个区别太明显,不做区分;
注意事项:因为在内存中的位置不同,所以要注意形式参数问题
- 基本类型:形式参数的改变不影响实际参数
- 引用类型:形式参数的改变直接影响实际参数
三、构造方法
构造方法的注意事项
- 默认构造方法问题:
- 如果在类中没有定义构造方法,系统将自动提供一个无参构造方法;
- 如果在类中定义了构造方法,系统将不再提供默认的无参构造方法;
- 构造方法的重载问题:
- 只要我们给出了构造方法,不管是无参还是代参构造方法,系统都不在提供构造方法,如果我们想使用无参构造方法,就必须自己定义声明;
- 建议永远自己给出无参构造方法;
四、Student s=new Student()
做了哪些事情
- 把
Student.class
文件加载到内存中方法区的class文件区域
;- 在
栈内存
中给s
变量开辟一个内存空间;- 在
堆内存
中给new Student()
对象申请一个空间;- 给
成员变量
进行默认初始化
;- 给
成员变量
进行显示初始化
;- 给
成员变量
进行构造方法初始化
(即通过构造方法给成员变量进行初始化);- 对象初始化完毕,把
堆内存
的地址值
赋值给s
变量;
- 总结:
-
构造方法
对类进行初始化实际上是对类中的数据
(即成员变量
)进行初始化; - 对成员变量进行初始化的时候,首先
默认初始化
,然后是显示初始化
,最后才是构造方法初始化
;-
默认初始化
实际上就是给数据赋默认值(即0,false,null,'\u0000',0.0f,0.0d,...)的过程, -
显示初始化
实际上就是把手动赋值的值赋值给成员变量,比如private int num=1
;这个1就是显示初始化, -
构造方法初始化
就是通过构造方法传递过来的值对成员变量进行初始化;
- 因此才是这个初始化顺序,即默认初始化
--->显示初始化
--->构造方法初始化
;
-
-
五、 成员变量的定义规则##
- 变量什么时候定义成成员变量?
- 如果这个变量是
用来修饰这个类的信息的
,那么该变量就应该定义为成员变量;
- 如果这个变量是
- 变量定义在哪里好呢?
- 变量的范围越小越好,因为能及时被回收;
六、static 关键字
- 特点:
- 随着类的加载而加载;
- 因此被
static
修饰的成员变量/成员方法
也叫类成员
;
- 因此被
- 优先于对象而存在;(由第一点就可以推出第二点)
- 自然也就优先于对象的成员变量和成员方法而存在;
- 被
static
修饰的成员变量
和成员方法
被类的所有对象共享;- 即一个对象修改了某个成员变量的值,其他对象该成员变量的值也被修改了;
- 这是我们判断是否使用
static
关键字的条件;- 即如果某个成员变量或成员方法是被所有对象共享的,那么这个成员变量或成员方法就应该被定义为静态的;
- 因为随着类的加载而加载,所以可以直接通过类名来调用,不用通过对象来调用(当然也可以通过对象名来调用);
- 随着类的加载而加载;
- 注意事项:
- 在静态方法中没有
this
关键字; -
静态方法
只能访问静态的成员变量
和静态的成员方法
;- 即静态只能访问静态,非静态可以访问静态和非静态;
- 在静态方法中没有
-
static
内存图解
注意到:
- 被
static
修饰的方法(如main方法)不用创建对象直接通过静态方法区
加载到栈内存中; - 当成员变量被
static
修饰的时候,该成员变量
就存在于静态方法区
的静态区
,而不是堆中,对象通过地址来指向该变量,这里就是静态标记
,而类的成员方法不被static
修饰时,存放在静态方法区的该对象专有的方法区,通过地址值指向这些方法,这就是方法标记
; -
静态方法区
中的静态区
专门用来存放被static
修饰的变量和方法,同时在方法区中专门有一个区域用来存放类的构造方法
和非静态成员方法
;
七、JVM内存简单区分
-
栈
- 特点:
- 每个线程都包含一个栈区,每个栈中的数据(基本类型,引用类型)都是私有的,其他栈不能访问,所以线程中的数据也是私有的;
- 栈中只存放基本数据类型以及引用数据类型的引用;
- 特点:
-
堆
- 特点:
- JVM只有一个堆区(heap),且被所有线程共享;
- 堆内存中存放的全是对象,且每个对象都包含一个与之对应的class的信息(class的目的是为了得到操作指令);
- 特点:
-
静态方法区
- 特点:
- 被所有的线程共享;
- 静态方法区中包含的都是在整个程序中
永远唯一
的元素,如所有的class文件,static修饰的变量;
- 静态方法区细分:
- class文件区域
- 特点:
- java编译生成的所有class文件首先被加到该区域,然后该class文件中的成员信息再被加载到栈或者堆中;
- 静态区
- 特点:
1. 所有被`static`修饰的`变量`和`方法`存放区域,通过地址值进行引用;
- 方法区
- 特点:
- 对象的
构造方法
和非静态方法
存放区域,通过地址值进行引用;
- 对象的
- 特点:
- 常量池
- 特点:
- 存放一些常量;
- 特点:
- class文件区域
- 特点:
八、代码块
- 定义:在java中,用
{}
括起来的代码被称为代码块,根据位置和声明的不同,可以被分为以下几类:- 局部代码块
- 在方法中出现,用于限定变量生命周期,及早释放,提高内存利用率;
- 构造代码块
- 定义:在构造方法的位置(即类中成员位置),用{}括起来的代码。每次调用构造方法前,都会先执行构造代码块,即先执行构造代码块,再执行构造方法;
- 作用:
- 可以把多个构造方法中相同的代码放到一起,提高代码复用性;
- 对对象进行初始化;
- 说明:每调用一次构造方法,构造代码块就会被执行一次,因此构造代码块会被执行多次;
- 静态代码块
- 定义:在类中成员位置,用{}括起来的代码,并且加了static修饰;
- 作用:对类进行初始化;
- 说明:静态代码块只执行一次;
- 同步代码块
- 定义: 在方法中出现,用{}括起来的代码,被
synchroized
修饰的代码块; - 作用:在多线程中保证
{}
中的代码是原子性操作,即保证线程安全;
- 定义: 在方法中出现,用{}括起来的代码,被
- 局部代码块
- 注意:
- 静态代码块,构造代码块,构造方法的执行顺序
- 静态代码块 > 构造代码块 > 构造方法
- 静态代码块,构造代码块,构造方法的执行次数
- 静态代码块:只执行一次;
- 构造代码块:执行多次,构造方法被调用几次,构造代码块就执行几次;
- 构造方法:执行多次;
- 静态代码块,构造代码块,构造方法的执行顺序
九、继承
1. 继承注意事项
- 父类的私有成员(成员变量,成员方法)不能被继承;
- 子类不能继承父类的构造方法,但是可以通过
super
关键字访问父类的构造方法; - 不要为了部分功能而去继承;
- 继承使用条件:是两个对象是集合中的属于关系时(两个类之间如果可以用
is a
来表述),就可以使用继承;
2.继承中类中成员之间的关系
1)继承中成员变量
之间的关系
- 当子类和父类中的成员变量名称一样时,遵循就近原则,即
- 首先到子类的局部变量中找,有就使用,没有执行下一步;
- 在子类的成员变量中找,有就使用,没有执行下一步;
- 在父类的的成员变量中找,有就使用,没有就报错;
2)继承中构造方法
之间的关系
- 子类中
所有的构造方法
默认都会访问父类的空参构造方法
;- 因为子类会继承父类中的数据,可能还会使用父类的数据。所以子类初始化之前,一定要先完成父类数据的初始化;
- 子类每个构造方法的第一条语句默认是
super();
,不写也默认有;
- 如果父类没有无参构造方法,那么子类的构造方法就会报错,解决方法:
- 方法一:在父类中加一个无参构造方法;
- 方法二:通过
super(...)
去显式调用父类的带参构造方法; - 方法三:子类通过
this
去调用本类的其他构造方法,从而间接调用父类的构造方法;
- 总结:
- 子类中一定要有一个构造方法去访问父类的构造方法,否则父类数据就无法初始化;
- 实际上这里是
分层初始化
,即先初始化爷爷类,在初始化父亲类,最后才是初始化子类,而super()
仅仅表示先初始化父类;
- 注意事项:
this(...)或者super(...)必须出现在第一条语句上;
如果不是放在第一条语句上,就可能对父类的数据进行了多次初始化,所以必须放在第一条语句上;
4)继承中成员方法
的关系
-
方法重写
(和方法重载分开):- 子类中出现了和父类中一模一样的方法声明(即返回值类型,方法名,参数列表均相同);
- 使用特点:
- 在访问重写方法时,如果子类有就先访问子类的,子类中没有,就到父类中找,如果没有找到,就报错;
简单记:就近原则,先找自己的,有就用;如果没有再找父类的;
- 什么时候使用方法重写:
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样既沿袭了父类的功能(通过
super.成员方法()
来调用父类的功能),又定义了子类特有的功能;
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样既沿袭了父类的功能(通过
- 方法重写注意事项:
- 父类的私有方法不能被重写;
因为父类私有方法就不能被继承,自然就不能被重写;
2. 子类重写父类的方法时,访问权限不能更低,即子类方法的访问权限必须大于等于父类方法的访问权限;
>子类重写父类方法的时候,最好声明一模一样。
3. 父类是静态方法
,子类也必须通过静态方法进行重写;
>父类是静态,子类重写时也必须是静态,否则会报错;
>
>父类是非静态,子类重写时也必须是非静态,否则会报错;
- 父类的私有方法不能被重写;
5)this
和super
的区别与联系
-
表示的意义
- this: 代表本类对象的引用;
- super:代码父类对象的引用(实际上是父类存储空间的标识);
-
用法
- 访问成员变量
//表示访问本类的成员变量
this.成员变量
//表示访问本类的成员变量
super.成员变量
- 访问构造方法
>//表示访问本类的构造方法
>
>this(...)
>
>//表示访问父类的构造方法,虽然不能继承父类的构造方法,但是可以通过super来访问
>
>//实际开发中,经常使用这种方法给父类的参数赋值,而且即使父类的参数是private修饰的,也不影响通过子类进行赋值;
>
>super(...)- 访问成员方法
//表示访问本类的成员方法
this.成员方法()
//表示访问父类的成员方法
super.成员方法()
成员变量属于每个对象 (而不是类)
私有的值,它表征了该对象的某种状态,因此父类的成员变量子类可以访问,却不能修改,而成员方法表征了某一系列对象所具有的行为,因此子类可以覆盖父类的方法,实现自己的特有的功能,但是JVM是如何实现方法重写时,调用的是子类的方法的了?
十、final
关键字
-
final
使用场景- 如果父类的某个
成员变量
只能被子类访问,但是不能被修改值; - 如果父类的某个
成员方法
只能被子类调用,但是不能被子类重写, - 如果某个
类
就是最终类,不能有子类;
- 如果父类的某个
-
final
特点:final可以修饰类
,成员变量
,成员方法
;-
final
修饰类
,该类不能被继承; -
final
修饰成员变量
,这个成员变量只能被使用,不能被子类重新赋值;当一个变量被
final
修饰时,不能修改实际上就是常量,这是自定义常量,
字面值常量就是字符串,true,10这种形式的;
3.final
修饰成员方法
,这个成员方法只能被调用,不能被子类重写;
-
-
final
修饰局部变量- 首先
final
可以修饰局部变量;区别于
private
等权限修饰符,这四个权限修饰符不能修饰局部变量,因为局部变量本来就只能在方法内部可以使用,方法外部不能使用,即本来就被封装起来了,权限修饰符再来修饰没有任何意义。
2.final
修饰基本数据类型时,基本类型的值不能发生改变;
3.final
修饰引用数据类型时,引用类型的地址值不能发生改变,但是该对象的堆内存的值是可以改变的,即该对象的成员变量的值是可以改变的,成员方法是可以被重写的;
- 首先
-
final
修饰变量的初始化时机- 被
final
修饰的变量只能赋值一次; - 对于非静态常量而言,赋值在构造方法完毕之前完成;
即case 1:如果被final修饰的变量已经赋值(例如
private int num=10;
),就不能在构造方法中给该常量赋值,否则会报错,case 2:如果被final修饰的变量未赋值(例如
private int num;
),可以在构造代码块中赋值,但不能再在构造方法中赋值,否则会报错;case 3:如果被final修饰的变量未赋值(例如
private int num;
),同时也没有在构造代码块中赋值,就可以在构造方法中赋值,此时不会报错; - 被
十一、多态
1. 多态:同一个事物(对象),在不同时刻体现出来的不同状态
2. 多态的前提:
- 要有继承关系;
- 要有方法重写;
- 要有父类引用指向子类对象;
Fu f=new Zi();
3. 多态中的成员访问特点
- 成员变量
>编译能否通过看左边,运行看左边;
>
>成员变量不存在重写概念,所以只能访问的父类的成员变量;
- 构造方法
>在创建子类对象(调用子类构造方法)的时候,会默认访问父类的构造方法(会先对父类进行初始化),对父类的数据进行初始化;
- 成员方法
>编译能否通过看左边,运行看右边;
>
>由于父类的成员方法可以子类重写,所以实际上调用的是子类的方法;
- 静态方法
>编译能否通过看左边,运行看左边
>
>静态和类相关,算不上重写,所以访问还是父类的;
总结:由于成员方法
存在重写
,所以他运行看子类的,成员变量
、静态方法
均不存在重写概念,所以运行看父类的
4. 多态的优点与缺点
1)优点
提高了代码的维护性(通过继承实现);
-
提高了代码的扩展性(通过多态保证);
由于子类可以重写父类的方法,所在在父类中调用父类的方法,在子类中重写这些方法,这样就保证了扩展性
//父类 class Animal { public void eat(){ System.out.println("eat"); } public void sleep(){ System.out.println("sleep"); } } class Dog extends Animal { public void eat(){ System.out.println("狗吃肉"); } public void sleep(){ System.out.println("狗站着睡觉"); } } class Cat extends Animal { public void eat() { System.out.println("猫吃鱼"); } public void sleep() { System.out.println("猫趴着睡觉"); } } class AnimalTool { private AnimalTool(){} //这里就体现出了由于多态而保证扩展性,如果这里传入Cat,Dog,就没有扩展性了, //每新添加一个Animal的子类,就需要把这个方法复制一份,扩展性就差了 //扩展性就体现在这里传入的是Animal,而不是Animal的子类 public static void useAnimal(Animal a) { a.eat(); a.sleep(); } }
2)缺点
- 不能使用子类中的特有功能;
- 解决:将父类的引用强制转换成对子类的引用(向下转型);
对象间的转型问题
向上转型
Fu f=new Zi();
向下转型
Zi zi=(Zi)f;
- 解决:将父类的引用强制转换成对子类的引用(向下转型);
3)多态继承中内存图解
- 这里应该可以理解
super
表示父类存储空间的标识这句话了吧; - JVM是如何实现方法重写时,调用的是子类的方法的了?当没有重写时,调用的是父类的方法了?这里不明白;
4)多态中的对象变化内存图解
注意:
-
ClassCastException
是在运行时才抛出的,因为在编译的时候,地址的指向没有出错,类是在运行时才加载到内存中去的,因此才会检测类的相关信息;
5)多态的种类
- 具体类多态(基本没用)
即左边是一个具体类,右边是具体类的子类
- 抽象类多态(常用)
即左边是一个抽象类,右边是抽象类的具体实现类
- 接口类多态 (用的最多)
即左边是一个接口,右边是接口的具体实现类
十二、抽象类
(一)为什么会有抽象类
由于现实世界的复杂性,有些事物虽然是对象,但是是对一类事物的总结(如水果,动物),本身是一个抽象的对象,没有具体的属性值和具体的功能实现,如果这个时候我们给这种对象具体属性值和具体的功能,就违反了对现实事物的描述,所以是不对的,因此我只能对这种事物进行声明,表明他有某个功能,只要是这个事物的子类,就一定有这个功能,就要实现这个功能,这就是抽象类的来源;
(二)抽象类的特点
- 抽象类和抽象方法必须用
abstract
来修饰; - 抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类;
- 抽象类不能实例化,即不能用抽象类来创建对象;
- 因为抽象类不是具体的,不能造出具体的对象,否则就违反了现实上面的说明;
- 但是抽象类是有构造方法的,只是不能实例化对象而已;
- 抽象类的构造方法专门用来给子类访问,对父类进行数据初始化;
- 抽象类的子类
- 如果不想重写抽象方法,该子类可以是个抽象类;
- 如果子类是一个具体的类,就必须重写所有的抽象方法;
- 抽象类的实例化是通过具体的子类来实现的,是多态的方式,因此多态的所有特点,使用规则均适合抽象的实例化;
(三)抽象类的成员特点
- 成员变量
- 既可以有变量,也可以有常量;(要和接口做区别)
- 构造方法
- 有构造方法,用于子类访问父类数据的初始化;
- 成员方法
- 抽象方法:强制要求子类做的事情
- 非抽象方法:子类继承的事情,提高代码复用性;
(四)抽象类的注意事项
- 一个抽象类中可以没有抽象方法,这样做是为了不然抽象类创建对象(抽象类本来就不能创建对象),必须由其具体实现类创建对象
-
abstract
关键字不能和那些关键字一起出现-
abstract
不能和private
一起出现冲突,
abstract
修改的方法必须由具体子类实现,而private
修饰的方法,子类无法访问;
2.abstract
和final
不能一起出现
>冲突,abstract
修饰的方法必须由具体子类实现,而final
修饰的方法不能被子类重写;
3.abstract
和static
不能一起出现
>无意义,abstract
修饰的方法没有方法体,而static
修饰的方法可以直接用类名调方法,这样相当于调的空方法;
4. 抽象类中的非抽象方法可以用static
修饰,这样用类名调方法就不会出现上述问题;
-
十三、接口
(一)接口的作用
提高事物功能的扩展性,弥补类的不足;
比如被训练的猴子可以表演杂技,但是表演杂技并不是动物和猴子固有的功能,因此不能写到动物类,猴子类,只能是被训练过的猴子拥有表演杂技的功能,这种表演杂技的功能就是额外的功能,既然不能写在类中,就通过接口实现这种功能;
(二)接口的特点
- 接口不能被实例化
接口中的方法都是抽象的,不给出具体实现;(JDK 8中接口中的方法可以有方法实现)
接口的实例化通过具体子类来实现,即通过多态的方式来实例化;
- 接口的子类
- 接口的子类可以是抽象类,但是没有意义
- 接口的子类可以是具体类,但是必须实现所有的抽象方法;
(三)接口的成员特点
- 成员变量
- 接口中的变量默认
public static final
修饰,即接口中的成员变量只能是常量;
- 接口中的变量默认
- 构造方法
- 接口没有构造方法,因为接口主要是扩展功能的,而没有具体存在,必须依附于某个主体(实际上就是对象);
所有的类都默认继承
Object
类,而Object
类唯一构造方法就是Object()
,因此如果在接口的实现类的构造方法中如果出现super()
时,并不是访问的接口的接口的构造方法(接口本来就没有构造方法),而是访问的是Object
类的构造方法; - 成员方法
- 只能是抽象方法,而且默认
public abstract
修饰;
- 只能是抽象方法,而且默认
注意:类与类之间只能是单继承关系,但是接口与接口之间可以是多继承的关系,即一个接口可以继承自多个接口;
(四)接口与抽象类的区别
- 成员不同
- 抽象类
- 成员变量:可以是变量,也可以是常量;
- 构造方法:有
- 成员方法:可以有抽象方法,也可以有非抽象方法;
- 接口
- 成员变量:只能是常量;
- 构造方法:无
- 成员方法:只能是抽象方法;
- 抽象类
- 关系不同
- 类与类:继承,单继承;
- 类与接口:实现,可以多实现;
- 接口与接口:单继承,多继承均可;
- 设计理念不同
- 抽象类:被继承体现的是
is a
的关系,抽象类中定义的是该继承体系中的共性功能
; - 接口:被实现体现的是
like a
(像什么)的关系,接口中定义的是该继承体系的扩展功能
(个性功能);
- 抽象类:被继承体现的是
十四、内部类
(一)内部类的访问特点
- 内部类可以直接访问外部类的成员,包括
private
修饰的成员; - 外部类要访问内部类的成员,必须创建内部类对象;
(二)成员内部类特点
- 如何直接访问内部类的成员变量,成员方法;
外部类名.内部类名 对象名=外部类对象.内部类对象;
Outer.Innner oi=new Outer().new Inner();
- 内部类用
private
修饰(为了保证数据安全),这时内部类就不能被访问,通常做法是,在外部类中定义一个方法,在方法内部访问内部类(当然有逻辑控制),这样就可以通过外部类来访问内部类; - 当内部类被
static
修饰时(这样做是为了方便数据访问),内部类就不能访问外部类非静态成员,因为静态只能访问静态;成员内部类被
static
修饰时,访问方式为外部类名.内部类名 对象名=new 外部类名.内部类名();
Outer.Inner oi=new Outer.Inner(); - 内部类的方法访问外部类的成员:通过外部类名限定this对象;
Outer.this.method()
、Outer.this.params
;
(三)局部内部类
- 可以直接访问外部类的成员;
- 在局部位置(即方法内部),可以创建内部类对象,通过对象名调用内部类方法,来使用局部内部类的功能;
- 局部内部类访问局部变量时,局部变量必须被
final
修饰;- 因为局部变量随着方法的调用而调用,随着方法的调用结束而消失,而堆内存的内容并不会立即消失,所以通过
final
修饰变成常量,即使局部变量消失了,常量的值还存在,依然可以使用。实际上,通过反编译class
文件可以发现,被final
修饰的局部变量,在内部类中直接是常量,而不是局部变量的变量名。
- 因为局部变量随着方法的调用而调用,随着方法的调用结束而消失,而堆内存的内容并不会立即消失,所以通过