확장 메서드는 기존 타입, 서드파티 라이브러리 타입에 새 기능을 추가할 수 있으며 원본 클래스 소스 코드를 수정할 필요가 없습니다. IDE 코드 자동 완성에서 네이티브 메서드와 함께 확장 메서드가 표시됩니다.
다른 사람이 제공한 API는 수정할 수 없지만 편의용 메서드를 추가하고 싶을 때 활용합니다.
요구사항 예시: int.parse('42') 대신 '42'.parseInt()를 사용하고 싶은 경우
문법 구조
extension 확장이름? on 대상타입 {
// 메서드, getter, setter, 연산자, 정적 멤버
}Code language: JavaScript (javascript)
예제
// 모든 코드를 한 파일에 작성, 분리할 필요 없고 main이 바로 실행됨
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
double parseDouble() {
return double.parse(this);
}
}
void main() {
print('42'.parseInt()); // 42
print('3.14'.parseDouble()); // 3.14
print('100'.padLeft(6)); // 기본 String 메서드와 비교
}Code language: Dart (dart)
실행 결과
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
42
3.14
100Code language: CSS (css)
확장 이름은 선택 사항입니다. this는 확장을 호출한 인스턴스 객체를 의미합니다.
확장에서 지원하는 멤버 종류
확장 내부에 인스턴스 메서드, getter, setter, 연산자, 정적 멤버를 정의할 수 있습니다.
extension StringExt on String {
// getter
bool get isEmptyOrBlank => trim().isEmpty;
// 인스턴스 메서드
String repeat(int count) => List.filled(count, this).join();
// 연산자 오버로딩
String operator *(int count) => repeat(count);
// 정적 멤버 (확장 이름으로 접근)
static String emptyStr = '';
static void printTip() => print("문자열 확장 정적 메서드");
}
void main() {
var str = " dart ";
print(str.isEmptyOrBlank);
print("hi" * 3);
print("hello".repeat(2));
// 정적 멤버 접근
print(StringExt.emptyStr);
StringExt.printTip();
}Code language: PHP (php)
실행 결과
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
false
hihihi
hellohello
문자열 확장 정적 메서드Code language: CSS (css)
이름 없는 확장(익명 확장)
확장 이름을 적지 않으면 현재 라이브러리 내에서만 접근 가능하며 명시적 호출로 이름 충돌을 해결할 수 없습니다.
외부에서 확장이름(인스턴스) 구문으로 호출할 수 없습니다.
extension on String {
bool get isBlank => trim().isEmpty;
}
void main() {
print(" ".isBlank); // true
}Code language: Dart (dart)
익명 확장 안의 정적 멤버는 확장 내부에서만 사용 가능합니다.
제네릭 확장(Generic extensions)
List<T>와 같은 제네릭 타입에 확장을 구현할 수 있습니다.
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
void main() {
List<int> list = [1, 2, 3, 4];
print(list.doubleLength); // 8
print(-list); // [4,3,2,1]
print(list.split(2)); // [[1,2],[3,4]]
}Code language: Dart (dart)
실행 결과
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
8
[4, 3, 2, 1]
[[1, 2], [3, 4]]Code language: CSS (css)
정적 타입(static) vs dynamic
dynamic 변수에서는 확장 메서드를 호출할 수 없음
확장 메서드는 정적 해석 방식으로 처리됩니다. 런타임에 타입이 결정되는 dynamic 변수는 확장을 찾지 못합니다:
void main() {
dynamic d = '2';
// print(d.parseInt()); // 런타임에서 NoSuchMethodError 발생
var v = '2';
print(v.parseInt()); // 타입 추론으로 String으로 인식되어 정상 실행
}
extension NumberParsing on String {
int parseInt() => int.parse(this);
}Code language: JavaScript (javascript)
원인: 확장 메서드는 컴파일 시점에 변수의 정적 타입 기준으로 바인딩되며 런타임에 탐색하지 않기 때문입니다.
API 이름 충돌 해결 방안
여러 확장에 동일한 이름의 멤버가 존재할 때 사용하는 세 가지 방법입니다.
1: import 시 show / hide로 충돌하는 확장 가리기
// string_apis.dart에 NumberParsing 존재
// string_apis_2.dart에 NumberParsing2, 동일하게 parseInt 구현
import 'string_apis.dart';
import 'string_apis_2.dart' hide NumberParsing2;
void main() {
print('42'.parseInt()); // string_apis.dart의 구현 사용
}Code language: Dart (dart)
2: 확장을 명시적으로 호출(래퍼 구문)
import 'string_apis.dart'; // NumberParsing
import 'string_apis_2.dart'; // NumberParsing2
void main() {
// print('42'.parseInt()); // 충돌로 컴파일 오류
print(NumberParsing('42').parseInt());
print(NumberParsing2('42').parseInt());
}Code language: Dart (dart)
3: 라이브러리 가져올 때 접두사 사용
두 확장 이름이 완전히 같을 경우 접두사로 구분합니다:
import 'string_apis.dart';
import 'string_apis_3.dart' as rad;
void main() {
print(NumberParsing('42').parseInt());
print(rad.NumberParsing('42').parseInt());
// 충돌 없는 확장 메서드는 암시적 호출 가능
print('42'.parseNum());
}Code language: Dart (dart)
확장 메서드 (Extension methods)