programing

자바에서 이니셜 라이저와 생성자의 사용

nasanasas 2020. 8. 30. 08:42
반응형

자바에서 이니셜 라이저와 생성자의 사용


그래서 저는 늦게 Java 기술을 연마했고 이전에 알지 못했던 몇 가지 기능을 발견했습니다. 정적 및 인스턴스 이니셜 라이저는 이러한 두 가지 기술입니다.

내 질문은 생성자에 코드를 포함하는 대신 이니셜 라이저를 언제 사용합니까? 몇 가지 명백한 가능성을 생각했습니다.

  • 정적 / 인스턴스 이니셜 라이저는 "최종"정적 / 인스턴스 변수의 값을 설정하는 데 사용할 수 있지만 생성자는

  • 정적 이니셜 라이저를 사용하여 클래스의 정적 변수 값을 설정할 수 있습니다. 이는 각 생성자의 시작 부분에 "if (someStaticVar == null) // do stuff"코드 블록을 갖는 것보다 더 효율적이어야합니다.

두 경우 모두 이러한 변수를 설정하는 데 필요한 코드가 단순히 "var = value"보다 더 복잡하다고 가정합니다. 그렇지 않으면 변수를 선언 할 때 단순히 값을 설정하는 대신 이니셜 라이저를 사용할 이유가없는 것 같습니다.

그러나 이것들이 사소한 이득은 아니지만 (특히 최종 변수를 설정하는 능력), 이니셜 라이저를 사용해야하는 상황이 다소 제한되어있는 것 같습니다.

생성자에서 수행되는 많은 작업에 대해 이니셜 라이저를 확실히 사용할 수 있지만 실제로 그렇게하는 이유를 모르겠습니다. 클래스의 모든 생성자가 많은 양의 코드를 공유하더라도 private initialize () 함수를 사용하는 것이 이니셜 라이저를 사용하는 것보다 나에게 더 의미가있는 것 같습니다. 새로운 코드를 작성할 때 해당 코드를 실행하도록 잠그지 않기 때문입니다. 건설자.

내가 뭔가를 놓치고 있습니까? 이니셜 라이저를 사용해야하는 다른 여러 상황이 있습니까? 아니면 실제로 매우 특정한 상황에서 사용하기에는 다소 제한된 도구입니까?


정적 이니셜 라이저는 언급 된 클레 터스처럼 유용하며 동일한 방식으로 사용합니다. 클래스가로드 될 때 초기화 될 정적 변수가있는 경우 정적 이니셜 라이저를 사용하는 것이 좋습니다. 특히 복잡한 초기화를 수행하고 여전히 정적 변수가 final. 이것은 큰 승리입니다.

"if (someStaticVar == null) // do stuff"가 지저분하고 오류가 발생하기 쉽습니다. 정적으로 초기화되고 선언 final되면 그 가능성을 피할 수 있습니다 null.

그러나 다음과 같이 말할 때 혼란 스럽습니다.

정적 / 인스턴스 이니셜 라이저는 "최종"정적 / 인스턴스 변수의 값을 설정하는 데 사용할 수 있지만 생성자는

나는 당신이 둘 다 말하고 있다고 가정합니다.

  • 정적 이니셜 라이저는 "최종"정적 변수의 값을 설정하는 데 사용할 수 있지만 생성자는
  • 인스턴스 이니셜 라이저는 "최종"인스턴스 변수의 값을 설정하는 데 사용할 수 있지만 생성자는

첫 번째 부분은 정확하고 두 번째 부분은 틀립니다. 예를 들어 다음과 같이 할 수 있습니다.

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

또한 생성자간에 많은 코드를 공유 할 때이를 처리하는 가장 좋은 방법 중 하나는 생성자를 연결하여 기본값을 제공하는 것입니다. 이것은 무슨 일이 일어나고 있는지 아주 분명합니다.

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

익명 내부 클래스는 생성자를 가질 수 없으므로 (익명이므로) 인스턴스 이니셜 라이저에 매우 적합합니다.


나는 최종 정적 데이터, 특히 컬렉션을 설정하기 위해 정적 초기화 블록을 가장 자주 사용합니다. 예를 들면 :

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

이제이 예제는 한 줄의 코드로 수행 할 수 있습니다.

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

