정적 초기화 블록


265

내가 이해하는 한 "정적 초기화 블록"은 한 줄에서 수행 할 수없는 경우 정적 필드의 값을 설정하는 데 사용됩니다.

그러나 왜 우리에게 특별한 블록이 필요한지 이해하지 못합니다. 예를 들어 필드를 정적으로 (값을 할당하지 않고) 선언합니다. 그런 다음 위의 선언 된 정적 필드에 값을 생성하고 할당하는 몇 줄의 코드를 작성하십시오.

왜이 라인이 다음과 같은 특수 블록에 필요 static {...}합니까?


6
사소한 피드백이지만 가정을 명확하게 설명하여 어떤 대답이 옳은지를 분명히 밝히면 도움이 될 것입니다. 내가 먼저 질문을 읽을 때, 나는 잘못이-이해하고 차이 알고 있다고 생각 {...}대를 static {...}. (이 경우 Jon Skeet는 확실히 귀하의 질문에 더 잘 대답했습니다)
David T.

1
이 질문은 매우 불분명합니다. 당신은 응답자가 뒤섞여 서 당신이 의미하는 바에 대해 많은 긴 추측을합니다. 사람들이 대답 할 수있는 명확한 정보를 얻을 수 있도록 염두에두고있는 정적 초기화 블록 예제와 대안을 명시 적으로 작성하는 것은 어떻습니까?
돈 해치

답변:


430

비 정적 블록 :

{
    // Do Something...
}

클래스의 인스턴스가 생성 될 때마다 호출 됩니다 . 정적 블록 에만 호출됩니다 한 번 클래스 자체가 초기화 될 때 해당 유형의 많은 개체를 생성하는 방법에 상관없이,.

예:

public class Test {

    static{
        System.out.println("Static");
    }

    {
        System.out.println("Non-static block");
    }

    public static void main(String[] args) {
        Test t = new Test();
        Test t2 = new Test();
    }
}

인쇄합니다 :

Static
Non-static block
Non-static block

107
이것이 왜 대답입니까? 질문에 대답조차하지 않습니다.
Paul Bellora

43
"클래스가 생성 될 때마다 호출됩니다. 정적 블록은 해당 유형의 개체 수에 관계없이 한 번만 호출됩니다."
Adam Arold

83
궁금한 독자의 경우 비 정적 블록은 실제로 Java 컴파일러에 의해 클래스의 모든 생성자 ( source )에 복사됩니다 . 따라서 필드를 초기화하는 것은 여전히 ​​생성자의 작업입니다.
Martin Andersson

2
허용되는 대답은 stackoverflow.com/a/2420404/363573 입니다. 이 답변은 정적 블록이 필요한 실제 예를 보여줍니다.
Stephan

16
이 답변이 갑자기 하향 조정되는 이유는 무엇입니까? 이 답변이 받아 들여지는 것에 대해 의견이 맞지 않을 수도 있지만, 어떤 식 으로든 잘못되거나 오도되지는 않습니다. 간단한 예제를 통해 이러한 언어 구성을 이해하도록 돕기 위해 노력하고 있습니다.
Frederik Wordenskjold

133

정적 초기화 블록에 없다면 어디에 있었습니까? 초기화 목적으로 만 로컬 변수를 선언하고 필드와 구별하는 방법은 무엇입니까? 예를 들어, 어떻게 작성하고 싶 습니까 ?

public class Foo {
    private static final int widgets;

    static {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        widgets = first + second;
    }
}

경우 firstsecond블록에없는, 그들은 필드처럼 보일 것입니다. 블록 static이 앞에 없는 블록에 있으면 정적 초기화 블록 대신 인스턴스 초기화 블록으로 계산되므로 생성 된 인스턴스 총 한 번이 아니라 한 번 실행 됩니다.

이제이 특별한 경우에 대신 정적 메소드를 사용할 수 있습니다.

public class Foo {
    private static final int widgets = getWidgets();

    static int getWidgets() {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        return first + second;
    }
}

...하지만 동일한 블록 내에 여러 변수를 할당하거나 아무것도하지 않을 때는 작동하지 않습니다 (예 : 무언가를 로깅하거나 원시 라이브러리를 초기화하려는 경우).


1
정적 변수는 정적 변수가 할당되기 전이나 후에 발생합니까? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng

