생성자

생성자란 무엇인가

생성자는 클래스 인스턴스를 생성하기 위한 특수 함수로, 주로 속성 초기화에 사용됩니다. 기본 생성자를 제외한 모든 생성자는 클래스명과 동일한 이름을 가집니다.

Dart 생성자 분류:

  1. 생성형 생성자 (Generative)
  2. 기본 생성자 (Default)
  3. 명명 생성자 (Named)
  4. 상수 생성자 (Const)
  5. 리다이렉트 생성자 (Redirecting)
  6. 팩토리 생성자 (Factory)
  7. 리다이렉트 팩토리 생성자
  8. 간결한 생성자 구문 (Dart 3.13+)
  9. 주 생성자 (Primary Constructors, 간략 필드 + 생성자 구문)

1. 생성형 생성자 Generative Constructor

가장 기본적인 생성자로 인스턴스를 생성하고 멤버 변수를 초기화합니다. 초기화 매개변수 this.변수를 지원해 할당 로직을 간소화할 수 있습니다.

class Point {
  double x;
  double y;

  // 생성형 생성자, this.x로 매개변수를 멤버에 직접 할당
  Point(this.x, this.y); // x, y에 직접 값을 할당
}

void main() {
  final p = Point(10, 20);
  print("(${p.x}, ${p.y})"); // (10, 20)
}
Code language: JavaScript (javascript)

2. 기본 생성자 Default Constructor

클래스에 직접 생성자를 정의하지 않으면 Dart가 매개변수 없고 이름 없는 생성형 생성자를 자동 제공합니다. C++, Java, C# 등 다른 C 계열 언어도 동일한 규칙입니다.

class PointA {
  double x = 0;
  double y = 0;
  // 암시적 기본 생성자: PointA();
}

void main() {
  final p = PointA(); // 기본 생성자가 자동 호출됨
  print("(${p.x}, ${p.y})"); // (0.0, 0.0)
}
Code language: PHP (php)

주의: 아무 생성자라도 직접 작성하는 순간 기본 생성자는 사라집니다.

3. 명명 생성자 Named Constructor

용도: 하나의 클래스에 의미가 명확한 여러 생성자를 정의할 때 사용. 문법 형식 클래스명.생성자명()

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // 주 생성형 생성자
  Point(this.x, this.y);

  // 명명 생성자: 원점
  Point.origin() : x = xOrigin, y = yOrigin;
}

void main() {
  final p1 = Point(5, 6);
  final p2 = Point.origin(); // 명명 생성자 호출
  print(p1.x); //5
  print("원점: (${p2.x}, ${p2.y})"); // (0,0)
}
Code language: PHP (php)

중요 규칙

  • 서브클래스는 상위 클래스의 명명 생성자를 상속받지 않습니다; 서브클래스에 동일한 명명 생성자가 필요하면 직접 구현해야 합니다;
  • 명명 생성자는 초기화 리스트로 final 변수에 값을 할당하는 것을 지원합니다.

4. 상수 생성자 Const Constructor

컴파일 타임 상수 객체를 생성할 때 사용하며 만족해야 하는 조건은 다음과 같습니다:

  1. 모든 멤버 변수에 final 수식자를 붙여야 함
  2. 생성자 앞에 const 키워드를 붙이고, 인스턴스 생성 시 const를 사용하면 상수 캐시를 재사용합니다. const를 생략하면 별도의 일반 객체가 생성됩니다.
class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0); // 클래스 전체 공유 정적 상수 객체
  final double x, y;

  // 상수 생성자는 final로 선언된 x, y를 초기화할 수 있음
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // 컴파일 상수로 동일 객체 재사용
  const p1 = ImmutablePoint(1, 2);
  const p2 = ImmutablePoint(1, 2); // 전달 매개변수가 완전히 같아야 같은 인스턴스 공유
  print(p1 == p2); // true

  // 일반 인스턴스로 새로운 별개 객체 생성
  final p3 = ImmutablePoint(1, 2);
  print(p1 == p3); // false
}Code language: PHP (php)

