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


413

최근에 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()
    {
        // .....
    }
}

참전 용사 중 일부가 최선의 방법이라고 생각합니다. 일관성 있고 하나의 접근 방식을 고수하고 싶습니다.


3
구조체의 경우 인스턴스 필드 이니셜 라이저를 사용할 수 없으므로 생성자를 사용할 수밖에 없습니다.
yoyo

답변:


310

내 규칙:

  1. 선언의 기본 값으로 초기화하지 마십시오 ( null, false, 0, 0.0...).
  2. 필드 값을 변경하는 생성자 매개 변수가없는 경우 선언에서 초기화를 선호하십시오.
  3. 생성자 매개 변수로 인해 필드 값이 변경되면 생성자에 초기화를 두십시오.
  4. 연습에서 일관성을 유지하십시오 (가장 중요한 규칙).

4
kokos는 컴파일러가 기본값 (0, false, null 등)으로 멤버를 초기화하지 않아야 함을 의미합니다. 컴파일러는이를 수행합니다 (1.). 그러나 기본값 이외의 다른 필드로 필드를 초기화하려면 선언 (2)에서 수행해야합니다. 나는 당신을 혼란스럽게하는 "default"라는 단어의 사용법일지도 모른다.
Ricky Helgesson

95
컴파일러가 초기화했는지 여부에 관계없이 기본값을 지정하지 않으면 규칙 1에 동의하지 않습니다. 개발자 가 특정 언어의 기본값이 무엇인지 추측 하거나 문서를 찾도록합니다. 가독성을 위해 항상 기본값을 지정합니다.
제임스

32
유형의 기본값 default(T)은 항상 내부 이진 표현이있는 값입니다 0.
Olivier Jacot-Descombes

15
규칙 1을 좋아하든 아니든, 읽기 전용 필드에는 사용할 수 없으며 생성자가 완료 될 때 명시 적으로 초기화 되어야합니다 .
yoyo

36
나는 규칙 1에 동의하지 않는 사람들에 동의하지 않습니다. 다른 사람들이 C # 언어를 배우기를 기대해도됩니다. 각 foreach루프에 "목록의 모든 항목에 대해 다음을 반복합니다"라는 주석 을 달지 않는 것처럼 C #의 기본값을 지속적으로 다시 지정할 필요는 없습니다. 또한 C #에 초기화되지 않은 의미가 있다고 가정 할 필요도 없습니다. 값이 없다는 것은 분명한 의미를 갖기 때문에 제외하는 것이 좋습니다. 명시 적으로 new작성 하는 것이 이상적이면 C # 1에서 요구 한대로 새 대리자를 만들 때 항상 사용해야 합니다. 그러나 누가 그렇게합니까? 이 언어는 양심적 인 코더를 위해 설계되었습니다.
Edward Brey

149

C #에서는 중요하지 않습니다. 당신이 제공하는 두 코드 샘플은 완전히 동일합니다. 첫 번째 예에서 C # 컴파일러 (또는 CLR?)는 빈 생성자를 생성하고 마치 마치 생성자에있는 것처럼 변수를 초기화합니다 (Jon Skeet이 아래 주석에서 설명하는 것과 약간의 차이가 있습니다). 이미 생성자가 있으면 "위"의 초기화가 맨 위로 이동합니다.

모범 사례의 관점에서 전자는 다른 생성자를 쉽게 추가하고 연결하는 것을 잊어 버릴 수 있기 때문에 전자가 후자보다 오류가 적습니다.


4
GetUninitializedObject로 클래스를 초기화하기로 선택한 경우에는 올바르지 않습니다. ctor에있는 것은 건드리지 않지만 필드 선언은 실행됩니다.
Wolf5

28
실제로 그것은 중요합니다. 기본 클래스 생성자가 파생 클래스에서 재정의되는 가상 메서드 (일반적으로 나쁜 아이디어이지만 발생할 수 있음)를 호출하는 경우 인스턴스 변수 이니셜 라이저를 사용하면 메서드가 호출 될 때 변수가 초기화됩니다. 생성자는 그렇지 않습니다. (인스턴스 변수 이니셜 라이저는 기본 클래스 생성자가 호출 되기 전에 실행 됩니다.)
Jon Skeet