1
정적 변수가 할당되기 전에 또는 후에 정적 블록이 발생하는지 궁금합니다. 예를 들어 private static int widgets = 0; static{widgets = 2;}'='할당이 순서대로 발생한다는 것을 알았습니다. 즉, '='우선 순위가 먼저 할당됩니다. 위의 예는 '위젯'에 2의 값을 제공합니다. (PS는 주석을 5 분 안에 만 편집 할 수 있다는 것을 몰랐습니다 ...)
Weishi Zeng

@WeishiZeng : 예, 이것은 docs.oracle.com/javase/specs/jls/se8/html/…에 설명되어 있습니다. – 포인트 9
Jon Skeet

그러나 정적 초기화 블록과 정확히 동일한 코드를 가진 개인 정적 방법을 사용하고 위젯을 개인 정적 방법에 할당 할 수 없었습니까?
Zachary Kraus

1
@Zachary : 값을 반환하고 메소드 호출 결과를 할당하는 것을 의미합니까? 그렇다면, 예 - 당신이 때 하는 블록의 결과로 정확히 하나의 변수에 할당. 약 7 시간 만에 세부 사항으로 답변을 편집 할 것입니다.
Jon Skeet 2016 년

103

예를 들면 다음과 같습니다.

  private static final HashMap<String, String> MAP = new HashMap<String, String>();
  static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

"정적"섹션의 코드는 클래스의 인스턴스가 생성되기 전에 (그리고 정적 메소드가 다른 곳에서 호출되기 전에) 클래스로드시 실행됩니다. 이렇게하면 클래스 리소스를 모두 사용할 수 있습니다.

정적이 아닌 초기화 블록을 가질 수도 있습니다. 그것들은 클래스에 대해 정의 된 생성자 메소드 세트의 확장처럼 작동합니다. 키워드 "static"이 꺼져 있다는 점을 제외하면 정적 초기화 블록과 같습니다.


4
이 특정 예에서 때때로 이중 브레이스 패턴이 "
거품

악용 될 수 있지만, 반면에 일부 엉망을 정리하고 어떤 종류의 코드를 좀 더 "고체"로 만듭니다. 나는 Erlang에서 재미를 위해 프로그램하고, 당신은 지역 변수가 필요하지 않은 것에 중독되어 있습니다 :-)
Pointy

1
<< "정적"섹션의 코드는 클래스의 인스턴스가 생성되기 전에 (그리고 정적 메소드가 다른 곳에서 호출되기 전에) 클래스로드시 실행됩니다. 이렇게하면 클래스 리소스를 모두 사용할 수 있습니다. >> (위의 답변에서 언급 한 "Pointy") 정적 블록 실행과 관련하여주의해야 할 사항입니다.
학습자

afterPropertiesSet 메소드에서 InitializingBean을 사용 하여이 작업을 수행 할 수 있습니까?
egemen

48

런타임 중에 클래스를 한 번만 로드하는 것과 같이 실제로 값을 지정하지 않으려는 경우에도 유용 합니다.

예 :

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

또 다른 이점이 있습니다. 예외를 처리하는 데 사용할 수 있습니다. 그 상상 getStuff()여기에서 발생 Exception하는 정말 catch 블록에 속하는 :

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

다음 static초기화 여기에 유용합니다. 거기에서 예외를 처리 할 수 ​​있습니다.

또 다른 예는 할당하는 동안 수행 할 수없는 작업을 나중에 수행하는 것입니다.

private static Properties config = new Properties();

static {
    try { 
        config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
    } catch (IOException e) {
        throw new ExceptionInInitializerError("Cannot load properties file.", e);
    }
}

JDBC 드라이버 예제로 돌아가려면 괜찮은 JDBC 드라이버 자체도 static초기화 프로그램을 사용하여에 자신을 등록합니다 DriverManager. 또한 볼 대답을.


2
여기에 위험한 부두가 있습니다 ... 정적 초기화는 합성 clinit () 메소드에서 실행되며 암시 적으로 동기화 됩니다. 이는 JVM이 해당 클래스 파일에 대한 잠금을 획득 함을 의미합니다. 두 클래스가 서로를로드하려고 시도하고 각 클래스가 다른 스레드에서로드를 시작하면 다중 스레드 환경에서 교착 상태가 발생할 수 있습니다.
Ajax

@ Ajax : 문제의 JDBC 드라이버 또는로드를 담당하는 응용 프로그램 코드 에서이 버그로 간주합니다. 일반적으로 괜찮은 JDBC 드라이버의 경우 응용 프로그램을 시작하는 동안 응용 프로그램 전체에 한 번만로드하면 문제가 없습니다.
BalusC

