생성자란 무엇인가
생성자는 클래스 인스턴스를 생성하기 위한 특수 함수로, 주로 속성 초기화에 사용됩니다. 기본 생성자를 제외한 모든 생성자는 클래스명과 동일한 이름을 가집니다.
Dart 생성자 분류:
- 생성형 생성자 (Generative)
- 기본 생성자 (Default)
- 명명 생성자 (Named)
- 상수 생성자 (Const)
- 리다이렉트 생성자 (Redirecting)
- 팩토리 생성자 (Factory)
- 리다이렉트 팩토리 생성자
- 간결한 생성자 구문 (Dart 3.13+)
- 주 생성자 (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
컴파일 타임 상수 객체를 생성할 때 사용하며 만족해야 하는 조건은 다음과 같습니다:
- 모든 멤버 변수에
final수식자를 붙여야 함 - 생성자 앞에
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 키워드로 표시하며 활용 시나리오는 다음과 같습니다:
- 항상 새 객체를 만들지 않음 (캐시 재사용, 서브클래스 인스턴스 반환)
- 객체 생성 전 복잡한 로직 실행 (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=0 | new 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. 상위 클래스 생성자와 상속 규칙
- 서브클래스는 상위 클래스의 어떤 생성자도 상속받지 않습니다;
- 생성자 실행 순서: 초기화 리스트 → 상위 클래스 생성자 → 서브클래스 생성자 본문;
- 상위 클래스에 매개변수 없는 기본 생성자가 없으면 서브클래스에서 직접
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)