sourcecode

Java에서의 이니셜라이저와 컨스트럭터 사용

copyscript 2022. 9. 6. 22:26
반응형

Java에서의 이니셜라이저와 컨스트럭터 사용

그래서 최근 Java 기술을 연마하고 있는데, 이전에는 몰랐던 몇 가지 기능을 발견했습니다.스태틱과 인스턴스 이니셜라이저는 이러한 두 가지 기술입니다.

궁금한 점은 언제 컨스트럭터에 코드를 포함하지 않고 이니셜라이저를 사용할 것인가 하는 것입니다.몇 가지 확실한 가능성을 생각해 봤습니다.

  • static/initializer를 사용하여 "최종" static/initializer 값을 설정할 수 있지만 생성자는 사용할 수 없습니다.

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

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

그러나 이러한 것들이 사소한 이득(특히 최종 변수를 설정하는 능력)은 아니지만 이니셜라이저를 사용해야 하는 상황은 상당히 제한적인 것으로 보입니다.

컨스트럭터에서 이루어지는 많은 작업에 이니셜라이저를 사용할 수 있는 것은 확실합니다만, 그렇게 하는 이유를 잘 모르겠습니다.비록 클래스의 모든 컨스트럭터가 많은 양의 코드를 공유하더라도 개인 initialize() 함수는 새로운 컨스트럭터를 쓸 때 코드를 실행하도록 제한하지 않기 때문에 이니셜라이저를 사용하는 것보다 더 의미가 있는 것 같습니다.

내가 뭘 빼놓았나요?이니셜라이저를 사용해야 하는 다른 상황이 몇 가지 있습니까?아니면 정말 특정 상황에서 사용하기에는 다소 제한적인 도구일까요?

스태틱 이니셜라이저는 앞서 언급한 바와 같이 유용하며 저도 같은 방법으로 사용합니다.때할 수 가 " " "가 .final이건 큰 승리야

StaticVar // stuff가 발생하기. "if (some StaticVar == null) // do things"는 "if (some StaticVar == null) // do things"입니다.되어 「」라고 선언되어 .final그 다음, 당신은 그것이 일어날 가능성을 회피합니다.null.

하지만, 당신이 이렇게 말할 때 나는 혼란스럽다.

static/initializer를 사용하여 "최종" static/initializer 값을 설정할 수 있지만 생성자는 사용할 수 없습니다.

둘 다 말하는 거겠죠?

  • static initializer를 사용하여 "최종" 정적 변수의 값을 설정할 수 있지만 생성자는 사용할 수 없습니다.
  • 인스턴스 이니셜라이저를 사용하여 "최종" 인스턴스 변수의 값을 설정할 수 있지만 생성자는 사용할 수 없습니다.

첫 번째 포인트는 맞지만 두 번째 포인트는 틀렸어요예를 들어 다음과 같이 할 수 있습니다.

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

인스턴스 레벨이 아닌 클래스에서 동기화해야 합니다.이로 인해 클래스가 로드될 때 1회 비용 대신 생성된 모든 인스턴스에 비용이 발생합니다.게다가 못생겼어;-)

이니셜라이저와 컨스트럭터의 초기화 순서에 대한 답을 찾는 기사를 다 읽었습니다.못찾아서 코드를 써서 이해했는지 확인했어요.이 작은 데모를 코멘트로 추가하려고 합니다.당신의 이해도를 테스트하기 위해 아래에 있는 답을 읽기 전에 당신이 답을 예측할 수 있는지 알아보세요.

/**
 * 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");
  }

}

출력:

java CtorOrder
A ctor
B initX
B ctor

스태틱 이니셜라이저는 스태틱콘텍스트의 컨스트럭터와 동등합니다.인스턴스 이니셜라이저보다 더 자주 볼 수 있습니다.정적 환경을 설정하려면 코드를 실행해야 하는 경우가 있습니다.

일반적으로 instance initalizer는 익명 내부 클래스에 적합합니다.JMock의 요리책을 보고 코드를 읽기 쉽게 하는 혁신적인 방법을 알아보십시오.

컨스트럭터 간에 체인을 하기 어려운 로직이 있는 경우(예를 들어 super()를 호출해야 하기 때문에 이()를 호출할 수 없는 경우), 인스턴스 initalizer에서 일반적인 작업을 수행함으로써 중복을 피할 수 있습니다.그러나 인스턴스 이니털라이저는 매우 드물기 때문에 많은 사람에게 놀라운 구문이기 때문에 저는 그것들을 피하고 생성자의 동작이 필요하다면 익명이 아닌 구체적인 클래스를 만들고 싶습니다.

JMock은 예외입니다.그것은 프레임워크가 사용되는 방식이기 때문입니다.

선택 시 고려해야 할 중요한 한 가지 측면이 있습니다.

이니셜라이저 블록은 클래스/개체의 멤버이지만 컨스트럭터는 없습니다.이는 확장/서브클래싱을 고려할 때 중요합니다.

  1. 이니셜라이저는 서브클래스로 상속됩니다.(단, 음영 처리 가능)
    즉, 기본적으로 서브클래스가 부모클래스의 의도대로 초기화됨을 보증합니다.
  2. , 컨스트럭터는 상속되지 않습니다.(컨스트럭터에서는super() 없음]을 암묵적으로 [아, 아, 아, 아, 아]를 합니다.super(...)를 수동으로 호출합니다).
    , 또는 가 있을 수 .super(...)콜이 부모 클래스에서 의도한 대로 서브 클래스를 초기화하지 않을 수 있습니다.

이니셜라이저 블록의 예를 다음에 나타냅니다.

    class ParentWithInitializer {
        protected 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);
        }
    }

출력:

initializing in initializer block of: ChildOfParentWithInitializer
init

-> 서브클래스가 실장하는 컨스트럭터에 관계없이 필드는 초기화됩니다.

이제 컨스트럭터를 사용한 예를 살펴보겠습니다.

    class ParentWithConstructor {
        protected 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);
        }
    }

출력:

Constructor of ChildOfParentWithConstructor inits to null
null

-> 그러면 필드가 초기화됩니다.null디폴트로는 원하는 결과가 아닐 수도 있습니다.

위의 멋진 답변들과 함께 한 가지 포인트를 더하고 싶습니다.Class.forName("")을 사용하여 JDBC에서 드라이버를 로드하면 클래스 로드가 발생하고 드라이버 클래스의 스태틱인테셜라이저가 기동되어 드라이버 내부의 코드가 드라이버 매니저에 등록됩니다.이것은 스태틱코드 블록의 중요한 용도 중 하나입니다.

말씀하신 바와 같이 많은 경우 유용하지 않습니다.사용률이 낮은 구문과 마찬가지로 다음 사용자가 코드를 볼 때 30초 동안 볼트에서 코드를 꺼내지 않도록 하는 것이 좋습니다.

한편, 그것은 몇 가지 밖에 할 수 없는 방법(대부분은 커버했다고 생각합니다.

정적 변수 자체는 어느 정도 피해야 합니다.항상 그렇지는 않지만, 많은 변수를 사용하거나 한 클래스에서 많은 변수를 사용하면 다른 접근방식을 찾을 수 있습니다.미래의 자신이 감사할 것입니다.

일부 부작용을 수행하는 정적 이니셜라이저의 큰 문제 중 하나는 유닛 테스트에서 조롱할 수 없다는 것입니다.

도서관이 그렇게 하는 걸 봤는데, 정말 골치 아프네요.

따라서 정적 이니셜라이저만 순수하게 유지하는 것이 좋습니다.

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

반응형