그러나 분명히 버그 일 것이지만 JDBC 드라이버의 결함은 아닙니다. 어쩌면 드라이버에는 자체 정적 이니셜 라이저가있을 수도 있고 앱에서 다른 클래스와 함께이 클래스를 무고하게 초기화 할 수도 있습니다. java.awt.AWTEvent와 sun.util.logging.PlatformLogger 사이의 교착 상태 덕분에 이것을 발견했습니다. 나는 AWTEvent를 만져서 헤드리스를 실행하도록 지시했으며 일부 다른 라이브러리는 PlatformLogger를로드했습니다 ... AWTEvent 도로 드합니다.
Ajax

1
두 클래스 모두 다른 스레드에서 동기화되어 상처를 입 었으며 약 1/150 런이 교착 상태에 빠졌습니다. 따라서 정적 블록에서의 클래스로드에 대해 훨씬 더 신중합니다. 위에서 언급 한 경우, 지연된 제공자 패턴을 사용하여 즉시 (교착 상태 가능성이없는) 임시 제공자 클래스를 작성하고 필드를 초기화 한 다음 실제로 액세스 할 때 (비 동기화 된 필드 액세스에서), 그런 다음 교착 상태를 유발할 수있는 클래스를 실제로로드합니다.
Ajax

11

나는 static block단지 구문 설탕 이라고 말할 것 입니다. static블록으로 할 수있는 것은없고 다른 것으로는 할 수 없습니다.

여기에 게시 된 일부 예제를 재사용하십시오.

이 코드는 static이니셜 라이저 를 사용하지 않고 다시 작성할 수 있습니다 .

방법 # 1 : 포함 static

private static final HashMap<String, String> MAP;
static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

방법 # 2 : 미포함 static

private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
    HashMap<String, String> ret = new HashMap<>();
    ret.put("banana", "honey");
    ret.put("peanut butter", "jelly");
    ret.put("rice", "beans");
    return ret;
}

10

존재해야하는 몇 가지 실제 이유가 있습니다.

  1. 초기화 static final 에서 예외가 발생할 수있는 멤버 초기화
  2. 초기화 static final계산 된 값으로 멤버

사람들은 static {}특정 클래스 (예 : JDBC 드라이버)를로드하는 것과 같이 런타임 내에서 클래스가 의존하는 것을 초기화하는 편리한 방법으로 블록 을 사용하는 경향이 있습니다 . 다른 방법으로도 가능합니다. 그러나 위에서 언급 한 두 가지는 static {}블록 과 같은 구조로만 수행 할 수 있습니다 .


8

객체가 정적 블록으로 구성되기 전에 클래스에 대해 한 번의 코드 비트를 실행할 수 있습니다.

예 :

class A {
  static int var1 = 6;
  static int var2 = 9;
  static int var3;
  static long var4;

  static Date date1;
  static Date date2;

  static {
    date1 = new Date();

    for(int cnt = 0; cnt < var2; cnt++){
      var3 += var1;
    }

    System.out.println("End first static init: " + new Date());
  }
}

7

정적 블록이 정적 필드에만 액세스 할 수 있다고 생각하는 것은 일반적인 오해입니다. 이를 위해 실제 프로젝트에서 자주 사용하는 코드를 아래에 표시하고 싶습니다 ( 약간의 다른 컨텍스트 에서 다른 답변 에서 부분적으로 복사 ).

public enum Language { 
  ENGLISH("eng", "en", "en_GB", "en_US"),   
  GERMAN("de", "ge"),   
  CROATIAN("hr", "cro"),   
  RUSSIAN("ru"),
  BELGIAN("be",";-)");

  static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>(); 
  static { 
    for (Language l:Language.values()) { 
      // ignoring the case by normalizing to uppercase
      ALIAS_MAP.put(l.name().toUpperCase(),l); 
      for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l); 
    } 
  } 

  static public boolean has(String value) { 
    // ignoring the case by normalizing to uppercase
    return ALIAS_MAP.containsKey(value.toUpper()); 
  } 

  static public Language fromString(String value) { 
    if (value == null) throw new NullPointerException("alias null"); 
    Language l = ALIAS_MAP.get(value); 
    if (l == null) throw new IllegalArgumentException("Not an alias: "+value); 
    return l; 
  } 

  private List<String> aliases; 
  private Language(String... aliases) { 
    this.aliases = Arrays.asList(aliases); 
  } 
} 

여기서 이니셜 라이저는 인덱스 ( ALIAS_MAP) 를 유지 관리하고 별칭 집합을 원래 열거 형 유형으로 다시 매핑하는 데 사용됩니다 . Enum자체적으로 제공되는 내장 valueOf 메소드의 확장으로 사용 됩니다.

