static 是Java的⼀个关键字,可以⽤来修饰成员变量、修饰成员⽅法、构造静态代码块、实现静态导包以及实现静态内部类.

1、修饰成员变量

  ⽤ static 修饰成员变量可以说是该关键字最常⽤的⼀个功能,通常将⽤ static 修饰的成员变量称为类成员或者静态成员,那么静态成员和不⽤ static 修饰的⾮静态成员有什么区别呢?
   不⽤ static 修饰的成员变量在内存中的构造

package com.ys.bean;
/**
* Create by YSOcean
*/
public class Person {
    private String name;
    private Integer age;
    public Person(String name, Integer age) {
                    this.name = name;
                    this.age = age;
 }
 @Override
 public String toString() {
        return "Person{" +
                        "name='" + name + '\'' +
                        ", age=" + age +
                     '}';
 }
 //get和set⽅法省略
}

⾸先,我们创建⼀个实体类 Person,有两个属性 name 和 age,都是普通成员变量(没有⽤ static关键字修饰),接着我们通过其构造⽅法创建两个对象:

Person p1 = new Person("Tom",21);
Person p2 = new Person("Marry",20);
System.out.println(p1.toString());//Person{name='Tom', age=21}
System.out.println(p2.toString());//Person{name='Marry', age=20}

 这两个对象在内存中的存储结构如下:
file

由上图可知,我们创建的两个对象 p1 和 p2 存储在堆中,但是其引⽤地址是存放在栈中的,⽽且这两个对象的两个变量互相独⽴,我们修改任何⼀个对象的属性值,是不改变另外⼀个对象的属性值的。
  下⾯我们将 Person 类中的 age 属性改为由 static 关键字修饰:

package com.ys.bean;
/**
* Create by YSOcean
*/
public class Person {
    private String name;
    private static Integer age;
    public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
 }
 @Override
 public String toString() {
        return "Person{" +
                        "name='" + name + '\'' +
                        ", age=" + age +
                    '}';
 }
 //get和set⽅法省略
}

创建 p1 和 p2 两个对象,并打印这两个对象,看看和上⾯打印的有啥区别呢?

Person p1 = new Person("Tom",21);
Person p2 = new Person("Marry",20);
System.out.println(p1.toString());//Person{name='Tom', age=20}
System.out.println(p2.toString());//Person{name='Marry', age=20}

们发现第三⾏代码打印的 p1 对象 age 属性变为 20了,这是为什么呢?
file

这是因为⽤在 jvm 的内存构造中,会在堆中开辟⼀块内存空间,专⻔⽤来存储⽤ static 修饰的成员变量,称为静态存储区,⽆论我们创建多少个对象,⽤ static 修饰的成员变量有且只有⼀份存储在静态存储区中,所以该静态变量的值是以最后创建对象时设置该静态变量的值为准,也就是由于 p1 先设置age = 21,后来创建了 p2 对象,p2将 age 改为了20,那么该静态存储区的 age 属性值也被修改成了20。
  PS:在 JDK1.8 以前,静态存储区是存放在⽅法区的,⽽⽅法区不属于堆,在 JDK1.8 之后,才将⽅法区⼲掉了,⽅法区中的静态存储区改为到堆中存储。
  总结:static 修饰的变量被所有对象所共享,在内存中只有⼀个副本。由于与对象⽆关,所以我们可以直接通过 类名.静态变量 的⽅式来直接调⽤静态变量。对应的⾮静态变量是对象所拥有的,多少个对象就有多少个⾮静态变量,各个对象所拥有的副本不受影响。

2、修饰修饰成员⽅法

⽤ static 关键字修饰成员⽅法也是⼀样的道理,我们可以直接通过 类名.静态⽅法名() 的⽅式来调⽤,⽽不⽤创建对象。

public class Person {
     private String name;
     private static Integer age;
     public static void printClassName(){
        System.out.println("com.ys.bean.Person");
     }
     public Person(String name, Integer age) {
                        this.name = name;
                        this.age = age;
     }
 @Override
 public String toString() {
         return "Person{" +
                         "name='" + name + '\'' +
                         ", age=" + age +
                        '}';
 }
 //get和set⽅法省略
}

调⽤静态⽅法:

Person.printClassName();//com.ys.bean.Person

3、静态代码块

⽤ static 修饰的代码块称为静态代码块,静态代码块可以置于类的任意⼀个地⽅(和成员变量成员⽅法同等地位,不可放⼊⽅法中),并且⼀个类可以有多个静态代码块,在类初次载⼊内存时加载静态代码块,并且按照声明静态代码块的顺序来加载,且仅加载⼀次,优先于各种代码块以及构造函数。
public class CodeBlock {
 static{
            System.out.println("静态代码块");
 }
}

 由于静态代码块只在类载⼊内存时加载⼀次的特性,我们可以利⽤静态代码块来优化程序性能,⽐如某个⽐较⼤配置⽂件需要在创建对象时加载,这时候为了节省内存,我们可以将该配置⽂件的加载时机放⼊到静态代码块中,那么我们⽆论创建多少个对象时,该配置⽂件也只加载了⼀次。

