Java에서 초기화 블록을 사용해야합니까?


16

나는 최근에 본 적이없는 Java 구조를 발견했으며 사용 해야하는지 궁금합니다. 이니셜 라이저 블록 이라고 합니다 .

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

코드 블록은 각 생성자에 복사됩니다. 즉, 생성자가 여러 개인 경우 코드를 다시 작성할 필요가 없습니다.

그러나이 구문을 사용하는 세 가지 주요 단점이 있습니다.

  1. 여러 코드 블록을 정의 할 수 있으며 작성된 순서대로 실행되므로 코드 순서가 중요한 Java에서는 매우 드문 경우 중 하나입니다. 단순히 코드 블록의 순서를 변경하면 실제로 코드가 변경되므로 나에게 해로운 것 같습니다.
  2. 나는 그것을 사용함으로써 어떤 이점도 보지 못한다. 대부분의 경우 생성자는 미리 정의 된 값으로 서로를 호출합니다. 그렇지 않은 경우에도 코드를 개인용 메서드에 넣고 각 생성자에서 호출 할 수 있습니다.
  3. 클래스의 끝에 블록을 넣을 수 있고 생성자는 일반적으로 클래스의 시작에 있으므로 가독성이 떨어집니다. 필요하지 않다면 코드 파일의 완전히 다른 부분을 보는 것은 직관적이지 않습니다.

위의 진술이 사실이라면 왜이 언어 구성이 도입 되었는가? 합법적 인 사용 사례가 있습니까?


3
게시 한 예제에는 이니셜 라이저 블록처럼 보이는 것이 포함되어 있지 않습니다.
Simon B

6
@SimonBarker는 다시 한 번 봅니다. – { doStuff(); }클래스 수준에서 초기화 블록입니다.
amon

@SimonBarker 주위에있는 코드 블록doStuff()
Reinstate Monica-dirkk


2
"[S] 코드 블록의 순서를 변경하면 실제로 코드가 변경됩니다." 그리고 변수 초기화 프로그램이나 개별 코드 줄의 순서를 변경하는 것과 어떻게 다른가요? 종속성이없는 경우에는 해가 발생하지 않으며 종속성이있는 경우 종속성을 잘못된 순서로 배치하는 것은 개별 코드 행에 대한 순서가 잘못된 종속성과 동일합니다. Java가 메소드 및 클래스를 정의하기 전에 참조 할 수 있다고해서 Java에서 순서 종속 코드가 드문 것은 아닙니다.
JAB

답변:


20

초기화 블록을 사용하는 두 가지 경우가 있습니다.

첫 번째는 최종 멤버를 초기화하는 것입니다. Java에서는 선언과 함께 인라인으로 최종 멤버를 초기화하거나 생성자에서 초기화 할 수 있습니다. 방법에서는 최종 멤버에게 할당하는 것이 금지됩니다.

유효합니다 :

final int val = 2;

이것도 유효합니다 :

final int val;

MyClass() {
    val = 2;
}

이것은 유효하지 않습니다 :

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

여러 생성자가 있고 초기화 논리가 너무 복잡하여 최종 멤버 인라인을 초기화 할 수 없거나 생성자가 자신을 호출 할 수없는 경우 초기화 코드를 복사 / 붙여 넣거나 사용할 수 있습니다 이니셜 라이저 블록.

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

초기화 블록에 대한 다른 유스 케이스는 작은 도우미 데이터 구조를 작성하는 것입니다. 멤버를 선언하고 선언 후 바로 이니셜 라이저 블록에 값을 넣습니다.

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}

유효하지 않은 메소드 호출이 아닙니다. init 메소드 내부의 코드가 유효하지 않습니다. 생성자와 초기화 블록 만 최종 멤버 변수에 할당 할 수 있으므로 init의 할당은 컴파일되지 않습니다.
barjak

네 번째 코드 블록이 컴파일되지 않습니다. Initiizer 블록은 모든 생성자 보다 먼저 실행 되므로 squareVal = val * val초기화되지 않은 값에 액세스하는 것에 대해 불평합니다. 이니셜 라이저 블록은 생성자에 전달 된 인수에 의존 할 수 없습니다. 이런 종류의 문제에 대해 본 일반적인 솔루션은 복잡한 논리를 사용하여 단일 "기본"생성자를 정의하고 해당 구성 요소에 대해 다른 모든 생성자를 정의하는 것입니다. 실제로 인스턴스 이니셜 라이저를 대부분 사용하면 해당 패턴으로 대체 할 수 있습니다.
Malnormalulo

11

일반적으로 비 정적 초기화 블록을 사용하지 마십시오 (정적 블록도 ​​피하십시오).