보시다시피 정적 초기화 프로그램은 private필드 에도 액세스 합니다 aliases. static블록이 이미 Enum값 인스턴스에 액세스 할 수 있음을 이해하는 것이 중요 합니다 (예 :) ENGLISH. 이는 블록이 호출 되기 전에 필드가 인스턴스로 초기화 된 것처럼 types 의 경우 초기화 및 실행 순서Enum 때문입니다 .static privatestatic

  1. 그만큼 Enum암시 적 정적 필드입니다 상수. 이를 위해서는 Enum 생성자 및 인스턴스 블록과 인스턴스 초기화가 먼저 수행되어야합니다.
  2. static 발생 순서대로 정적 필드의 차단 및 초기화

이 비 순차적 초기화 ( static블록 이전의 생성자 )는주의해야합니다. 또한 싱글 톤과 비슷한 인스턴스를 사용하여 정적 필드를 초기화 할 때도 발생합니다 (간이화).

public class Foo {
  static { System.out.println("Static Block 1"); }
  public static final Foo FOO = new Foo();
  static { System.out.println("Static Block 2"); }
  public Foo() { System.out.println("Constructor"); }
  static public void main(String p[]) {
    System.out.println("In Main");
    new Foo();
  }
}

우리가 보는 것은 다음과 같은 출력입니다.

Static Block 1
Constructor
Static Block 2
In Main
Constructor

정적 초기화는 실제로 생성자 이전 과 이후에도 발생할 수 있습니다 .

기본 메소드에서 Foo에 액세스하면 클래스가로드되고 정적 초기화가 시작됩니다. 그러나 정적 초기화의 일부로 정적 필드에 대한 생성자를 다시 호출 한 후 정적 초기화를 재개하고 기본 메소드 내에서 호출 된 생성자를 완료합니다. 정상적인 코딩에서는 다루지 않아도되기를 바라는 복잡한 상황입니다.

이에 대한 자세한 정보는 " 효과적인 Java " 책을 참조하십시오 .


1
에 액세스 aliases한다고해서 정적 블록이 비 정적 멤버에 액세스 할 수있는 것은 아닙니다. / static / 메소드가 리턴 한 값을 aliases통해 액세스합니다 . 언급했듯이 열거 형 변수가 이미 그 시점에서 사용 가능하다는 사실은 특이한 비트입니다.이 상황에서는 일반 클래스의 비 정적 멤버에 액세스 할 수 없습니다. Languagevalues()
Ignazio

정적 블록은 여전히 ​​정적 필드 (enum ENGLISH, GERMAN, ...의 경우) 인 객체에만 액세스합니다. 정적 필드는 객체 자체이므로 정적 객체의 인스턴스 필드에 액세스 할 수 있습니다.
Swami PR

1
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } 위의 코드는 열거 형 예제와 다르지 않으며 정적 블록 내부의 인스턴스 변수에 액세스 할 수 있습니다.
Swami PR

@SwamiPR은 실제로 놀랍게 컴파일하고 코드가 원칙적으로 다르지 않다는 데 동의해야합니다. Java 사양을 다시 읽어야합니다. 놓친 것이 있다고 생각합니다. 좋은 답변 감사합니다.
YoYo

@SwamiPR 문제는 실제로을 사용해야한다는 것 Enum입니다. 단일 인스턴스를 가리 키도록하는 가장 좋은 방법 입니다. 여기를 참조 하십시오 . 그리고 당신의 요점에, 나는 몇 가지 업데이트를 만들었습니다.
YoYo

3

정적 변수를 런타임에 설정해야하는 경우 static {...}블록이 매우 유용합니다.

예를 들어, 정적 멤버를 구성 파일 또는 데이터베이스에 저장된 값으로 설정해야하는 경우.

Map초기 멤버 선언에 이러한 값을 추가 할 수 없으므로 정적 멤버 에 값을 추가하려는 경우에도 유용합니다 .


3

따라서 정적 필드가 있습니다 ( "클래스 변수"라고도합니다. 클래스의 인스턴스가 아닌 클래스에 속하기 때문입니다. 즉, 어떤 객체가 아닌 클래스와 연결되어 있기 때문입니다). 따라서이 클래스의 인스턴스를 만들고 싶지 않고이 정적 필드를 조작하려면 세 가지 방법으로 수행 할 수 있습니다.

1- 변수를 선언 할 때 초기화하십시오.

static int x = 3;

2- 정적 초기화 블록이 있습니다 :

