강의를 듣다 보면 자주 쓰이는 코드의 형태가 있다. 그렇지만 이게 도대체 무엇인지는 모른다.
그래서 자세하고 낱낱이 파헤쳐보자. 오늘 파헤칠 것은 `const 생성자`이다.
사전 지식
컴파일 타임(Compile Time)
- 런 타임이 되기 전 사람이 작성한 코드(Dart, C++, Java 등)를 컴퓨터가 이해할 수 있는 기계어로 바꾸는 시간(순간, 과정)이다.
- 변수 선언이 잘못되거나 문법이 틀리면 컴파일 타임 때 에러를 발견한다.
void main() {
int number = "Hello"; // ❌ 컴파일 에러 중 타입 에러(문자열을 숫자 변수에 할당)
}
런 타임(Run Time)
- 컴파일 타임이 끝난 후 프로그램이 실제로 실행되는 시간이다.
- 컴파일 에러가 없더라도 런 타임에는 에러가 생길 수도 있고, 사용자가 입력을 잘못하면 런 타임 때 에러가 발생한다.
void main() {
List<int> numbers = [1, 2, 3];
// ❌ 런타임 에러 발생 (범위를 벗어난 인덱스)
print(numbers[5]);
}
- `print(numbers[5]);` → 컴파일 타임일 때는 에러가 발생하지 않지만 실제 프로그램이 실행되는 런 타임에서는 에러를 일으킨다.
- 컴파일 타임에서는 List의 요소가 몇 개인지 파악을 하지 못하기 때문에 컴파일 에러를 발생시키지 않는다.
const 키워드
dart에서 `const` 키워드는 상수를 만들기 위해 사용하며 js, ts와 달리 변수의 초기화를 컴파일 타임(Compile Time)때 진행한다.
void main() {
const Map<String, String> p1 = {'name': 'lee'};
const Map<String, String> p2 = {'name': 'lee'};
print('p1 == p2 : ${p1 == p2}'); // true (같은 객체로 취급됨)
}
- `const` 키워드로 선언한 변수에 같은 값을 할당했다면 런 타임이 됐을 때 값이 같다고 취급한다.
const 생성자
Dart에서 `const 생성자`는 객체를 불변(immutable)하게 만들어주는 특별한 생성자다.
일반 생성자와 다르게 값이 변하지 않는다면 메모리를 절약할 수 있는 장점이 있다.
사용 이유
class Person {
String name;
Person(this.name);
}
void main() {
var p1 = Person("lee");
var p2 = Person("lee");
print(p1 == p2); // false (서로 다른 객체)
const p3 = Person('lee'); // 컴파일 에러
const p4 = Person('lee'); // 컴파일 에러
}
- `p1`과 `p2`는 `{name: 'lee'}`로 같은 값을 가지지만, 서로 다른 객체로 생성된다. 즉, 매번 새로운 메모리를 차지하게 된다.
- `const` 키워드로 선언한 변수에 동일한 값을 할당하면 서로 같다고 취급된다. 하지만 `p3`와 `p4`에 `const`를 적용하면 컴파일 에러가 발생한다.
- `Person('lee');`는 호출이다. 함수나 메서드, 클래스의 호출은 런 타임일 때 호출이 된다.
- const p3 = Person('lee');에서 const를 사용했기 때문에 컴파일 타임에 변수를 초기화해야 한다. 하지만 Person('lee');는 런타임에 호출되므로 컴파일 타임에서 초기화할 수 없어 컴파일 에러가 발생한다.
그런데 `const 생성자`를 사용하면 클래스 호출로 인한 컴파일 에러를 방지할 수 있고 값이 같을 때는 같은 객체로 취급하여 메모리를 절약할 수 있다.
사용법
class Person {
final String name;
const Person(this.name);
}
void main() {
// 클래스에 const 키워드를 붙여 호출하거나
// const 키워드를 붙여 변수를 선언한다.
var p1 = const Person("lee");
var p2 = const Person("lee");
const p3 = Person('lee');
print(p1 == p2); // true (같은 객체로 취급됨)
print(p2 == p3); // true (같은 객체로 취급됨)
}
- `final String name;` → 불변성을 보장하기 위해 클래스 내의 모든 멤버 변수는 `final`이어야 한다.
- `const Person(this.name);` → `const` 키워드를 붙여 생성자를 정의한다.
- `const Person("lee");` → 클래스에 `const` 키워드를 붙여 호출한다.
- `const p3 = Pserson('lee');` → `const` 키워드를 붙여 변수를 선언한다.
class Person {
final String name;
const Person(this.name);
@override
String toString() {
return '{name: $name}';
}
}
class Person2 {
final String name;
const Person2(this.name);
@override
String toString() {
return '{name: $name}';
}
}
void main() {
const Map<String, String> p1 = {'name': 'lee'};
const Map<String, String> p2 = {'name': 'lee'};
const p3 = Person('lee');
const p4 = Person('lee');
const p5 = Person2('lee');
print(identical(p1, p2)); // true
print(identical(p3, p4)); // true
print(identical(p1, p3)); // false
print(identical(p3, p5)); // false
}
- `print(identical(p1, p3));` → 값이 같아도 맵으로 만든 객체와 클래스로 만든 객체는 서로 다르다.
- `print(identical(p3, p5));` → 값이 같아도 생성자가 다르면 즉, 클래스가 다르면 서로 다르다.
느낀 점
책을 읽으면 저자의 생각을 알 수 있다고 하던가?
나도 이제 코드에서 클래스에 `const`를 붙여 호출하는 개발자의 의도를 알게 된 것 같다.
Flutter의 `StatefulWidget`을 사용할 때 상태가 변하면 `build()`가 매번 호출 될텐데 그때마다 같은 형태의 객체를 생성한다면 앱의 성능이 저하될 것을 예측할 수 있게 되어 기쁘다.
'TIL(Today I Learned)' 카테고리의 다른 글
[TIL-006] Dart로 콘솔 쇼핑몰 만들기-1 (0) | 2025.03.10 |
---|---|
[TIL-005] Flutter 위젯 생명 주기(Widget Life Cycle) (0) | 2025.03.07 |
[TIL-004] 내가 Dart, Flutter 중에 무엇을 사용하고 있는 것일까? (0) | 2025.03.06 |
[TIL-002] Flutter 위젯은 쌓기 놀이 (0) | 2025.03.05 |
[TIL-001] Dart 기본 문법은 JS와 비슷했다 (1) | 2025.03.04 |