5. 리다이렉트 생성자 Redirecting Constructor

: this(매개변수) 구문으로 동일 클래스의 다른 생성자로 실행을 넘기는 생성자로, 함수 본문은 비워야 합니다.

class Point {
  double x, y;

  // 주 생성자
  Point(this.x, this.y);

  // 리다이렉트 생성자: x만 전달하고 y는 0으로 고정, 주 생성자로 전달
  Point.alongX(double x) : this(x, 0); // 명명 생성자를 호출한 뒤 x를 주 생성자에 넘기고 y=0 고정
}

void main() {
  final p = Point.alongX(9);
  print("x=${p.x}, y=${p.y}"); // x=9, y=0
}
Code language: JavaScript (javascript)

6. 팩토리 생성자 Factory Constructor

factory 키워드로 표시하며 활용 시나리오는 다음과 같습니다:

  1. 항상 새 객체를 만들지 않음 (캐시 재사용, 서브클래스 인스턴스 반환)
  2. 객체 생성 전 복잡한 로직 실행 (JSON 파싱, 매개변수 검증). 제약사항: this에 접근할 수 없음
캐시 싱글톤 Logger 예시
class Logger {
  final String name;
  bool mute = false;
  // 라이브러리 내부에서만 접근 가능한 비공개 캐시
  static final Map<String, Logger> _cache = {};

  // 팩토리 생성자: 우선 캐시에서 조회
  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  // 팩토리 생성자: JSON 파싱으로 인스턴스 생성
  factory Logger.fromJson(Map<String, dynamic> json) {
    return Logger(json["name"].toString());
  }

  // 내부 비공개 생성형 생성자
  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

void main() {
  final log1 = Logger("UI");
  final log2 = Logger("UI");
  print(log1 == log2); // true, 동일 캐시 객체

  final jsonLog = Logger.fromJson({"name": "Net"});
  jsonLog.log("request success");
}
Code language: JavaScript (javascript)

7. 리다이렉트 팩토리 생성자 Redirecting Factory

문법 factory 이름() = 다른클래스.생성자;. 추상 클래스에서 인스턴스 생성을 위임할 때 유리하며 매개변수 중복 작성을 피할 수 있습니다.

abstract class Listenable {}

class _MergingListenable implements Listenable {
  List<_MergingListenable> list;
  _MergingListenable(this.list);
}

// 내부 비공개 클래스 생성자로 연결하는 리다이렉트 팩토리
factory Listenable.merge(List<Listenable> list) = _MergingListenable;
Code language: PHP (php)

8. 생성자 분리 (Constructor Tear-offs)

괄호 () 없이 생성자를 직접 함수 인자로 전달할 수 있으며, 불필요한 람다 익명 함수를 대체해 성능이 더 좋습니다.

권장 작성법
void main() {
  final charCodes = [65, 66, 67];
  // 명명 생성자 String.fromCharCode
  final strs = charCodes.map(String.fromCharCode);
  print(strs); // (A, B, C)

  // .new를 이용한 무명 생성자 분리
  final buffers = charCodes.map(StringBuffer.new);
}
Code language: PHP (php)
비권장: 불필요한 람다
// 불필요한 감싸기, 사용하지 마세요
charCodes.map((code) => String.fromCharCode(code));
Code language: JavaScript (javascript)

9. Dart 3.13+ 간결 생성자 구문 Concise Constructor

클래스 내부에서 클래스명을 생략하고 new / factory만으로 생성자를 정의할 수 있어 클래스명.xxx를 적지 않아도 됩니다:

기존 구문간략 구문
Point(this.x,this.y)new(this.x,this.y)
Point.origin():x=0,y=0new origin():x=0,y=0
factory Point.clone()factory clone()
class Point {
  double x, y;

  // 간략 무명 생성형 생성자
  new(this.x, this.y);

  // 간략 명명 생성형 생성자
  new origin() : x = 0, y = 0;

