Java中的static讨论

首先

这篇文章是2017年MicroAd圣诞日历第18天的文章。

我今想讨论一下在Java中关于静态(static)的一些冗长的话题,这可能是平时你并不太注意或关注的。如果我们现在不谈论它,以后可能就再也没有机会了。

目标

再小的 static,也是 static,
在写简单的小测验的同时,偷偷整理一下 Java 中的 static 特性,再确认一遍。

静态字段

首先,static这个词在中文中意思是”静态”。所谓”动态”指的是,无论创建多少个实例,类中的这个变量都只有一个。我们可以通过给字段添加static来共享信息和资源,在多个实例之间共享。可以通过类名.字段名来调用,即使通过创建实例调用也可以访问相同的内容。

通过使用final static进行常量定义,可以防止每次创建实例时相同的值被复制到对象中,从而节省内存。

public final static String KEY = "happy-toilet";

这是一个可以在任何地方召唤的Happy Toilet的恒定值。

静态方法(类方法)

静态方法不属于类的实例,而是属于类本身。
可以通过类名.方法名进行调用,如果是函数式接口的方法引用,则可以直接通过类名::方法名进行调用。
静态成员与静态字段一起被称为静态成员。
没有static修饰的一般方法被称为非静态方法或实例方法。

以下是Java8之后,函数式接口方法引用中静态方法和实例方法调用的区别。

public class MethodReference {

  public static void main(String[] args) {
    // static メソッド
    wakeUp(MethodReference::printWakeUp);

    System.out.print(" and ");

    // インスタンスメソッド
    MethodReference mr = new MethodReference();
    mr.goToilet(mr::printGoToilet);
  }

  interface Human {
    void doSomething();
  }

  static void wakeUp(Human human) {
    human.doSomething();
  }

  static void printWakeUp() {
    String wakeUp = "wake up";
    System.out.print(wakeUp);
  }

  void goToilet(Human human) {
    human.doSomething();
  }

  void printGoToilet() {
    String toilet = "happy toilet";
    System.out.print(toilet);
  }
}

醒來並開心地使用廁所的結果。

无法重写

public class StaticPractice {

  public static void main(String[] args) {

    Human developer = new Developer();

    System.out.println(developer.getMorning());

  }
}

class Human {
  static String morning = "Happy Toilet";

  static String getMorning() {
    return morning;
  }
}

class Developer extends Human {
  static String morning = "Happy Coding";

  static String getMorning() {
    return morning;
  }
}

当然的,结果是 Happy Toilet。
将“Developer developer = new Developer();”修改为“一応 Happy Coding 是可以的”。

无法被override,所以我们必须创建一个实例来表示这个概念,并从该实例中调用方法,但归根结底,静态方法本来就是这样的。

 Human.getMorning();
 Developer.getMorning();

在没有经过实例来进行的情况下,最好通过类本身来进行引用。

实际上,将上述代码直接移植到 IntelliJ 中试试看。

alert.png

请注意,被类本身没有引用的静态方法。

(特别感谢 @kawamnv さん)

公共的静态的主方法(String [] args)

当调用主方法时,由于内存中尚不存在实例,所以即使主方法所在的类尚未被实例化,也需要执行。

静态类(静态内部类)

在类中有三种类型的内部类:成员类、局部类和匿名类。
静态成员类是其中的一种,声明位置在类块中(与字段和方法的位置相同)。
然而严格来说,静态成员类很难称为内部类,更正确的表达应该是它是一个完全不同的类。

当我们将包裹内部类的类称为外部类时,外部类与其实例的关系相对较弱,并且可以访问外部类的静态成员。

与其他静态元素一样,可以通过外部类名.成员类名来使用。

class Outer { // 外部クラス

  // メンバクラス➀ static クラス
  static class StaticInner {
    private Integer in;

    public Integer getIn() {
      return in;
    }

    public void setIn(Integer in) {
      this.in = in;
    }
  }

  // メンバクラス➁ 厳密的なメンバクラス
  class Inner {
    private String str;

    public String getStr() {
      return str;
    }

    public void setStr(String str) {
      this.str = str;
    }
  }
}

下面是在无关的类和主方法中调用的方式。

class Execute {
  public static void main(String[] args) {
    // static クラス
    Outer.StaticInner staticInner = new Outer.StaticInner();

    // 厳密的なメンバクラス
    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();
  }
}

我在上述例子中加入了一个例子,严格的成员类与外部类的实例有着强烈的关联,因此如果没有外部类的实例,就无法创建新的实例。由于它不是静态的,所以也可以访问非静态成员。

静态初始化程序

static {
  // 処理
}

静态初始化块是在类加载(加载.class文件)时执行一次的块。
它用于编写在实例化某个类之前或在主方法之前要调用执行的处理。
当然,只能对静态对象进行编写。

我曾经思考过这是否有用途。

public final static String VALUE;

static {
  VALUE = makeValue();
} 

/**
 * 特定条件下で値を動的に生成する
 * @return value 生成した値
 */
private static String makeValue() {
  // omit
}

前几天,我突然有机会尝试了这种用法。
因为我想在实例化之前的早期阶段,创建一个新值并在整个过程中重复使用它,所以正好这样做非常适合。

构造函数和静态初始化器

class Something {

  void printA() {
    System.out.print("t");
  }

  void printB() {
    System.out.print("e");
  }

  Something () {
    System.out.print("l");
    printB();
  }

  static {
    System.out.print("i");
  }
}

class StaticTest {
  static {
    System.out.print("T");
  }

  public static void main(String [] args) {
    System.out.print("o");
    new Something().printA();
  }
}

答案只需要一种选项:最终结果是厕所。
静态初始化程序可以在实例化之前被调用,早于构造函数。

静态导入声明

Java5以后引入了该功能。

以下是前次发布时用于创建约束注释的源代码。

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {CustomSizeValidator.class})
public @interface CustomSize {
  // omit
}

实际上,由于ElementType和RetentionPolicy是具有较长包名的枚举类型,因此我们可以像上面那样简洁地编写代码。

如果是外部类的静态成员,则不一定非要使用枚举,可以使用其他方式进行利用。

import static java.lang.System.*;
import static java.lang.Math.*;

结束了

早上第一个如厕的时间,重新确认基础文法非常重要(小声说)。

以上

广告
将在 10 秒后关闭
bannerAds