天天看点

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏(15)

十五、static 关键字

先来个提纲挈领(唉呀妈呀,成语区博主上线了)吧:

static 关键字可用于变量、方法、代码块和内部类,表示某个特定的成员只属于某个类本身,而不是该类的某个对象。

01、静态变量

静态变量也叫类变量,它属于一个类,而不是这个类的对象。

public class Writer {

   private String name;

   private int age;

   public static int countOfWriters;

   public Writer(String name, int age) {

       this.name = name;

       this.age = age;

       countOfWriters++;

   }

   public String getName() {

       return name;

   public void setName(String name) {

   public int getAge() {

       return age;

   public void setAge(int age) {

}

其中,countOfWriters 被称为静态变量,它有别于 name 和 age 这两个成员变量,因为它前面多了一个修饰符 static。

这意味着无论这个类被初始化多少次,静态变量的值都会在所有类的对象中共享。

Writer w1 = new Writer("沉默王二",18);

Writer w2 = new Writer("沉默王三",16);

System.out.println(Writer.countOfWriters);

按照上面的逻辑,你应该能推理得出,countOfWriters 的值此时应该为 2 而不是 1。从内存的角度来看,静态变量将会存储在 Java 虚拟机中一个名叫“Metaspace”(元空间,Java 8 之后)的特定池中。

静态变量和成员变量有着很大的不同,成员变量的值属于某个对象,不同的对象之间,值是不共享的;但静态变量不是的,它可以用来统计对象的数量,因为它是共享的。就像上面例子中的 countOfWriters,创建一个对象的时候,它的值为 1,创建两个对象的时候,它的值就为 2。

简单小结一下:

1)由于静态变量属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问;

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏(15)

2)不需要初始化类就可以访问静态变量。

public class WriterDemo {

   public static void main(String[] args) {

       System.out.println(Writer.countOfWriters); // 输出 0

02、静态方法

静态方法也叫类方法,它和静态变量类似,属于一个类,而不是这个类的对象。

public static void setCountOfWriters(int countOfWriters) {

   Writer.countOfWriters = countOfWriters;

setCountOfWriters() 就是一个静态方法,它由 static 关键字修饰。

如果你用过 java.lang.Math 类或者 Apache 的一些工具类(比如说 StringUtils)的话,对静态方法一定不会感动陌生。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏(15)

Math 类的几乎所有方法都是静态的,可以直接通过类名来调用,不需要创建类的对象。

怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏(15)

1)Java 中的静态方法在编译时解析,因为静态方法不能被重写(方法重写发生在运行时阶段,为了多态)。

2)抽象方法不能是静态的。

3)静态方法不能使用 this 和 super 关键字。

4)成员方法可以直接访问其他成员方法和成员变量。

5)成员方法也可以直接方法静态方法和静态变量。

6)静态方法可以访问所有其他静态方法和静态变量。

7)静态方法无法直接访问成员方法和成员变量。

03、静态代码块

静态代码块可以用来初始化静态变量,尽管静态方法也可以在声明的时候直接初始化,但有些时候,我们需要多行代码来完成初始化。

public class StaticBlockDemo {
    public static List<String> writes = new ArrayList<>();
    static {
        writes.add("沉默王二");
        writes.add("沉默王三");
        writes.add("沉默王四");
        System.out.println("第一块");
    }
    static {
        writes.add("沉默王五");
        writes.add("沉默王六");
        System.out.println("第二块");
    }
}      

writes 是一个静态的 ArrayList,所以不太可能在声明的时候完成初始化,因此需要在静态代码块中完成初始化。

1)一个类可以有多个静态代码块。

2)静态代码块的解析和执行顺序和它在类中的位置保持一致。为了验证这个结论,可以在 StaticBlockDemo 类中加入空的 main 方法,执行完的结果如下所示:

第一块

第二块

04、静态内部类

Java 允许我们在一个类中声明一个内部类,它提供了一种令人信服的方式,允许我们只在一个地方使用一些变量,使代码更具有条理性和可读性。

