深入理解Java中的static关键字:从内存分配到实际应用
欢迎来到我的博客。我是一名新生,他已经在前端教了两年半。我熟悉的技术是JavaScript和Vue。我目前正朝着完整的堆栈迈进。如果我的博客带来帮助,请欢迎您。关注我,我将继续更新文章!!!

文章目录
1。什么是静态关键字
在Java中,静态表示“全局”或“静态”,用于修改成员变量和成员方法,当然,它也可以修改代码块。
Java将内存分为堆栈内存和堆内存,其中堆栈内存用于存储一些基本类型的变量,数组和对象的类型,而堆存储器主要存储一些对象。当JVM加载类时,如果有静态修改的成员变量和成员方法,将为这些成员变量和成员方法打开一个固定大小的内存区域(只要加载此类,机器可以根据班级名称在运行时数据区域的方法区域中找到它们。有了这些“固定”功能,JVM可以很容易地访问它们。
静态修改的成员变量和成员方法与类无关。它们不依赖特定的实例变量,也就是说,它们都由班级的所有实例共享。对所有实例的引用指向同一位置,任何实例对其的修改都会导致其他实例的更改。
public class User {
private static int userNumber = 0 ;
public User(){
userNumber ++;
}
public static void main(String[] args) {
User user1 = new User();
User user2 = new User();
System.out.println("user1 userNumber:" + User.userNumber);
System.out.println("user2 userNumber:" + User.userNumber);
}
}
输出:
user1 userNumber:2
user2 userNumber:2
2。静态关键字的使用2.1静态变量
静态变量也称为静态变量。静态变量和非静态变量之间的差异在于,所有对象都共享静态变量,只有一个副本在内存中,仅在第一次加载类时,才会在内存初始化。非静态变量归对象所有,并在创建对象时初始化。有多个副本,每个对象拥有的副本不会互相影响。
静态成员变量的初始化顺序以定义顺序初始化。
因此,我们通常在这两种情况下使用静态变量:在对象之间共享数据和易于访问。
public class TestStatic {
public static int count = 0;
public static void main(String[] args){
TestStatic test1=new TestStatic();
System.out.println(test1.count);
TestStatic test2=new TestStatic();
test2.count++;
System.out.println(test1.count+" "+test2.count+" "+TestStatic.count);
}
}
输出结果:
0
1 1 1
可以看出,静态变量不是类的特定对象,而是由类的所有对象共享。静态变量可以通过对象调用或直接由类直接使用。
2.2静态方法
静态方法通常称为静态方法。由于可以在不依赖任何对象的情况下访问静态方法,因此对于静态方法没有任何对象,因为它不取决于任何对象。由于没有对象,因此无法讨论。我去过这个。由于此功能,无法以静态方法访问类的非静态成员变量和类的非静态成员方法,因为非静态成员方法/变量必须依靠要调用的特定对象。
但是应该注意的是,尽管无法以静态方法访问非静态成员方法和非静态成员变量,但可以在非静态成员方法中访问静态成员方法/变量。
因为静态方法独立于任何实例,因此必须实现静态方法,而不是摘要。
总而言之,应注意以下几点静态方法:
(1)他们只能调用其他静态方法。
(2)他们只能访问静态数据。
(3)他们无法以任何方式指代它或超级。
让我们举一个简单的例子:
在上面的代码中,由于print2方法独立于对象,因此可以通过类名称直接调用。如果可以以静态方法访问非静态方法/变量,则在主要方法中有以下语句:
myObject.print2();
目前,没有对象,str2根本不存在,因此会有矛盾。方法也是如此。由于您无法预测在Print1方法中是否访问了非静态成员变量,因此还禁止在静态成员方法中访问非静态成员方法。
对于非静态成员方法,访问静态成员方法/变量显然是无限的。
因此,如果您想在不创建对象的情况下调用方法,则可以将此方法设置为静态。我们最常见的静态方法是主要方法。至于为什么主要方法必须静态,现在很清楚。因为执行主方法时程序不会创建任何对象,因此只能通过类名访问它。
2.3静态代码块
静态关键字的另一个关键功能是形成静态代码块以优化程序性能。可以将静态块放置在班上的任何地方,并且可以在同类中有多个静态块。首先加载类时,每个静态块将按静态块的顺序执行,并且只能执行一次。
可以使用静态块来优化程序性能的原因是由于其特征:只有在加载类时才能执行一次。这是一个示例:
class Person{
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
Isbornboomer习惯于该人是否出生于1946 - 1964年。每次称为Isbornboomer时,都会生成两个对象,这会导致空间浪费。如果更改为此,它将更好:
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}
因此,很多时候,只需要将某些初始化操作放置在静态代码块中。
3。关于静态关键字3.1的误解。静态关键字会更改班级成员的访问权限吗?
一些初学者会将Java中的静态与C/C ++中的静态关键字函数混淆。请在这里记住一件事:与C/C ++中的静态不同,Java中的静态关键字不会影响变量或方法的范围。在Java中,唯一可能影响访问权限的关键字是私人,公共和受保护的关键字(包括软件包访问权限)。查看以下示例以了解:
及时错误“年龄是私人”,这意味着静态关键字不会更改变量和方法的访问权限。
3.2可以通过此访问静态成员变量吗?
尽管对于静态方法没有,但可以通过非静态方法访问静态成员变量吗?让我们首先查看以下示例。此代码输出的结果是什么?
public class Main {
static int value = 33;
public static void main(String[] args) throws Exception{
new Main().printValue();
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
}
输出结果:33
这主要是理解这一点和静态的主要观察团队。这代表什么?这代表当前对象。如果通过new Main()调用printValue,则当前对象是由new Main()生成的对象。对象享受静态变量,因此printValue中的值无疑是33。printValue方法内部的值是局部变量,根本不能与之相关联,因此输出是33。这里的事情:尽管静态成员变量独立于对象,但它们并不意味着无法通过对象访问它们。所有静态方法和静态变量都可以通过对象访问(只要访问权限就足够)。
3.3静态可以对局部变量作用吗?
在C/C ++中,静态可以范围范围局部变量,但请记住Java:不允许使用静态来修改局部变量。不要问为什么,这是Java语法的规定。
3.4静态和最终意味着什么?
静态最终最终用于修改成员变量和成员方法,可以简单地理解为“全局常数”!
对于变量,一旦给出值,就无法修改表示形式,并且可以通过类名来访问。
对于方法,表示表示不被覆盖,并且可以直接通过班级名称访问。
4。常见的书面访谈问题4.1问题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
在此代码的特定执行过程中,您必须在执行开始时首先找到主要方法,因为主要方法是程序的入口,但是在执行主方法之前,您必须首先加载测试类,以及加载测试类,您会发现从基类继承的测试类,因此它将首先加载基类。加载基类时,发现有一个静态块,因此执行静态块。加载基类后,测试类继续加载,然后发现测试类中有静态块,因此执行了静态块。加载所需类后,主方法开始执行。在主要方法中执行新测试()时,将首先调用父类的构造函数,然后称为其自己的构造函数。因此,出现上述输出结果。
4.2问题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
此代码的特定执行过程:首先加载测试类,因此将执行测试类中的静态块。然后执行新的myClass(),并且尚未加载myClass类,因此需要加载myClass类。加载MyClass类时,发现MyClass类从测试类继承。但是,由于已经加载了测试类,因此仅需要加载myClass类,并且将执行MyClass类中的静态块。加载后,对象是通过构造函数生成的。生成对象时,必须先初始化父类的成员变量,因此将执行Person Person Person = New Person()测试。尚未加载人类的班级,因此将首先加载人类课程,并将在同类课程中执行人类班级。静态块的块,然后执行父类构造函数,完成父类的初始化,然后初始化自身。因此,将执行MyClass中的Person Person = New Person(),最后将执行MyClass构造函数。
4.3问题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
尽管主要方法中没有陈述,但它们仍将输出,因为上面已经解释了原因。此外,静态块可以出现在类中的任何位置(只要它不在方法内,请记住,没有方法可以执行),并且执行以静态块的顺序执行。
5。静态代码块的初始化顺序
class Parent {
static String name = "hello";
{
System.out.println("parent block");
}
static {
System.out.println("parent static block");
}
public Parent() {
System.out.println("parent constructor");
}
}
class Child extends Parent {
static String childName = "hello";
{
System.out.println("child block");
}
static {
System.out.println("child static block");
}
public Child() {
System.out.println("child constructor");
}
}
public class TestStatic {
public static void main(String[] args) {
new Child();// 语句(*)
}
}
输出结果:
parent static block
child static block
parent block
parent constructor
child block
child constructor
分析:执行新孩子()时,它首先检查父类中是否有静态代码块。如果是这样,它首先在父类中执行静态代码块的内容,以及执行完成后父类中静态代码块的内容时,然后在子类中执行静态代码块(其自身类) )。当子类的静态代码块完成后,它将检查父类中是否有非静态代码块。如果有,请执行父。类的非静态代码块,执行父类的非静态代码块,然后执行父类的构造方法;执行父类的施工方法之后,然后检查子类中是否有非静态代码块,如果是这样执行了子类的非静态代码块。执行子类的非静态代码块后,将执行子类构建方法。这是对象的初始化顺序。
总结
对象的初始化顺序:首先,执行父类的静态内容。执行父类的静态内容后,然后执行子类的静态内容。在执行子类的静态内容之后,请检查父类是否不是静态代码块,如果是父类的非静态代码块,则执行父类的非静态代码块,并且然后执行父类的施工方法;执行父类的构建方法后,如果有非静态代码块执行子类,则该子类没有非静态代码块。在执行子类执行后执行子类的非静态代码块。
简而言之,首先执行静态代码块的内容,然后执行父类非静态代码块和构造方法方法,然后执行子类非静态代码块和构造方法方法。
注意
子类的构造函数。无论该构造函数是否采用参数,它都会首先查找没有参数的父类的构造函数。如果父类没有没有参数的构造函数,则子类必须使用Supper Key Child用带有参数的父类调用构造函数,否则无法传递汇编。
A.静态关键字用法方案1。静态变量
将变量声明为静态变量通常基于以下三个目的:
第一个更容易理解。由于内存中只有一个副本,因此可以用作共享变量,例如定义全局配置和执行全局计数。喜欢:
public class CarConstants {
// 全局配置,一般全局配置会和final一起配合使用, 作为共享变量
public static final int MAX_CAR_NUM = 10000;
}
public class CarFactory {
// 计数器
private static int createCarNum = 0;
public static Car createCar() {
if (createCarNum > CarConstants.MAX_CAR_NUM) {
throw new RuntimeException("超出最大可生产数量");
}
Car c = new Car();
createCarNum++;
return c;
}
public static getCreateCarNum() {
return createCarNum;
}
}
尽管第二种类型的情况不多,但基本上是在每个项目中都使用的,例如声明Logger变量:
private static final Logger LOGGER = LogFactory.getLoggger(MyClass.class);
实际上,删除静态也是可行的,例如:
private final Logger LOGGER = LogFactory.getLoggger(MyClass.class);
这样,每个MyClass实例化对象都会有一个记录器。如果创建了1000个MyClass对象,将会有1000个其他记录器对象,从而浪费资源。因此,Logger对象通常被声明为静态变量。这样,可以减少内存资源的使用。
第三大最经典的场景是Singleton模式。由于Singleton模式只能保留全球一个副本,因此自然和静态的原始意图是一致的,并且最适合使用静态来修改它是最合适的。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
2。静态方法
将方法声明为静态方法,通常是为了促进呼叫而无需创建对象。这种用法方法非常普遍,例如JDK的集合类中的某些方法,singleton模式的GetInstance方法,创建/构建出厂模式的方法以及UTIL Tool类中的方法。
3。静态代码块
静态代码块通常用于对静态变量进行一些初始化操作,例如单例模式和定义枚举类:
4。静态内类
通常,内部课程并不经常使用。如果您需要在外部类中定义内部类,则通常会根据外部类和内部类具有牢固关系的前提来使用它。
在谈论静态内部类的使用之前,让我们首先看一下静态内部类和非静态内类之间的区别:
非静态内部类对象保留对外部类对象的引用(编译器隐式将外部类对象引用为内部类的构造函数参数);虽然静态内部类对象不保留对外部类对象的引用
由于非静态内类的实例创建需要引用外部类对象,因此非静态内类对象的创建必须依赖于外部类的实例。而实例创建静态内类仅需要依靠外部类;
而且由于非静态内类对象具有对外部类对象的引用,因此非静态内类可以访问外部类的非静态成员。虽然静态内类只能访问外部类的静态成员;
两者之间的基本差异实际上决定了使用静态修改内部类的真正意图:
首先是在情况下更频繁地使用静态内部类的情况,例如JDK集合中的条目和建筑商设计模式。
hashmap条目:
建筑商设计模式:
public class Person {
private String name;
private int age;
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}
public static class Builder {
private String name;
private int age;
public Builder() {
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age=age;
return this;
}
public Person build() {
return new Person(this);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 在需要创建Person对象的时候
Person person = new Person.Builder().name("张三").age(17).build();
第二种情况通常发生在多线程的情况下。非静态内部类可能会导致内存溢出问题,例如以下示例:
public class Task {
public void onCreate() {
// 匿名内部类, 会持有Task实例的引用
new Thread() {
public void run() {
//...耗时操作
};
}.start();
}
}
在上述代码中:
new Thread() {
public void run() {
//...耗时操作
};
}.start();
声明并创建一个匿名的内部类对象,该对象具有对外部类任务实例的引用。如果在运行方法中完成了耗时的操作,则很长一段时间内将不会回收外部类任务的实例。如果任务太多,对象会导致内存溢出。
优化方法:
public class Task {
public void onCreate() {
SubTask subTask = new SubTask();
subTask.start();
}
static class SubTask extends Thread {
@Override
public void run() {
//...耗时操作
}
}
}
5。静态导入
静态导入实际上是导入静态的,用于在特定类或软件包中导入静态方法或静态变量。如以下代码所示:
import static java.lang.Math.PI;
public class MathUtils {
public static double calCircleArea(double r) {
// 可以直接用 Math类中的静态变量PI
return PI * r * r;
}
}
这样,您可以在编写代码时节省一些代码,但是它将影响代码的可读性,因此不建议以这种方式使用它。
B.静态变量和普通成员变量之间的差异
静态变量和普通成员变量之间有四个主要区别:
C.类构造函数是静态方法吗?
关于类构造函数是否是静态方法,存在许多争议。在“ Java编程思想”一书中,提到“尽管没有静态构造器修改,但实际上是一种静态方法。”我个人认为这一说法有点缺乏。好的,原因如下:
1)在类构造函数中,实际上有一个隐藏的参数此引用,该参数绑定到对象。也就是说,只有在对象被创建之前,才能在调用构造函数之前出现此引用。构造函数做什么?它负责创建实例对象时初始化实例。也就是说,在JVM分配了堆上实例对象的相应存储空间之后,它需要调用构造函数以初始化和分配实例对象的成员变量。
2)让我们再次查看静态方法。由于静态不依赖任何对象访问它,也就是说,它与此对象没有关联。从这个角度来看,班级的构造函数不是静态方法
3)从JVM指令级别,类构造函数不是静态方法。让我们首先查看以下代码:
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public static void create() {
}
}
public class Main {
public static void main(String[] args) {
Person.create();
Person p = new Person("Jack");
}
}
分解此代码后的字节码如下:
从上面可以看出,在调用静态方法时,调用了InvoKestatic指令,当调用类构造函数时,实际执行了InvokeSpecial指令。 JVM规范中这两个说明的解释如下:
#jvms-6.5.invokestatic
可以看出,这两个说明的用途是完全不同的。创动的定义非常明确,用于调用和执行静态方法。 InvokeSpecial用于调用实例方法,用于专门调用父类方法,私人方法和类构建。用具。
嗨,这是Ruiyuxi的一名大学生,喜欢JavaScript和Vue。如果我的文章为您带来帮助,欢迎您关注我,我将继续更新更多高质量的文章。您的注意是我的动力! !!