深入理解Java中的static关键字:从内存分配到实际应用

欢迎来到我的博客。我是一名新生,他已经在前端教了两年半。我熟悉的技术是JavaScript和Vue。我目前正朝着完整的堆栈迈进。如果我的博客带来帮助,请欢迎您。关注我,我将继续更新文章!!!

深入理解Java中的static关键字:从内存分配到实际应用

文章目录

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。如果我的文章为您带来帮助,欢迎您关注我,我将继续更新更多高质量的文章。您的注意是我的动力! !!

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

28阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

25阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读