source

생성자 또는 선언 시 클래스 필드를 초기화하시겠습니까?

factcode 2022. 9. 24. 09:54
반응형

생성자 또는 선언 시 클래스 필드를 초기화하시겠습니까?

최근 C#과 Java에서 프로그래밍을 하고 있는데 클래스 필드를 초기화하기 가장 좋은 장소가 어디인지 궁금합니다.

선언할 때 해야 하나요?

public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

    public void Roll()
    {
       // ......
    }
}

또는 컨스트럭터에서?:

public class Dice
{
    private int topFace;
    private Random myRand;

    public Dice()
    {
        topFace = 1;
        myRand = new Random();
    }

    public void Roll()
    {
        // .....
    }
}

저는 여러분 중 몇몇 참전용사들이 가장 좋은 방법이 무엇이라고 생각하는지 정말 궁금합니다.저는 일관되게 한 가지 접근 방식을 고수하고 싶습니다.

규칙:

  1. 「」 。null,false,0,0.0
  2. 필드 값을 변경하는 생성자 매개 변수가 없는 경우 선언에서 초기화를 선호합니다.
  3. 생성자 매개 변수로 인해 필드 값이 변경된 경우 초기화를 생성자에 넣습니다.
  4. 실천을 일관되게 하라(가장 중요한 규칙).

C#에서는 상관없습니다.당신이 준 두 코드 샘플은 완전히 동일합니다.첫 번째 예에서는 C# 컴파일러(혹은 CLR?)가 빈 컨스트럭터를 구축하고 컨스트럭터에 있는 것처럼 변수를 초기화합니다(Jon Sket이 다음 코멘트에서 설명하는 약간의 뉘앙스가 있습니다).이미 컨스트럭터가 있는 경우, "위"의 모든 초기화가 그 상단으로 이동됩니다.

베스트 프랙티스의 관점에서 보면, 전자는 후자에 비해 에러 발생률이 낮습니다.다른 생성자를 쉽게 추가하고 체인을 하는 것을 잊어버릴 수 있기 때문입니다.

C#의 의미는 여기서 Java와 약간 다릅니다.C#에서는 슈퍼클래스 컨스트럭터를 호출하기 전에 선언에서의 할당이 실행됩니다.Java에서는 바로 실행되며, 그 직후에 'this'를 사용할 수 있습니다(특히 익명 내부 클래스에 유용합니다). 두 형식의 의미론이 실제로 일치함을 의미합니다.

가능하면 필드를 최종 항목으로 만듭니다.

한 가지 주의사항이 있다고 생각합니다.나도 그런 실수를 저지른 적이 있다.파생 클래스 내에서 추상 기본 클래스에서 상속된 필드를 "선언 시 초기화"하려고 했습니다.그 결과 "base" 필드 세트와 새로 선언된 필드 세트 두 개가 존재하여 디버깅에 상당한 시간이 소요되었습니다.

교훈: 상속된 필드를 초기화하려면 생성자 내부에서 초기화해야 합니다.

예제의 유형을 가정하면 반드시 생성자의 필드를 초기화해야 합니다.예외적인 경우는 다음과 같습니다.

  • 정적 클래스/메서드의 필드
  • static/final/et al로 입력된 필드

저는 항상 클래스 맨 위에 있는 필드 목록을 목차(사용 방법이 아닌 여기에 포함된 내용)로 생각하고 생성자를 서론이라고 생각합니다.방법은 물론 장입니다.

여러 가지 상황이 있습니다.

그냥 빈 리스트만 있으면 돼

상황은 명백하다.목록을 준비하고 목록에 항목을 추가할 때 예외가 발생하는 것을 방지하면 됩니다.

public class CsvFile
{
    private List<CsvRow> lines = new List<CsvRow>();

    public CsvFile()
    {
    }
}

가치관을 알고 있다

디폴트로는 어떤 값을 가질지 정확히 알고 있거나 다른 논리를 사용해야 합니다.

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = new List<string>() {"usernameA", "usernameB"};
    }
}

또는

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = GetDefaultUsers(2);
    }
}

가능한 값이 있는 빈 목록

다른 생성자를 통해 값을 추가할 수 있는 빈 목록이 기본적으로 표시될 수 있습니다.

public class AdminTeam
{
    private List<string> usernames = new List<string>();

    public AdminTeam()
    {
    }

    public AdminTeam(List<string> admins)
    {
         admins.ForEach(x => usernames.Add(x));
    }
}

Java에서 선언이 있는 이니셜라이저는 어떤 컨스트럭터가 사용되는지(여러 개 있는 경우) 또는 컨스트럭터의 파라미터(인수가 있는 경우)에 관계없이 필드가 항상 같은 방법으로 초기화됨을 의미합니다.단, 컨스트럭터는 나중에 값을 변경할 수 있습니다(최종값이 아닌 경우).따라서 선언과 함께 이니셜라이저를 사용하면 어떤 컨스트럭터가 사용되는지, 어떤 컨스트럭터에 전달되는 파라미터에 관계없이 초기화 값은 모든 경우에 필드가 갖는 값임을 독자에게 알 수 있습니다.따라서 선언과 함께 이니셜라이저를 사용하는 것은 생성된 모든 객체의 값이 동일한 경우뿐입니다.

경우에 따라 다르죠?

저는 일반적으로 모든 것을 초기화하고 일관되게 합니다.네, 너무 명확하지만 유지보수가 좀 더 쉬워요.

퍼포먼스가 걱정되는 경우는, 필요한 것만을 초기화하고, 코스트를 최대한으로 억제하는 영역에 배치합니다.