常见的内部类有四种,成员内部类、局部内部类、匿名内部类和静态内部类,限于篇幅原因,前三种不在我们本次文章的讨论范围,以后有机会再细说。

public class Singleton {

   private Singleton() {}

   private static class SingletonHolder {

       public static final Singleton instance = new Singleton();

   public static Singleton getInstance() {

       return SingletonHolder.instance;

以上这段代码是不是特别熟悉,对,这就是创建单例的一种方式,第一次加载 Singleton 类时并不会初始化 instance,只有第一次调用 getInstance() 方法时 Java 虚拟机才开始加载 SingletonHolder 并初始化 instance,这样不仅能确保线程安全也能保证 Singleton 类的唯一性。不过,创建单例更优雅的一种方式是使用枚举。

1)静态内部类不能访问外部类的所有成员变量。

2)静态内部类可以访问外部类的所有静态变量,包括私有静态变量。

3)外部类不能声明为 static。

十六、Java 枚举

开门见山地说吧,enum(枚举)是 Java 1.5 时引入的关键字,它表示一种特殊类型的类,默认继承自 java.lang.Enum。

为了证明这一点,我们来新建一个枚举 PlayerType:

public enum PlayerType {
    TENNIS,
    FOOTBALL,
    BASKETBALL
}      

两个关键字带一个类名,还有大括号,以及三个大写的单词,但没看到继承 Enum 类啊?别着急,心急吃不了热豆腐啊。使用 JAD 查看一下反编译后的字节码,就一清二楚了。

public final class PlayerType extends Enum
{
    public static PlayerType[] values()
    {
        return (PlayerType[])$VALUES.clone();
    }
    public static PlayerType valueOf(String name)
    {
        return (PlayerType)Enum.valueOf(com/cmower/baeldung/enum1/PlayerType, name);
    }
    private PlayerType(String s, int i)
    {
        super(s, i);
    }
    public static final PlayerType TENNIS;
    public static final PlayerType FOOTBALL;
    public static final PlayerType BASKETBALL;
    private static final PlayerType $VALUES[];
    static 
    {
        TENNIS = new PlayerType("TENNIS", 0);
        FOOTBALL = new PlayerType("FOOTBALL", 1);
        BASKETBALL = new PlayerType("BASKETBALL", 2);
        $VALUES = (new PlayerType[] {
            TENNIS, FOOTBALL, BASKETBALL
        });
    }
}      

看到没?PlayerType 类是 final 的,并且继承自 Enum 类。这些工作我们程序员没做,编译器帮我们悄悄地做了。此外,它还附带几个有用静态方法,比如说 values() 和 valueOf(String name)。

01、内部枚举

好的,小伙伴们应该已经清楚枚举长什么样子了吧?既然枚举是一种特殊的类,那它其实是可以定义在一个类的内部的,这样它的作用域就可以限定于这个外部类中使用。

public class Player {
    private PlayerType type;
    public enum PlayerType {
        TENNIS,
        FOOTBALL,
        BASKETBALL
    }
   
    public boolean isBasketballPlayer() {
      return getType() == PlayerType.BASKETBALL;
    }
    public PlayerType getType() {
        return type;
    }
    public void setType(PlayerType type) {
        this.type = type;
    }
}      

PlayerType 就相当于 Player 的内部类,isBasketballPlayer() 方法用来判断运动员是否是一个篮球运动员。

由于枚举是 final 的,可以确保在 Java 虚拟机中仅有一个常量对象(可以参照反编译后的静态代码块「static 关键字带大括号的那部分代码」),所以我们可以很安全地使用“==”运算符来比较两个枚举是否相等,参照 isBasketballPlayer() 方法。

那为什么不使用 equals() 方法判断呢?

if(player.getType().equals(Player.PlayerType.BASKETBALL)){};

if(player.getType() == Player.PlayerType.BASKETBALL){};

1

2

“==”运算符比较的时候,如果两个对象都为 null,并不会发生 NullPointerException,而 equals() 方法则会。

另外, “==”运算符会在编译时进行检查,如果两侧的类型不匹配,会提示错误,而 equals() 方法则不会。