2
정말? 나는 C # (제 2 판)을 통해 Richter의 CLR 에서이 정보를 얻었으며 이것이 핵심은 구문 설탕 (잘못 읽었을 수 있습니까?)이었고 CLR은 변수를 생성자에 막 혔습니다. 그러나 당신은 이것이 사실이 아니라고 말하고 있습니다. 즉, 기본 ctor에서 가상을 호출하고 문제의 클래스에서 재정의하는 미친 시나리오에서 멤버 초기화가 ctor 초기화 전에 시작될 수 있습니다. 내가 제대로 이해 했습니까? 방금 찾았 어? 5 년 전 게시물에 대한이 최신 의견에 대해 의아해합니다 (OMG는 5 년이 지났습니까?).
Quibblesome

@Quibblesome : 자식 클래스 생성자는 부모 생성자에 대한 체인 호출을 포함합니다. 부모 생성자가 모든 코드 경로에서 정확히 한 번만 호출되고 호출 전에 생성되는 객체가 제한적으로 사용되는 경우 언어 컴파일러는 원하는만큼 코드를 자유롭게 포함 할 수 있습니다. C #에 대한 나의 불만 중 하나는 필드를 초기화하는 코드와 생성자 매개 변수를 사용하는 코드를 생성 할 수 있지만 생성자 매개 변수를 기반으로 필드를 초기화하는 메커니즘을 제공하지 않는다는 것입니다.
supercat

1
값이 WCF 메서드에 할당되고 전달되면 @Quibblesome, 이전 문제가 문제가됩니다. 데이터를 모델의 기본 데이터 유형으로 재설정합니다. 가장 좋은 방법은 생성자 (나중에 하나)에서 초기화를 수행하는 것입니다.
Pranesh Janarthanan

16

C #의 의미는 여기서 Java와 약간 다릅니다. C #에서는 선언에서 선언이 수퍼 클래스 생성자를 호출하기 전에 수행됩니다. Java에서는 'this'를 사용할 수있게 된 직후에 수행되며 (익명 내부 클래스에 특히 유용함) 두 형식의 의미가 실제로 일치 함을 의미합니다.

가능하면 필드를 최종적으로 만드십시오.


15

하나의 경고가 있다고 생각합니다. 한 번 그런 오류를 저지른 적이 있습니다. 파생 클래스 내부에서 추상 기본 클래스에서 상속 된 필드를 "선언시 초기화"하려고했습니다. 그 결과 두 개의 필드 세트가 존재했습니다. 하나는 "기본"이고 다른 하나는 새로 선언 된 것이므로 디버그하는 데 시간이 꽤 걸립니다.

교훈 : 상속 된 필드 를 초기화 하려면 생성자 내에서 수행하십시오.


따라서 필드를로 참조하면 derivedObject.InheritedField기본 또는 파생 필드를 참조합니까?
RayLuo

6

예제에서 유형을 가정하면 생성자에서 필드를 초기화하는 것이 좋습니다. 예외적 인 경우는 다음과 같습니다.

  • 정적 클래스 / 메소드의 필드
  • 정적 / 최종 / et al로 입력 된 필드

나는 항상 클래스 상단의 필드 목록을 목차 (사용 방법이 아니라 여기에 포함 된 내용)로, 생성자를 소개로 생각합니다. 물론 방법은 장입니다.


왜 "확실히"그렇게됩니까? 추론을 설명하지 않고 스타일 기본 설정 만 제공했습니다. @quibblesome의 답변 에 따르면, 기다리지 말고 "완전히 동등한"것이므로 실제로 개인 스타일 선호도입니다.
RayLuo

4

내가 말하면 어떻게됩니까?

나는 일반적으로 모든 것을 초기화하고 일관된 방식으로 수행합니다. 예, 지나치게 명시 적이지만 유지 관리가 조금 더 쉽습니다.

우리가 성능에 대해 걱정한다면 잘해야 할 일만 초기화하고 가장 많은 돈을 벌 수있는 영역에 배치하십시오.

실시간 시스템에서는 변수 또는 상수가 필요한지 의문입니다.

그리고 C ++에서는 종종 어느 곳에서나 초기화하지 않고 Init () 함수로 옮깁니다. 왜? 글쎄, C ++에서 객체 생성 중에 예외를 던질 수있는 것을 초기화하면 메모리 누수가 발생합니다.


4

많은 상황이 있습니다.

빈 목록 만 있으면됩니다

상황은 분명하다. 내 목록을 준비하고 누군가가 목록에 항목을 추가 할 때 예외가 발생하지 않도록해야합니다.

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

3

Java에서 선언이있는 이니셜 라이저는 생성자가 사용되는 생성자 (둘 이상이있는 경우) 또는 생성자의 매개 변수 (인수가있는 경우)에 관계없이 필드가 항상 동일한 방식으로 초기화됨을 의미합니다. 최종 값이 아닌 경우 값을 변경하십시오. 따라서 선언자와 함께 이니셜 라이저를 사용하면 초기화 된 값 은 사용 된 생성자와 생성자에 전달 된 매개 변수에 관계없이 모든 경우에 필드가 갖는 값이라는 것을 독자에게 제안합니다 . 따라서 생성 된 모든 객체의 값이 동일한 경우에만 선언과 함께 이니셜 라이저를 사용하십시오.