static int x;

static {
 x=3;
}

3- 클래스 변수에 액세스하고 초기화하는 클래스 메소드 (정적 메소드)를 갖습니다. 이것은 위 정적 블록의 대안입니다. 개인 정적 메소드를 작성할 수 있습니다.

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

왜 정적 메소드 대신 정적 초기화 블록을 사용합니까?

실제로 프로그램에서 필요한 것에 달려 있습니다. 그러나 정적 초기화 블록이 한 번 호출되고 클래스 메소드의 유일한 장점은 클래스 변수를 다시 초기화해야하는 경우 나중에 재사용 할 수 있다는 것입니다.

프로그램에 복잡한 배열이 있다고 가정 해 봅시다. ( 예를 들어 for 루프를 사용하여) 초기화하면 이 배열의 값이 프로그램 전체에서 변경되지만 어느 시점에서 다시 초기화하려고합니다 (초기 값으로 돌아갑니다). 이 경우 개인 정적 메소드를 호출 할 수 있습니다. 프로그램에서 값을 다시 초기화 할 필요가없는 경우 정적 블록을 사용할 수 있으며 나중에 프로그램에서 사용하지 않으므로 정적 메소드가 필요하지 않습니다.

참고 : 정적 블록은 코드에 표시된 순서대로 호출됩니다.

예 1 :

class A{
 public static int a =f();

// this is a static method
 private static int f(){
  return 3;
 }

// this is a static block
 static {
  a=5;
 }

 public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
  System.out.print(A.a); // this will print 5
 }

}

예 2 :

class A{
 static {
  a=5;
 }
 public static int a =f();

 private static int f(){
  return 3;
 }

 public static void main(String args[]) {
  System.out.print(A.a); // this will print 3
 }

}

0

보충으로 @Pointy와 같이

"정적"섹션의 코드는 클래스의 인스턴스가 생성되기 전에 (그리고 정적 메소드가 다른 곳에서 호출되기 전에) 클래스로드시 실행됩니다.

System.loadLibrary("I_am_native_library")정적 블록 에 추가해야합니다 .

static{
    System.loadLibrary("I_am_a_library");
}

관련 라이브러리가 메모리에로드되기 전에 원시 메소드가 호출되지 않도록 보장합니다.

오라클의 loadLibrary 에 따르면 :

동일한 라이브러리 이름으로이 메소드를 두 번 이상 호출하면 두 번째 및 후속 호출이 무시됩니다.

따라서 예기치 않게 System.loadLibrary를 넣는 것은 라이브러리가 여러 번로드되는 것을 피하기 위해 사용되지 않습니다.


0

먼저 응용 프로그램 클래스 자체가 java.class.Class런타임 동안 개체 로 인스턴스화된다는 것을 이해해야 합니다. 이것은 정적 블록이 실행될 때입니다. 그래서 당신은 실제로 이것을 할 수 있습니다 :

public class Main {

    private static int myInt;

    static {
        myInt = 1;
        System.out.println("myInt is 1");
    }

    //  needed only to run this class
    public static void main(String[] args) {
    }

}

"myInt is 1"을 콘솔에 출력합니다. 클래스를 인스턴스화하지 않았습니다.


0
static int B,H;
static boolean flag = true;
static{
    Scanner scan = new Scanner(System.in);
    B = scan.nextInt();
    scan.nextLine();
    H = scan.nextInt();

    if(B < 0 || H < 0){
        flag = false;
        System.out.println("java.lang.Exception: Breadth and height must be positive");
    } 
}

-1

정적 블록은 동적 기술로 정적 데이터 멤버를 초기화하는 기술에 사용되거나 정적 데이터 멤버의 동적 초기화에 대해 말할 수 있습니다. 정적 블록이 사용 중입니다. 비 정적 데이터 멤버 초기화의 경우 생성자가 있지만 우리는 가지고 있지 않습니다. 정적 데이터 멤버를 동적으로 초기화 할 수있는 모든 장소

Eg:-class Solution{
         // static int x=10;
           static int x;
       static{
        try{
          x=System.out.println();
          }
         catch(Exception e){}
        }
       }

     class Solution1{
      public static void main(String a[]){
      System.out.println(Solution.x);
        }
        }

이제 정적 int x가 동적으로 초기화됩니다. 컴파일러가 Solution.x로 갈 때 클래스 로딩 시간에 솔루션 클래스 및 정적 블록로드를로드합니다. 따라서 정적 데이터 멤버를 동적으로 초기화 할 수 있습니다.

}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.