Contents

생성자

클래스와 동일한 이름을 가지는 함수를 만들어 생성자를 선언할 수 있습니다. (선택적으로 명명된 생성자 에 명시되어 있는 식별자를 사용해도 됩니다.)

제너러티브 생성자는 생성자의 가장 흔한 형태로 새 인스턴스를 생성합니다:

class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // 인스턴스 변수를 초기화하는 더 나은 방법은
    // 형식 매개변수(formal parameters) 초기화를 참조하십시오.  
    this.x = x;
    this.y = y;
  }
}

this 키워드는 현재 인스턴스를 참조합니다.

형식 매개변수 초기화

생성자 인수를 인스턴스 변수에 할당하는 패턴은 자주 쓰입니다. Dart에서는 형식 매개변수 초기화를 사용하여 더 쉽게 수행합니다.

매개변수 초기화는 초기화 되어야만 하거나 기본 값이 주어져야하는 non-nullable 또는 final 인스턴스 변수에만 사용이 가능합니다.

class Point {
  final double x;
  final double y;

  // 생성자 바디가 실행되기 전에 x와 y 인스턴스 변수 설정.
  Point(this.x, this.y);
}

Initializing formal로 주어진 변수는 초기화 리스트 범위에서 암묵적으로 final 입니다.

디폴트 생성자

생성자를 선언하지 않았다면, 디폴트 생성자가 주어집니다. 디폴트 생성자는 인자가 없고, 인자가 없는 부모 클래스의 생성자를 호출합니다.

생성자는 상속되지 않습니다

자식 클래스는 부모 클래스로 부터 생성자를 상속받지 않습니다. 생성자를 선언하지 않은 자식 클래스는 이름과 인자가 없는 디폴트 생성자만을 가집니다.

명명된 생성자

다수의 생성자를 구현하거나, 코드의 명확성을 더하고 싶다면 명명된 생성자를 사용하세요:

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

부모 클래스의 생성자는 자식 클래스로 상속되지 않는다는 것을 꼭 기억하세요. 자식 클래스에서 부모 클래스와 같은 명명된 생성자를 사용하고 싶다면, 자식 클래스에서도 똑같이 구현해야 합니다.

부모 클래스의 Non-default 생성자 호출

자식 클래스의 생성자는 부모 클래스의 이름이 없고(unnamed), 인수가 없는(no-argument) 생성자를 디폴트로 호출합니다. 부모 클래스의 생성자는 자식 클래스 생성자 바디의 처음에 호출됩니다. 이니셜라이저 리스트가 사용되면, 부모 클래스가 호출되기 전에 실행됩니다. 요약하자면, 실행 순서는 다음과 같습니다:

  1. 이니셜라이저 리스트
  2. 부모 클래스의 인자가 없는 생성자
  3. 메인 클래스의 인자가 없는 생성자

부모 클래스가 이름과 인자가 없는 생성자를 가지고 있지 않는다면, 반드시 부모 클래스의 생성자 중 하나를 선택해서 호출해야 합니다. 생성자 바디에 콜론(:)을 추가해 선택한 부모 클래스의 생성자를 명시하세요.

다음 예제에서 Employee는 자신의 부모 클래스인 Person의 명명된 생성자를 호출합니다. 코드를 실행하고 싶다면 Run을 클릭하세요.

class Person {
  String? firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person은 디폴트 생성자가 없습니다;
  // super.fromJson()를 반드시 호출해야합니다.
  Employee.fromJson(super.data) : super.fromJson() {
    print('in Employee');
  }
}

void main() {
  var employee = Employee.fromJson({});
  print(employee);
  // Prints:
  // in Person
  // in Employee
  // Instance of 'Employee'
}

생성자가 실행되기 전에 부모 클래스의 생성자로 전해지는 인자가 평가되기 때문에 인자는 함수 호출처럼 표현식이 될 수 있습니다:

class Employee extends Person {
  Employee() : super.fromJson(fetchDefaultData());
  // ···
}

Super 매개변수