실시간 시스템에서는 변수나 상수가 필요조차 없는지에 대해 질문합니다.

그리고 C++에서는 두 장소 모두 초기화하지 않고 Init() 함수로 이동하는 경우가 많습니다. 왜일까요?C++에서는 오브젝트 구축 중에 예외를 발생시킬 수 있는 것을 초기화하면 메모리 누수가 발생할 수 있습니다.

C#의 설계에서는 인라인 초기화가 권장되지 않으면 언어로 되어 있지 않습니다.코드의 서로 다른 장소 간의 상호 참조를 피할 수 있다면, 일반적으로 더 나은 결과를 얻을 수 있습니다.

또한 최적의 성능을 위해 인라인으로 해야 하는 정적 필드 초기화와의 일관성 문제도 있습니다.컨스트럭터 설계의 프레임워크 설계 가이드라인에는 다음과 같이 기술되어 있습니다.

실행 시 명시적으로 정의된 정적 생성자가 없는 유형의 성능을 최적화할 수 있으므로 정적 생성자를 명시적으로 사용하는 대신 정적 필드를 인라인으로 초기화하는 것을 고려해 보십시오.

이 맥락에서 "고려하다"는 것은 특별한 이유가 없는 한 그렇게 하는 것을 의미합니다.static initializer 필드의 경우 초기화가 너무 복잡하여 인라인으로 코딩할 수 없는 것이 좋은 이유입니다.

일관성을 유지하는 것도 중요하지만, 이것은 스스로에게 물어보는 질문입니다: "다른 것을 위한 건설자가 있나요?"

일반적으로 클래스 자체가 변수의 하우징 역할을 하는 것 외에는 아무것도 하지 않는 데이터 전송 모델을 만들고 있습니다.

이러한 시나리오에서는 보통 메서드나 컨스트럭터가 없습니다.특히 선언에 따라 목록을 초기화할 수 있기 때문에 목록 초기화를 독점적으로 목적으로 컨스트럭터를 만드는 것은 어리석게 느껴질 것입니다.

그래서 다른 많은 사람들이 말했듯이, 그것은 사용법에 달려있다.심플하게 하고, 불필요한 것은 만들지 말아 주세요.

두 개 이상의 컨스트럭터가 있는 상황을 고려합니다.초기화는 컨스트럭터마다 다른가요?동일할 경우 각 컨스트럭터에 대해 반복할 필요가 있습니다.이는 kokos 문장과 일치하지만 파라미터와는 관련이 없을 수 있습니다.예를 들어 오브젝트가 생성된 방법을 나타내는 플래그를 유지한다고 가정합니다.그러면 생성자 매개 변수에 관계없이 생성자마다 플래그가 다르게 초기화됩니다.한편, 각 생성자에 대해 동일한 초기화를 반복할 경우 일부 생성자에서 초기화 매개 변수를 (의도 없이) 변경할 수 있습니다.여기서의 기본 개념은 공통 코드가 공통 위치를 가지며 다른 위치에서 반복되지 않아야 한다는 것입니다.그래서 저는 그것이 당신에게 더 이상 적용되지 않는 특정한 상황이 생길 때까지 그것을 선언서에 넣으라고 말하고 싶습니다.

선언에서 값을 설정하면 약간의 성능상의 이점이 있습니다.생성자에서 설정한 경우 실제로는 두 번 설정됩니다(먼저 기본값으로 설정한 다음 CTor에서 재설정).

일반적으로 컨스트럭터는 의존관계를 취득하고 관련 인스턴스 멤버를 초기화하지 않고 작업을 수행하려고 합니다.만약 당신이 당신의 수업을 유닛 테스트 하고 싶다면, 이것은 당신의 삶을 더 쉽게 해줄 것입니다.

인스턴스 변수에 할당하는 값이 컨스트럭터에게 전달할 파라미터의 영향을 받지 않으면 선언 시 할당합니다.

베스트 프랙티스에 대한 질문에 대한 직접적인 답변은 아니지만, 중요하고 관련성이 있는 리프레시 포인트는 범용 클래스 정의의 경우 컴파일러에 남겨두고 기본값으로 초기화하거나 특수한 방법을 사용하여 필드를 기본값으로 초기화해야 한다는 것입니다(코드 가독성을 위해 반드시 필요한 경우).).

class MyGeneric<T>
{
    T data;
    //T data = ""; // <-- ERROR
    //T data = 0; // <-- ERROR
    //T data = null; // <-- ERROR        

    public MyGeneric()
    {
        // All of the above errors would be errors here in constructor as well
    }
}

범용 필드를 기본값으로 초기화하는 특별한 방법은 다음과 같습니다.

class MyGeneric<T>
{
    T data = default(T);

    public MyGeneric()
    {           
        // The same method can be used here in constructor
    }
}

논리나 오류 처리가 필요하지 않은 경우:

  • 선언 시 클래스 필드 초기화

논리 또는 오류 처리가 필요한 경우:

  • 생성자에서 클래스 필드 초기화

이것은 초기화 값을 사용할 수 있고 초기화를 한 줄에 넣을 수 있을 때 잘 작동합니다.단, 이 형식의 초기화에는 그 단순성으로 인해 제한이 있습니다.초기화에 몇 가지 논리(에러 처리나 복잡한 어레이를 채우기 위한 for 루프 등)가 필요한 경우 단순 할당은 불충분합니다.인스턴스 변수는 오류 처리 또는 기타 논리를 사용할 수 있는 생성자에서 초기화할 수 있습니다.

https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html 에서.

언급URL : https://stackoverflow.com/questions/24551/initialize-class-fields-in-constructor-or-at-declaration

반응형