3

C #의 디자인은 인라인 초기화가 선호되거나 언어에 있지 않을 것을 제안합니다. 코드의 다른 위치 사이의 상호 참조를 피할 수 있으면 일반적으로 더 좋습니다.

정적 필드 초기화와의 일관성 문제도 있으며, 이는 최상의 성능을 위해 인라인이어야합니다. 생성자 설계를 위한 프레임 워크 설계 지침 은 다음과 같이 말합니다.

✓ 런타임은 명시 적으로 정의 된 정적 생성자가없는 유형의 성능을 최적화 할 수 있기 때문에 정적 생성자를 명시 적으로 사용하지 않고 정적 필드를 초기화하는 것을 고려하십시오.

이 문맥에서 "고려"는 정당한 이유가없는 한 그렇게하는 것을 의미합니다. 정적 이니셜 라이저 필드의 경우 초기화가 너무 복잡하여 인라인으로 코딩 할 수없는 것이 좋습니다.


2

일관성을 유지하는 것이 중요하지만 이것이 바로 다른 질문에 대한 생성자가 있습니까?라는 질문입니다.

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

이 시나리오에서는 일반적으로 메서드 나 생성자가 없습니다. 목록을 초기화하는 독점적 인 목적을 위해 생성자를 만드는 것은 어리석은 일입니다. 특히 선언과 함께 목록을 초기화 할 수 있기 때문입니다.

다른 많은 사람들이 말했듯이, 그것은 당신의 사용법에 달려 있습니다. 간단하게 유지하고 불필요한 것을 추가하지 마십시오.


2

생성자가 둘 이상인 상황을 고려하십시오. 다른 생성자마다 초기화가 다릅니 까? 그것들이 동일하다면 왜 각 생성자에 대해 반복합니까? 이것은 kokos 문과 일치하지만 매개 변수와 관련이 없을 수 있습니다. 예를 들어, 객체가 어떻게 생성되었는지 보여주는 플래그를 유지하려고한다고 가정 해 봅시다. 그런 다음 생성자 매개 변수에 관계없이 다른 생성자에 대해 해당 플래그가 다르게 초기화됩니다. 반면에, 각 생성자에 대해 동일한 초기화를 반복하면 일부 생성자에서 의도하지 않게 초기화 매개 변수를 변경할 수 있지만 다른 생성자에서는 초기화 매개 변수가 변경 될 가능성이 있습니다. 따라서 기본 개념은 공통 코드는 공통 위치를 가져야하며 다른 위치에서 잠재적으로 반복되지 않아야한다는 것입니다.


1

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


2
C #에서 필드는 항상 기본값을 먼저 설정합니다. 이니셜 라이저가 존재해도 아무런 차이가 없습니다.
Jeffrey L Whitledge

0

나는 일반적으로 생성자를 시도하여 의존성을 얻고 관련 인스턴스 멤버를 초기화하는 것 외에는 아무것도하지 않습니다. 이것은 당신이 당신의 수업을 단위 테스트하려는 경우 인생을 더 쉽게 만들 것입니다.

인스턴스 변수에 할당 할 값이 생성자에게 전달할 매개 변수의 영향을받지 않으면 선언시 값을 할당하십시오.


0

모범 사례 에 대한 귀하의 질문에 대한 직접적인 대답은 아니지만 중요하고 관련된 새로 고침 요점은 일반 클래스 정의의 경우 기본값으로 초기화하기 위해 컴파일러에 그대로 두거나 필드를 초기화하기 위해 특별한 방법을 사용해야한다는 것입니다 기본값으로 (코드 가독성에 절대적으로 필요한 경우).

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
    }
}

0

로직이나 오류 처리가 필요하지 않은 경우 :

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

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

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

초기화 값을 사용할 수 있고 초기화를 한 줄에 넣을 수있을 때 잘 작동합니다. 그러나이 형식의 초기화는 단순하기 때문에 제한이 있습니다. 초기화에 일부 논리 (예 : 오류 처리 또는 복잡한 배열을 채우기위한 for 루프)가 필요한 경우 간단한 할당이 부적절합니다. 인스턴스 변수는 생성자에서 초기화 될 수 있으며, 여기서 오류 처리 또는 다른 논리를 사용할 수 있습니다.

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

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