혼란스러운 구문

이 질문을 보면 3 가지 답변이 있지만이 구문으로 4 명을 속였습니다. 나는 그들 중 하나였으며 16 년 동안 Java를 작성해 왔습니다! 분명히 구문은 오류가 발생하기 쉽습니다! 나는 멀리 떨어져있을 것입니다.

텔레 스코핑 생성자

정말 간단한 것들을 위해, 당신은이 혼란을 피하기 위해 "telescoping"생성자를 사용할 수 있습니다 :

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

빌더 패턴

각 생성자 또는 기타 복잡한 초기화가 끝날 때 doStuff ()를 수행해야하는 경우 빌더 패턴이 가장 좋습니다. Josh Bloch 는 빌더가 좋은 아이디어 인 몇 가지 이유를 나열합니다. 빌더는 작성하는 데 약간의 시간이 걸리지 만 올바르게 작성되었으므로 사용하는 것이 좋습니다.

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

정적 이니셜 라이저 루프

나는 정적 이니셜 라이저를 많이 사용 했지만 때로는 클래스가 완전히로드되기 전에 두 개의 클래스가 서로의 정적 이니셜 라이저 블록에 의존하는 루프가 발생했습니다. 이로 인해 "클래스를로드하지 못했습니다"또는 유사하게 모호한 오류 메시지가 나타납니다. 문제가 무엇인지 파악하기 위해 소스 컨트롤에서 마지막으로 알려진 작동 버전과 파일을 비교해야했습니다. 전혀 재미 없다.

게으른 초기화

정적 이니셜 라이저는 작동하기 때문에 성능상의 이유로 좋으며 너무 혼란스럽지 않을 수 있습니다. 그러나 일반적으로 요즘에는 정적 초기화 프로그램 보다 게으른 초기화 를 선호 합니다. 그들이하는 일이 분명하고, 아직 클래스 로딩 버그에 빠지지 않았으며 초기화 블록보다 더 많은 초기화 상황에서 작동합니다.

데이터 정의

데이터 구조를 만들기위한 정적 초기화 (다른 답변의 예제와 비교) 대신 Paguro의 불변 데이터 정의 도우미 함수를 사용합니다 .

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

추방

Java의 초기에는 이니셜 라이저 블록이 일부 작업을 수행하는 유일한 방법 이었지만 지금은 혼란스럽고 오류가 발생하기 쉬우 며 대부분의 경우 더 나은 대안으로 대체되었습니다 (위에서 자세히 설명). 레거시 코드에서 볼 수 있거나 테스트 중일 때 초기화 블록에 대해 아는 것이 흥미 롭습니다.하지만 코드 검토를 수행하고 새 코드에서 하나를 보았을 때 위의 대안은 코드에 엄지 손가락을 넣기 전에 적합했습니다.


3

final( barjak 's answer 참조 )로 선언 된 인스턴스 변수의 초기화 외에도 static초기화 블록에 대해서도 언급 합니다.

그것들을 일종의 "정적 구성자 (static contructor)"로 사용할 수 있습니다.

이렇게하면 클래스를 처음 참조 할 때 정적 변수에서 복잡한 초기화를 수행 할 수 있습니다.

다음은 barjak의 예제에서 영감을 얻은 예입니다.

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}

1

비 정적 이니셜 라이저 블록과 관련하여 익명의 클래스에서 기본 생성자로 작동하는 것이 가장 중요합니다. 그것이 기본적으로 존재하는 유일한 권리입니다.


0

나는 진술 1, 2, 3에 전적으로 동의합니다. 나는 또한 이러한 이유로 블록 초기화 프로그램을 사용하지 않으며 왜 Java에 존재하는지 모르겠습니다.

그러나 한 가지 경우 에는 정적 블록 이니셜 라이저 를 사용해야합니다 : 생성자가 확인 된 예외를 throw 할 수있는 정적 필드를 인스턴스화해야 할 때.

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

그러나 대신해야합니다 :

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

나는 (그것은 또한 마크를 방지 매우 추한이 관용구를 찾을 수 contextfinal) 그러나 이것은이 유일한 방법 등의 필드를 초기화하는 자바에 의해 지원됩니다.


context = null;캐치 블록 을 설정 하면 컨텍스트를 최종으로 선언 할 수 있다고 생각합니다 .
GlenPeterson 2016 년

@GlenPeterson 내가 시도했지만 컴파일되지 않습니다 :The final field context may already have been assigned
발견

죄송합니다! 정적 블록 안에 로컬 변수를 도입하면 컨텍스트를 최종적으로 만들 수 있습니다.static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.