  // 간략 팩토리 생성자
  factory clone(Point other) => new(other.x, other.y);
}

void main() {
  final p = new(3, 4);
  final origin = new origin();
  final copy = p.clone(p);
}
Code language: PHP (php)

10. 인스턴스 변수 초기화 세 가지 방식

1: 선언 시 직접 할당
class PointA {
  double x = 1.0;
  double y = 2.0;
}
Code language: JavaScript (javascript)

2: 초기화 매개변수

this.변수

선택적 위치 매개변수, 명명 매개변수, 널 허용 변수를 지원합니다

class PointB {
  final double x;
  final double y;

  // 선택적 위치 매개변수
  PointB.opt([this.x = 0, this.y = 0]);

  // 기본값이 있는 명명 매개변수
  PointB.named({this.x = 5, this.y = 5});
}
Code language: JavaScript (javascript)

3: 초기화 리스트 Initializer List

콜론 : 뒤에 할당 로직을 작성하며, 실행 순서가 생성자 본문보다 먼저 입니다. this에 접근할 수 없으며 assert로 매개변수 검증을 지원합니다.

class Point {
  final double x;
  final double y;

  Point.fromJson(Map<String, double> json)
      : x = json["x"]!,
        y = json["y"]! {
    print("생성자 본문 실행, x=$x");
  }

  // assert 검증 포함
  Point.withAssert(this.x, this.y) : assert(x >= 0);
}
Code language: JavaScript (javascript)

11. 비공개 필드 직접 초기화 매개변수 (Dart3.12+)

밑줄로 시작하는 비공개 변수의 경우 생성자 매개변수를 this._필드로 작성하고, 외부 호출 시 밑줄 없는 공개 이름을 사용합니다.

class Point {
  final double _x;
  final double _y;

  // 내부는 _x, 외부 호출 시 x:10으로 작성
  Point({required this._x, this._y = 0});
}

void main() {
  final p = Point(x: 10); // _x가 아닌 x만 적으면 됨
  print(p._x); // 10
}
Code language: PHP (php)

초기화 리스트 안에서는 비공개 변수 본래 이름을 사용합니다:

class Point {
  final double _x;
  Point({required this._x}) : assert(_x > 0);
}
Code language: JavaScript (javascript)

12. 상위 클래스 생성자와 상속 규칙

  1. 서브클래스는 상위 클래스의 어떤 생성자도 상속받지 않습니다;
  2. 생성자 실행 순서: 초기화 리스트 → 상위 클래스 생성자 → 서브클래스 생성자 본문;
  3. 상위 클래스에 매개변수 없는 기본 생성자가 없으면 서브클래스에서 직접 super()를 호출해 상위 생성자를 지정해야 합니다.
기본 super 호출 예시

Java와 동일하게 super로 기본 클래스를 참조하고, C#에서는 base를 사용합니다.

class Person {
  final String name;
  Person(this.name);
  Person.fromMap(Map map) : name = map["name"];
}

class Employee extends Person {
  int id;
  // 상위 클래스 명명 생성자 호출
  Employee(Map data) : super.fromMap(data), id = data["id"];
}
Code language: JavaScript (javascript)

주의: super 매개변수 표현식 안에서는 this를 사용할 수 없습니다.

Super 매개변수 (Dart2.17+, 상위 클래스 매개변수 전달 간소화)

super.변수를 사용하면 직접 상위 생성자에 매개변수를 전달할 수 있어 수동으로 super(x,y)를 적지 않아도 됩니다.

class Vector2d {
  final double x, y;
  Vector2d(this.x, this.y);
}

class Vector3d extends Vector2d {
  final double z;
  // super.x super.y가 자동으로 상위 생성자에 전달됨
  Vector3d(super.x, super.y, this.z);
}

// 상위 명명 생성자 + super 명명 매개변수
class Vector3dYZ extends Vector2d {
  final double z;
  Vector3dYZ({required super.y, required this.z}) : super.named(x: 0);
}
Code language: JavaScript (javascript)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다