4、静态导包

静态导包是 JDK1.5以后的新特性,⽤ import static 包名 来代替传统的 import 包名 ⽅式。

我们创建⼀个数组,然后⽤ JDK ⾃带的 Arrays ⼯具类的 sort ⽅法来对数组进⾏排序:

package com.ys.test;
import java.util.Arrays;
/**
* Create by YSOcean
*/
public class StaticTest {
    public static void main(String[] args) {
    int[] arrays = {3,4,2,8,1,9};
    Arrays.sort(arrays);
 }
}

调⽤ sort ⽅法时,需要进⾏ import java.util.Arrays 的导包操作,那么变为静态导包呢?

package com.ys.test;
import static java.util.Arrays.*;
/**
* Create by YSOcean
*/
public class StaticTest {
 public static void main(String[] args) {
 int[] arrays = {3,4,2,8,1,9};
 sort(arrays);
 }
}

可以看到第三⾏代码的 import java.util.Arrays 变为了 import static java.util.Arrays.,意思是导⼊ Arrays 类中的所有静态⽅法,当然你也可以将 变为某个⽅法名,也就是只导⼊该⽅法,那么我们在调⽤该⽅法时,就可以不带上类名,直接通过⽅法名来调⽤(第 11 ⾏代码)。
  静态导包只会减少程序员的代码编写量,对于性能是没有任何提升的(也不会降低性能,Java核⼼技术第10版卷1第148⻚4.7.1章节类的导⼊有介绍),反⽽会降低代码的可读性,所以实际如何使⽤需要权衡。

5、静态内部类

  定义在⼀个类的内部的类叫内部类,包含内部类的类叫外部类,内部类⽤ static 修饰便是我们所说的静态内部类。
  定义内部类的好处是外部类可以访问内部类的所有⽅法和属性,包括私有⽅法和私有属性。
  访问普通内部类,我们需要先创建外部类的对象,然后通过外部类名.new 创建内部类的实例。

package com.ys.bean;
/**
* Create by hadoop
*/
public class OutClass {
 public class InnerClass{
 }
}

* OuterClass oc = new OuterClass();
* OuterClass.InnerClass in = oc.new InnerClass();

 访问静态内部类,我们不需要创建外部类的对象,可以直接通过 外部类名.内部类名 来创建实例。

package com.ys.bean;
/**
* Create by hadoop
*/
public class OutClass {
 public static class InnerClass{
 }
}

OuterClass.StaticInnerClass sic = new OuterClass.StaticInnerClass();

6、常⻅问题

  ①、静态变量能存在于普通⽅法中吗?

能。很明显,普通⽅法必须通过对象来调⽤,静态变量都可以直接通过类名来调⽤了,更不⽤说通过对象来调⽤,所以是可以存在于普通⽅法中的。

  ②、静态⽅法能存在普通变量吗?

不能。因为静态⽅法可以直接通过类名来直接调⽤,不⽤创建对象,⽽普通变量是必须通过对象来调⽤的。那么将普通变量放在静态⽅法中,在直接通过类来调⽤静态⽅法时就会报错。所以不能。

  ③、静态代码块能放在⽅法体中吗?

不能。⾸先我们要明确静态代码块是在类加载的时候⾃动运⾏的。  普通⽅法需要我们创建对象,然后⼿⼯去调⽤⽅法,所静态代码块不能声明在普通⽅法中。
那么对于⽤ static 修饰的静态⽅法呢?同样也是不能的。因为静态⽅法同样也需要我们⼿⼯通过类名来调⽤,⽽不是直接在类加载的时候就运⾏了。
也就是说静态代码块能够⾃动执⾏,⽽不管是普通⽅法还是静态⽅法都是需要⼿⼯执⾏的。

  ④、静态导包会⽐普通导包消耗更多的性能?

不会。静态导包实际上在编译期间都会被编译器进⾏处理,将其转换成普通按需导包的形式,所以在程序运⾏期间是不影响性能的。

  ⑤、static 可以⽤来修饰局部变量吗?

不能。不管是在普通⽅法还是在静态⽅法中,static 关键字都不能⽤来修饰局部变量,这是Java的规定。稍微想想也能明⽩,局部变量的声明周期是随着⽅法的结束⽽结束的,因为static 修饰的变量是全局的,不与对象有关的,如果⽤ static 修饰局部变量容易造成理解上的冲突,所以Java规定 static 关键字不能⽤来修饰局部变量。

链接:Java关键字解析

最后修改日期: 2022年2月24日