그러나 정적 버전은 특히 항목을 초기화하는 데 사소하지 않은 경우 훨씬 더 깔끔 할 수 있습니다.

순진한 구현은 수정 불가능한 목록을 생성하지 않을 수도 있으며 이는 잠재적 인 실수입니다. 위의 내용은 공개 메서드 등에서 즐겁게 반환 할 수있는 변경 불가능한 데이터 구조를 만듭니다.


여기에 이미 우수한 점을 추가하기 위해. 정적 이니셜 라이저는 스레드로부터 안전합니다. 클래스가로드 될 때 실행되기 때문에 생성자를 사용하는 것보다 더 간단한 정적 데이터 초기화가 가능합니다. 정적 데이터가 초기화되었는지 확인한 다음 실제로 초기화하려면 동기화 된 블록이 필요합니다.

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

Don't forget, you now have to synchronize at the class, not instance level. This incurs a cost for every instance constructed instead of a one time cost when the class is loaded. Plus, it's ugly ;-)


I read a whole article looking for an answer to the init order of initializers vs. their constructors. I didn't find it, so I wrote some code to check my understanding. I thought I would add this little demonstration as a comment. To test your understanding, see if you can predict the answer before reading it at the bottom.

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

Output:

java CtorOrder
A ctor
B initX
B ctor

A static initializer is the equivalent of a constructor in the static context. You will certainly see that more often than an instance initializer. Sometimes you need to run code to set up the static environment.

In general, an instance initalizer is best for anonymous inner classes. Take a look at JMock's cookbook to see an innovative way to use it to make code more readable.

Sometimes, if you have some logic which is complicated to chain across constructors (say you are subclassing and you can't call this() because you need to call super()), you could avoid duplication by doing the common stuff in the instance initalizer. Instance initalizers are so rare, though, that they are a surprising syntax to many, so I avoid them and would rather make my class concrete and not anonymous if I need the constructor behavior.

JMock is an exception, because that is how the framework is intended to be used.


There is one important aspect that you have to consider in your choice:

Initializer blocks are members of the class/object, while constructors are not. This is important when considering extension/subclassing:

  1. Initializers are inherited by subclasses. (Though, can be shadowed)
    This means it is basically guaranteed that subclasses are initialized as intended by the parent class.
  2. Constructors are not inherited, though. (They only call super() [i.e. no parameters] implicitly or you have to make a specific super(...) call manually.)
    This means it is possible that a implicit or exclicit super(...) call might not initialize the subclass as intended by the parent class.

Consider this example of an initializer block:

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

output:
initializing in initializer block of: ChildOfParentWithInitializer init
-> No matter what constructors the subclass implements, the field will be initialized.

Now consider this example with constructors:

class ParentWithConstructor {
    protected final String aFieldToInitialize;

    // different constructors initialize the value differently:
    ParentWithConstructor(){
        //init a null object
        aFieldToInitialize = null;
        System.out.println("Constructor of " 
            + this.getClass().getSimpleName() + " inits to null");
    }

    ParentWithConstructor(String... params) {
        //init all fields to intended values
        aFieldToInitialize = "intended init Value";
        System.out.println("initializing in parameterized constructor of:" 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithConstructor extends ParentWithConstructor{
    public static void main (String... args){
        System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
    }
}

output:
Constructor of ChildOfParentWithConstructor inits to null null
-> This will initialize the field to null by default, even though it might not be the result you wanted.


I would also like to add one point along with all the above fabulous answers . When we load a driver in JDBC using Class.forName("") the the Class loading happens and the static initializer of the Driver class gets fired and the code inside it registers Driver to Driver Manager. This is one of the significant use of static code block.


As you mentioned, it's not useful in a lot of cases and as with any less-used syntax, you probably want to avoid it just to stop the next person looking at your code from spending the 30 seconds to pull it out of the vaults.

On the other hand, it is the only way to do a few things (I think you pretty much covered those).

Static variables themselves should be somewhat avoided anyway--not always, but if you use a lot of them, or you use a lot in one class, you might find different approaches, your future self will thank you.

참고URL : https://stackoverflow.com/questions/804589/use-of-initializers-vs-constructors-in-java

반응형