Java
MicroAdDay 18

Javaのstaticな話

はじめに

この記事はMicroAd Advent Calendar 2017の18日目の記事です。

普段そこまで意識することがないのか、あるのか、
今語らないと今後語ることはないでしょう、java における static のイマサラな話をしてみようと思います。

ゴール

たかが static されど static、
簡単なクイズを書きながら、java における static なモノをこそこそと、今一度簡単に整理してみること。

static フィールド

そもそも static は 「静的」 を意味します。こそこそ。
「動的ではない」ということで、そのフィールドはいくらインスタンスをたくさん生成したとしても、クラスにたった1つです。
複数のインスタンスの間で共有されつづける情報、共有資源にして欲しいフィールドに static をつけます。クラス名.フィールド名 で呼びます。インスタンスを生成して呼び出しても同じモノにアクセスできます。

定数定義の際にfinal static で宣言することで、new する度に同じ値がインスタンスに複製されることが防止され、メモリの節約に役立ちます。

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

どこにいても呼び出すことができるハッピートイレの固定値です。

static メソッド(クラスメソッド)

static メソッドは、そのものがクラスのインスタンスではなく、クラスに属します。
呼び出しはクラス名.メソッド名、関数型インターフェースのメソッド参照の場合はクラス名::メソッド名で直接呼び出しができます。
static フィールドとともに、静的メンバと呼ばれます。
static が付かない一般のメソッドは非 static メソッド、インスタンスメソッドと呼びます。

以下は java8 以降の、関数型インターフェースのメソッド参照における static メソッドとインスタンスメソッドの呼び出しの違いです。

MethodReference.java
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);
  }
}

結果は wake up and happy 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 です。




Happy Coding したい場合は、Developer でインスタンスを作る必要があります。

public static void main(String [] args)

メインメソッドが呼び出される時、まだメモリにインスタンスは存在していないため、メインメソッドはそれを含んでいるクラスがインスタンス化されていなくても、実行される必要があるからです。

static クラス(static インナークラス)

クラスの中のクラス、インナークラスには三種類があり、メンバクラス、ローカルクラス、匿名クラスがあります。
static メンバクラスは、メンバクラスの一つで、宣言場所はクラスブロックの中(フィールドとメソッドと同じ位置)です。
しかし厳密には、static メンバクラスはインナークラスとは呼び難く、まったく別のクラスと表現した方が正しいでしょう。

インナークラスを包んでいるクラスを外部クラスと呼ぶと、
外部クラスやそのインスタンスとの関係が比較的薄く、外部クラスの static メンバにもアクセスできます。

他の static なモノ同様、外部クラス名.メンバクラス名で利用します。

Outer.java
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;
    }
  }
}

無関係なクラスかつメインメソッドでの呼び出しは以下の通りです。

Execute.java
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();
  }
}

上記の例に含めてみましたが、厳密なメンバクラスは、外部クラスから生み出される個々のインスタンスと強い結びつきを持つため、外部クラスのインスタンスがなければ new ができません。static なモノではないため、非 static メンバにもアクセスが許容されます。

static イニシャライザ

static {
  // 処理
}

static イニシャライザは、クラスロード(.class ファイルがロード)される際に、一度だけ、実行されるブロックです。
あるクラスをインスタンス化する前や、メインメソッドよりも前に呼び出し実行したい処理を記述します。
もちろん記述できるのは static なモノに限ります。

使い道あるかな?と思っていましたが、

public final static String VALUE;

static {
  VALUE = makeValue();
} 

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

先日、ふとしたきっかけでこのような使い方が試せました。
「インスタンス化よりも早いタイミングで、最初に一度だけ新しい値を作っておいて」それを使い回す目的だったので、ちょうどよかったと思います。

コンストラクタと static イニシャライザ

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();
  }
}

結果は Toilet です。
static イニシャライザは、new(インスタンス化) によってようやく呼び出されるコンストラクタよりも、早い段階で呼ばれることがわかります。

static import 宣言

java5 以降に導入されました。

以下は前回の投稿の制約アノテーション作成時に使用したソースです。

CustomSize.java
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
}

実際ElementTypeRetentionPolicyは長いパッケージ名を持つ enum なので、上記のようにすっきりと記述したりします。

外部クラスの static メンバでしたら、別に enum ではなくても利用できます。

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

おわりまして

朝一のトイレほど、文法の基礎を再確認することはとても大事です(こそこそ)

以上