该篇文章为对java中static关键字的解读思考和梳理,很幸运见证了你今日的努力。
目录
1.简介
在Java中,static
关键字是一个非常重要的关键字,它用于定义类的静态成员。静态成员属于类本身,而不是类的任何特定实例。这意味着静态成员在类加载到JVM时被创建,并且在类的所有实例之间共享。静态成员包括静态变量(也称为类变量)、静态方法、静态初始化块和静态内部类。
2.静态变量
1.从属于类,静态变量(包括静态方法中的局部变量)在类首次加载到JVM时初始化,且只初始化一次
,与类的声明周期相同,在整个应用程序执行期间都有效。与之比较,普通变量从属于对象,一般在对象创建的时候初始化。
2.所有实例共享同一个静态变量的值。
3.可通过类名直接访问,无法通过this关键字调用。
4.static成员变量初始化顺序按照定义的顺序来进行初始化.
3.静态方法
1.从属于类的方法,不属于类的任何特定实例,通过类名直接调用。
2.静态方法不能调用非静态成员,编译会报错。
因为在类加载时,只处理与类相关的初始化,编辑器无法获取到非成员变量,非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。因此,静态方法无法通过this关键字调用。
3.静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量
。先编译,后运行,才可对象操作。
4.静态方法可以访问静态变量和调用其他静态方法。
4.静态代码块
1.用于类的初始化,初始化静态变量(构造方法用于对象的初始化)。
2.在类加载时执行一次,且在静态变量初始化之前执行
。
3.静态初始化块可以有多个,它们按照在类中出现的顺序执行。
4.很多时候会将一些只需要进行一次的初始化操作都放在static代码块中
进行。
5.需要注意的点
(1)静态方法可以被继承吗?
1.子类可以继承父类的静态方法,但不能覆盖它们。
如果子类定义了 一个与父类同名的静态方法,这将创建一个新的静态方法,而不是覆盖父类的方法。 因此,通常避免在子类中隐藏父类的静态方法,如需要改变父类静态代码的行为,考虑使用不同方法名或其他设计模式。
2.当一个类继承另一个类时,它会继承父类的所有非私有成员,包括静态方法,这意味着子类可以访问和使用继承来的静态方法,就像它们自己类的一部分一样,但是子类不能覆盖(override)父类的静态方法,因为override用于对象实例的继承。
(2)静态方法会有多线程问题吗?
1.静态方法本身不会直接导致多线程问题。
2.使用static关键字,需要考虑线程安全问题,因为静态成员是共享的,多个线程可能同时访问和修改它们。
在多线程环境下,可能需要使用同步机制来确保线程安全。此外,过渡使用静态成员可能导致类的耦合度增加,降低代码的可测试型和可维护性,因此需要根据实际需求合理使用static关键字。
3.可以使用volatile、synchronized、final关键字,lock,ThreadLocal避免线程安全问题,选择使用哪种取决于具体的应用场景和需求。
(3)静态方法除了类加载的时候初始化,之后还会执行吗?
静态方法本身不会在类加载后自动执行,除非它们被显式调用
。
(4)static能作用于局部变量吗?
static是不允许用来修饰局部变量。
6.面试题
1、下面这段代码的输出结果是什么?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
输出结果:
base static
test static
base constructor
test constructor
1.找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
2.加载Test类的时候,发现Test类继承Base类,于是先去加载Base类
3.加载Base类的时候,发现Base类有static块,而是先执行static块,输出base static结果
4.Base类加载完成后,再去加载Test类,发现Test类也有static块,而是执行Test类中的static块,输出test static结果
5.Base类和Test类加载完成后,然后执行main方法中的new Test(),调用子类构造器之前会先调用父类构造器
6.调用父类构造器,输出base constructor结果
7.然后再调用子类构造器,输出test constructor结果
2、这段代码的输出结果是什么?
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
输出结果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
(1)找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
(2)加载Test类的时候,发现Test类有static块,而是先执行static块,输出test static结果
(3)然后执行new MyClass(),执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,而是要先加载Test类,Test类之前已加载
(4)加载MyClass类,发现MyClass类有static块,而是先执行static块,输出myclass static结果
(5)然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,而是执行Person person = new Person(“Test”)代码,发现Person类没有加载
(6)加载Person类,发现Person类有static块,而是先执行static块,输出person static结果
(7)接着执行Person构造器,输出person Test结果
(8)然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了
(9)再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果
(10)最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了
3、这段代码的输出结果是什么?
public class Test {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}
输出结果:
test static 1
test static 2
虽然在main方法中没有任何语句,但是还是会输出,原因是输出语句是类加载执行的。另外,static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。