(九)static和fianl
静态(static)和最终(final)
静态(static)
static本身是一个修饰符,可以修饰变量、方法、内部类、代码块。
静态是从类的层面来看的,已经超越了对象。静态是依附于类的存在而存在,所以静态修饰的变量、方法、代码块也可以叫类变量、类方法、类代码块。
1、特点
标记一个方法为static,意味着这个方法被所在类的所有实例公用,在类装载时初始化,被所有该类的实例共享,同时意味着:
1.static方法内部不能引用/声明static变量。
2.static方法不能被子类重写为非static方法。
3.父类的非static方法不能被子类重写为static方法。
4.static代码块可以用static{}来完成,在类被第一次装载时执行初始化,先于静态方法和其他方法的执行。
2、静态内存流程
图片详解:
程序启动的时候先通过类加载器(ClassLoader)将基本核心类库(java.long包)加载到方法区,其次是主函数所在的类加载到方法区。
加载主函数所在的类。首先将Java源文件编译为class文件,然后class文件通过类加载器(ClassLoader)将class文件加载到方法区当中。将StaticDemo1.class存放到方法区中,将主函数main存放到方法区的静态区中。
当执行语句Person p1 = new Person(); 时,会将Person类的java源文件编译成class文件,然后将class文件通过类加载器(ClassLoader)将class文件加载到方法区中。在静态常量池中开辟空间,将Person.class以及其包含的内容存放在静态常量池,然后在静态区中开辟空间,将Person.class中的静态属性以及静态方法存放到静态区中,并给静态属性变量赋予默认值(gender = ‘\u0000’)和方法区空间地址(0x02da)。以上工作完成之后,会在栈内存中声明一个对象p1。然后执行new Person,在堆内存中开辟空间存储p1对象的一系列属性(name,age,gender),并赋予默认值(name = null;age=0;gender = 0x02da;因为gender为静态变量所以gender的默认值是方法区中对应静态变量的空间地址)和堆内存空间地址(0x6f8d),然后将堆空间地址赋值给栈内存中对应的对象。
当执行p1.name = “张无忌”;时,对象p1会沿着p1对象中存储的堆内存空间地址找到自己的属性,给自己的name属性赋值为:张无忌,将默认值覆盖。p1.age = 81;也是如此。
当执行p1.gender = ‘男’;时,对象p1会沿着p1对象中存储的堆内存空间地址找到自己的属性gender,然后沿着gender中存储的方法区空间地址找到方法区中静态区里的gender,然后给静态区里的gender赋值为:男。
当执行语句Person p2 = new Person();时,因为所有的类已经加载过了,所以可以直接进行声明和初始化操作。栈内存和堆内存中的操作和语句Person p1 = new Person();是一样的。
当执行语句p2.name = “赵敏”; p2.age = 18 ; 过程和p1.name = “张无忌”; p1.age = 81 ; 是一样的。
当执行p2.gender = ‘女’;时,对象p2会沿着p2对象中存储的堆空间地址找到堆内存中p2对应的gender属性,然后在根据gender属性中存放的地址找到方法区中静态区里的gender变量并赋值为:女,将原来的“男”值替换掉。由于p1和p2中gender属性存储的方法区空间地址是一样的,所以我们可以认为p1.gender = p2.gender = ‘女’;。
当执行语句p1.eat();时,p1对象会调用方法区中静态常量池里Person.class的eat();方法,Person.class会给出eat()方法的地址,然后入栈,在栈内存中开辟空间执行eat();方法,eat();方法中会自动产生this关键字代表p1对象,此时this 中存储的使p1中存储的堆内存空间地址,然后输出方法的结果。
3、静态/类变量(static variable)
1.定义
static修饰变量那么这个变量我们就称之为静态变量,又称为成员变量或者类变量。
2.特点
静态变量随着类的加载而加载到了方法区中的静态区,并且在静态区中自动赋予了一个默认值。静态变量先于对象而存在,所以静态变量可以通过类名来调用,也可以通过对象来调用。该类产生的所有的对象实际上存的是该静态变量在静态区中的地址,静态变量是被所有对象所共享的。
例如:System.in; System.out; 都是静态变量。
1.静态变量有默认值。
2.静态变量可以被类调用,也可以被对象调用。
3.静态变量是被所有对象共享的。
4.静态变量可以使用this来调用。
5.静态变量不能定义到构造代码块中,但可以在构造代码块中对静态变量赋值。
6.静态变量不能定义到函数/方法中。静态变量在加载的时候就需要分配空间,而方法在加载的时候不需要空间,在执行的时候才需要内存空间。
注意:实际开发中,static能少用就少用。
3.静态变量与实际变量的区别
1.在语法定义上的区别。静态变量前要加static关键字,而实例变量前则不加。
2.在程序运行时的区别。实例变量属于某个对象,必须创建对象才能使用。静态变量则可以直接使用类名引用。
4、静态/类方法(static method)
1.定义
static修饰方法就叫静态方法,也叫类方法。
之前我们接触过的Arrays.sort()、Arrays.toString()、Arrays.copyOf()、System.arraycopy()都是静态方法。
2.特点
在类加载的时候加载到了方法区中的静态区,只是存储在静态区,在方法被调用的时候到栈内存中执行。静态区中的元素不归属于某一个对象而是归属于类。静态方法先于对象而存在的,所以静态方法可以通过类名来调用,也可以通过对象来调用。
1.静态方法中不能定义静态变量,但可以对静态变量赋值。
2.不能在静态方法中直接调用本类中的非静态方法。可以通过创建对象来调用。所有非静态的方法和变量都需要对象来调用。
3.静态方法中不能使用this或者super。(主函数是静态方法)
4.静态方法可以重载。
5.静态方法不可以重写。但父子类中可以出现方法签名一致的静态方法,比如:main主函数。
6.静态方法可以被继承。
父子类中可以存在方法签名一致的静态方法。如果父子类中出现了两个方法签名的一致的方法要么全是静态,要么全是非静态。
静态不属于对象。而是属于类。不依赖某个对象而存在,而是依赖于类存在的。类没有多态,多态针对的是方法。
注意:类只加载一次,是在第一次使用的时候才加载到方法区,而且加载到方法区中之后就不再移除了。
5、静态/类代码块(static code block)
1.定义
在类中成员的位置用static修饰用{}括起来的代码块。
静态/类代码块针对的是类。
实际上静态代码块使随着类的加载而加载到静态区,在类创建对象或者执行方法之前执行一次,终其一生只执行一次。
2.作用
上面也提到了,静态代码块只在类执行的时候执行一次,所以静态代码块一般是对类进行初始化。
3.执行顺序
在这个类第一次被真正使用(第一次创建对象/调用方法)的时候执行一次。如果一个类包含多个静态/类代码块,则按照书写顺序执行。由于类只在第一次使用的时候加载,所以静态代码块也只执行一次。
代码执行顺序:先父类后子类,先静态后动态。(先父子类的静态,后父子类的动态)静态优先,父类优先。
代码是从上到下,从左到右依次编译执行。
创建子类对象的时候需要先创建父类对象->加载父类->执行父类静态代码块->执行子类代码块->父类构造代码块->父类构造函数->子类构造代码块->子类构造函数
其实随着学习的深入,对静态的了解也更深入,可以说,所有的静态都和类的成员属性是平级的,但这个属性有些特殊,不能嵌套到别的成员里面声明,但可以被调用。
final(最终的)
final可以修饰数据,方法以及类。
1、常量(final variable)
1.定义
当final修饰数据(基本类型和引用类型)的时候,表示这个变量的值不可变,称之为常量。终其一生只能赋值一次。
在Java中所说的常量往往是指静态常量。因为实质上只有静态常量才是独有的一个。
我们之前接触过的System.in/out,in和out都是静态常量。NaN和infinity也是静态常量。arr.length,length是个常量。
2.特点
1.常量在定义好之后不可改变。
我自己的理解final固定的其实是栈内存中的数值。比如:就相当于常量在栈中的空间被锁死,只允许一次进入。这一次进入不确定是在什么时候。
2.常量可以作为参数传递。
3.对引用类型而言,final固定的是其在栈中的地址不可变。
数组在栈内存中存储的是地址,用final修饰,是不能改变数组的地址,但数组的值可以改变。
4.成员常量只要是在对象创建完成之前(构造方法/函数执行结束之前)赋初始值即可。
5.静态成员常量(static final)只要在类加载完成之前给值即可,而且只能在静态代码块中赋值。
public class Demo {
//定义一个常量
private final int i;
//定义一个静态常量
private fianl static int x;
//初始化静态常量
static{
x=3;
}
//在构造函数中初始化常量
public Demo(){
this(5);
}
public Demo(int i){
this.i = i;
}
pbulic static void m(int i){
int y = i;
}
public static void main(String[] args){
Demo d = new Demo(5);//对象创建的时候赋值
m(t.i);//作为参数传递
m(x);//作为参数传递
}
}
2、最终方法(final method)
1.定义
final修饰方法的时候,这个方法就是最终方法。
2.特点
1.最终方法不可以被重写,可以重载,可以被继承。
2.静态方法可以被final修饰。
public class Demo{
//最终方法
public final void m(){}
//重载最终方法
public int m(int i){return i;}
//修饰静态并重载
public final static void m(double d){}
}
class A extends Demo{
//此时只能重写m(int i)方法
public int m(int i){
int y = i;
return y;
}
}
3、最终类
1.定义
final修饰类那么这个类就是最终类。
2.特点
1.最终类不可以被继承,没有匿名内部类形式。
2.现阶段最终类的方法不能被重写。
我们之前接触过的System 、String是最终类。一般java中好多工具类都是最终类。
public final class Demo{}