수동으로 부모 클래스의 생성자 매개변수를 넘겨주는 것을 피하고 싶다면, super 이니셜라이저 매개변수를 부모 클래스의 생성자로 넘겨주면 됩니다. 이 피처를 리다이렉팅 생성자와 함께 사용하는 것은 불가능합니다. Super 이니셜라이저 매개변수는 형식 매개변수 초기화와 비슷한 문법과 의미를 가집니다:

class Vector2d {
  final double x;
  final double y;

  Vector2d(this.x, this.y);
}

class Vector3d extends Vector2d {
  final double z;

  // 매개변수 x와 y를 디폴트 super 생성자로 넘겨줍니다:
  // Vector3d(final double x, final double y, this.z) : super(x, y);
  Vector3d(super.x, super.y, this.z);
}

Super 생성자 호출이 positional 인자를 가지고 있다면, Super 이니셜라이저 매개변수는 positional이 될 수 없지만 명명된 매개변수는 언제나 가능합니다:

class Vector2d {
  // ...

  Vector2d.named({required this.x, required this.y});
}

class Vector3d extends Vector2d {
  // ...

  // 매개변수 y를 명명된 super 생성자로 넘겨줍니다:
  // Vector3d.yzPlane({required double y, required this.z})
  //       : super.named(x: 0, y: y);
  Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}

이니셜라이저 리스트

생성자 바디가 실행되기 전에 부모 클래스의 생성자를 호출할 뿐만 아니라 인스턴스 변수를 초기화할 수도 있습니다. 이니셜라이저는 쉼표로 구분합니다.

// 이니셜라이저 리스트는 생성자 바디가 실행되기 전에 인스턴스 변수를 설정합니다.
Point.fromJson(Map<String, double> json)
    : x = json['x']!,
      y = json['y']! {
  print('In Point.fromJson(): ($x, $y)');
}

개발하는 동안 assert를 이니셜라이저 리스트 안에 넣어서 입력에 조건을 추가 할 수 있습니다.

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

이니셜라이저 리스트는 final 필드를 설정 할 때 유용합니다. 다음 예제에서는 세 개의 final 필드를 이니셜라이저 리스트로 초기화합니다. Run을 클릭해 코드를 실행하세요.

import 'dart:math';

class Point {
  final double x;
  final double y;
  final double distanceFromOrigin;

  Point(double x, double y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

void main() {
  var p = Point(2, 3);
  print(p.distanceFromOrigin);
}

리다이렉팅 생성자

생성자의 목적이 오직 같은 클래스 내의 다른 생성자로의 리다이렉트(redirect)인 경우가 있습니다. 리다이렉팅 생성자의 바디는 비어있고 콜론 (:) 뒤에 나오며 클래스 이름 대신 this를 사용한 생성자 호출로 구성됩니다.

class Point {
  double x, y;

  // 클래스의 메인 생성자.
  Point(this.x, this.y);

  // 메인 생성자로 리디렉트.
  Point.alongXAxis(double x) : this(x, 0);
}

상수 생성자

어떤 클래스가 절대 바뀌지 않는 객체를 생성한다면, 이 객체를 컴파일 타임 상수로 만들 수 있습니다. 생성자를 const로 정의하고 모든 인스턴스 변수를 final로 선언하면 됩니다.

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

상수 생성자가 항상 상수를 생성하는 건 아닙니다. 더 자세히 알고 싶다면, 생성자 사용하기를 참고하세요.

Factory 생성자

항상 클래스의 새로운 인스턴스를 생성하지 않는 생성자를 구현하고 싶다면, factory 키워드를 사용하세요. 예를 들어, factory 생성자는 인스턴스를 캐시에서 반환하거나 서브타입의 인스턴스를 반환할 수 있습니다. Factory 생성자는 final 변수를 초기화 리스트에서 다루지 않는 로직을 사용하여 초기화하는 방법으로도 사용할 수 있습니다.

다음 예제에서 Logger factory 생성자는 캐시에서 객체를 반환하고, Logger.fromJson factory 생성자는 final 변수를 JSON 객체로 부터 초기화 합니다.

class Logger {
  final String name;
  bool mute = false;

  // _cache는 맨 앞의 _ 덕분에 library-private입니다.
  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

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

다른 생성자를 호출 할 때 처럼 factory 생성자를 호출하세요:

var logger = Logger('UI');
logger.log('Button clicked');

var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);