기본
// 1. dart는 모든 것들이 1급 객체이다.
// 2. heap, stack, static
//Heap: 동적 메모리 할당 영역. 객체가 여기에 저장
//Stack: 함수 호출과 관련된 지역 변수들이 저장되는 영역
//Static: 정적 메모리 영역. 클래스 변수 등이 여기에 저장
int n1 = 1;
double d1 = 10.1;
bool b1 = true;
String s1 = "홍길동";
var n2 = 10; // 타입 추론
dynamic n3 = 20; // 오브젝트 타입
void main() {
print("n1 ${n1.runtimeType}"); // 타입 확인
print("d1 ${d1}");
print("b1 ${b1}");
print("s1 ${s1}");
// n2 = 10.5; 생성시 int 형이 되기 때문에 double 형으로 바꿀 수 없다.
n3 = 20.5; // 가능
}
- 클래스 외부에 적게 되면 모두 static 영역에 띄워집니다.
- var 타입은 모든 타입을 받을 수 있으며, 생성될 때 타입이 결정됩니다. 위의 예제에서는 int형으로 결정될 것이며 당연히 double 형으로 바꿀 수 없습니다.
Dynamic 타입 설명
dynamic
타입은 Dart에서 매우 유연하게 사용할 수 있는 데이터 타입으로, 변수의 타입을 런타임에 결정합니다. 다음은 dynamic 타입에 대한 상세 설명입니다:- 모든 타입 할당 가능:
dynamic
타입은 숫자, 문자열, 객체 등 모든 타입을 할당할 수 있습니다. 이는 변수가 선언될 때부터 런타임까지 타입이 변경될 수 있음을 의미합니다.
- 런타임에 타입 결정:
dynamic
타입의 변수는 실행 도중에 할당되는 값에 따라 타입이 결정됩니다. 이는 컴파일 타임에 타입 검사가 이루어지지 않음을 의미합니다.
- 유연한 타입 변경:
dynamic
타입 변수는 다른 타입의 값을 할당할 수 있으며, 이는 코드의 유연성을 크게 높입니다. 예를 들어, 처음에는int
를 할당하고 나중에는double
이나String
을 할당할 수 있습니다.
- 타입 검사 및 경고 없음:
dynamic
타입을 사용할 때는 컴파일러가 타입 검사를 하지 않기 때문에 타입 오류가 발생할 가능성이 높아집니다. 따라서, 동적인 타입 할당을 필요로 하는 경우에만 신중하게 사용해야 합니다.
Null Safety란?
널 세이프티는 개발자가 널 에러를 피할 수 있도록 도와주는 다트 프로그래밍 언어의 기능입니다.
‘사운드 널 세이프티 인 다트’ 라고 불리며, 이를 통해 개발자는 코드 작성 시점에 널 에러를 잡을 수 있습니다.
Sound Null Safety in dart : 런타임 중에 null 포인터 예외를 방지하기 위해 Dart 컴파일러가 코드를 분석하고 컴파일할 때 타입 시스템에서 엄격한 규칙을 적용하는 것을 의미
void main() {
String name = "Jhon"; // 이 name이라는 변수는 null이 아닌 문자열만 가질 수 있다.
int age = 30; // null 이 아닌 정수값만 가질 수 있다.
String? nullableName; // 이 변수는 문자열 또는 null 값을 가질 수 있다.
int? nullabelInt; // 이 변수는 정수값 또는 null을 가질 수 있다.nullabelInt
// 방어적 코드
if (nullableName != null) {
print("name : ${nullableName}");
}
String? username = null; // null을 넣고 싶으면 물음표를 달아야한다.
print(username); // null 출력
print(username == null ? "홍길동" : username); // 홍길동 출력
print(username ?? "홍길동"); // 홍길동 출력
}
- null도 받고 싶다면 타입 뒤에 물음표를 추가하여야 하고, 기존의 일반적인 타입은 null을 받을 수 없습니다.
- 마지막줄은 null 대체 연산자 입니다 (아직 정확한 이름이 안정해진 것으로 알고 있습니다).
만약 username이 null이라면 “홍길동” 문자열을 출력합니다.
null 억제 연산자
void main() {
String? name = "John";
// String? name = null;
String nameNotNullable = name!;
print("name : $name"); // John 출력
}
익명함수와 람다 표현식
// 익명 함수
Function k = () {
print(1);
return 1;
};
Function h = () => 1; // 람다 표현식 : 1을 리턴하기 때문
void main() {
int result = k();
print(result);
}
- 익명 함수:
Function k = () {
print(1);
return 1;
};
k
는 숫자 1
을 출력한 후 1
을 반환하며 익명 함수라고 합니다.- 람다 표현식:
Function h = () => 1; // 람다 표현식 : 1을 리턴하기 때문
h
는 람다 표현식으로 작성된 함수로, 1
을 반환합니다.Another
생성자를 이용하여 초기화할 때
class Dog {
String name;
int age;
String color;
int thirsty;// 갈증 지수
Dog(this.name, this.age, this.color, this.thirsty);
}
void main() {
Dog d1 = Dog("Toto", 13, "white", 100);
Dog d2 = Dog("Mango", 2, "white", 50);
print("d1의 이름은 ${d1.name}"); // Toto 출력
print("d2의 이름은 ${d2.name}"); // Mango 출력
}
- 기존 자바와 비교하여 단순히 this.name, this.age 등으로 간단하게 작성할 수 있습니다.
선택적 매개변수
// 선택적 매개변수
class Person {
// 기본값이 없기 때문에 물음표를 붙여야 함
String? name;
// 기본값이 있기 때문에 물음표를 붙이지 않아도 가능
int money;
// money는 매개 변수에 money 값을 전달하지 않을 시 기본값으로 0이 설정되어 있음
Person({this.name, this.money = 0});
}
void main() {
Person p1 = Person(name: "홍길동");
Person p2 = Person(name: "임꺽정", money: 10000);
print("${p1.name}의 재산은 ${p1.money}"); // 홍길동의 재산은 0 출력
print("${p2.name}의 재산은 ${p2.money}"); // 임꺽정의 재산은 10000 출력
}
- Dart 언어는 오버로딩이 없습니다. 대신 더 강력한 선택적 매개변수 방식을 사용합니다. 문법은 매개변수를 {}로 감싸면 됩니다.
cascade 연산자
// cascade 연산자
class Chef {
String name;
Chef(this.name);
void cook() {
print("요리를 시작합니다");
}
}
void main() {
Chef c1 = Chef("홍길동")..cook(); // cascade 연산자
print("요리사 이름 ${c1.name}");
}
- .. 연산자를 사용하면 코드 한 줄로 객체를 변수로 넘겨주면서 객체가 가진 함수를 호출할 수 있는 유용한 표기법입니다.
late
class Person {
int id;
String name;
late int money; // 나중에 들어옴 or 디폴트 0
String? nickname; // 있을수도 있고 없을 수도 있는 것
Person(this.id, this.name, this.money, this.nickname);
}
void main() {
Person p = Person(1, "name", 10, "nickname");
}
- 변수의 초기화를 나중으로 미루고자 할 때 사용됩니다. 이는 주로 변수를 선언할 때 초기화할 수 없는 경우나, 변수를 초기화하기 위해 추가적인 논리가 필요한 경우에 유용합니다.
이름이 있는 생성자
class Person {
int id;
String name;
late int money; // 나중에 들어옴 or 디폴트 0
String? nickname; // 있을수도 있고 없을 수도 있는 것
// 기본 생성자
Person(this.id, this.name, this.money, this.nickname);
// 이름이 있는 생성자
Person.fromMap(Map<String, dynamic> m) :
this.id = m["id"],
this.name = m["name"],
this.money = m["money"],
this.nickname = m["nickname"] ?? "다른이름";
void init(){
if(money == 0){
for(int i=0; i<10; i++){
money = money + 1;
}
}
}
}
void main(){
var m = {
"id":2,
"name":"임꺽정",
"money":0
};
Person p2 = Person.fromMap(m)..init(); // cascade
print(p2.id);
print(p2.name);
print(p2.money);
print(p2.nickname);
}
- 생성자에 이름을 붙여 초기화 할 수도 있습니다.
- 다만 ‘클래스이름.생성자이름’ 과 같은 방식으로 사용하기 때문에 기존 자바에서의 static 과 혼동할 수 있기 때문에 주의해야 합니다.
mixin
mixin class Engine {
int power = 1000;
}
class Car with Engine {
}
void main() {
Car c = Car();
print(c.power); // 1000 출력
}
mixin
은 여러 클래스에 공통된 기능을 추가하는 데 사용됩니다. mixin
을 사용하면 상속 없이 여러 클래스의 기능을 혼합할 수 있습니다. Dart에서 mixin
은 클래스 간의 코드를 재사용할 수 있는 강력한 도구입니다. mixin
을 사용하려면 with
키워드를 사용합니다.mixin
키워드를 사용하여 클래스를 정의합니다.
with
키워드를 사용하여Car
클래스에Engine
믹스인을 추가합니다.
Car
클래스는Engine
믹스인에 정의된power
변수를 사용할 수 있습니다.
const
// const는 컴파일 시 초기화되며 final처럼 변경이 불가능하다
// 상수 데이터를 초기화할 때는 const를 사용한다
const String primaryColor = "green";
// final은 런타임 시 초기화
final String secondaryColor = "red";
// final과 const는 타입 생략이 가능 (타입 추론)
// const primaryColor = "green";
// final secondaryColor = "red";
class Button {
final String text;
const Button(this.text);
}
void main() {
// 동일한 버튼을 두 개 생성했으며 필드가 final이라 변경이 불가하고,
// 이런 상황에서 const를 생성자에 붙여 쓰게 되면
Button b1 = const Button("로그인");
Button b2 = const Button("로그인");
// 동일한 내용일 때 새로 new 하는 것이 아닌 기존 것을 재사용한다. (상태를 변경할 수 없으며, 내용이 완전히 동일하기 때문)
// 둘 다 동일한 해시코드 출력
print(b1.hashCode);
print(b2.hashCode);
}
const
에 대한 설명:
- 컴파일 시 초기화:
const
로 선언된 변수는 컴파일 시점에 초기화됩니다. 이는 해당 변수가 애플리케이션이 실행되기 전 컴파일 단계에서 이미 고정된 값을 가지게 됨을 의미합니다.
- 불변성:
const
로 선언된 변수는 값을 변경할 수 없습니다. 이는 상수로서의 역할을 하며, 이후에 값을 재할당하는 것이 불가능합니다.
- 재사용성:
- 동일한
const
값을 가진 객체를 생성할 때, 새로운 객체를 생성하지 않고 기존 객체를 재사용합니다. 이는 메모리를 절약하고, 객체 비교 시 효율성을 높입니다.
- const 생성자:
- 클래스의 생성자에
const
를 사용하면, 해당 클래스의 인스턴스가 불변 객체로 생성됩니다. 이는 클래스 인스턴스가 동일한 값을 가질 경우 동일한 객체를 재사용하게 만듭니다.
Collection
var list = [1,2,3];
// 원형 : List<dynamic> list = [1, 2, 3];
var map = {
"id":1,
"name":"홍길동"
};
void main() {
print(list[1]); // 2 출력
print(map["name"]); // 홍길동 출력
// 1. 값 추가
list.add(4);
print(list); // [1, 2, 3, 4] 출력
// 2. map 값 추가
map["phone"] = "0102222";
print(map); // {id: 1, name: 홍길동, phone: 0102222} 출력
}
Collection 사용 기본 및 전개 연산자
var list = [1, 2, 3];
void main() {
// 0. 깊은 복사
var r1 = [...list]; // 전개 연산자. ...list에 1, 2, 3 이 들어간다
print(r1); // [1, 2, 3] 출력
// 1. 추가
var r2 = [...list, 4]; // 전개 연산자. ...list에 1, 2, 3이 들어가고, 직접 적은 4 도 들어간다
print(r2); // [1, 2, 3, 4] 출력
// 2. 삭제
var r3 = list.where((e) => e != 2).toList();
print(r3); // [1, 3] 출력
// 3. 검색
var r4 = list.where((e) => e == 2).toList();
print(r4); // [2] 출력
// 4. 수정
var r5 = list.map((e) => e == 2 ? 5 : e).toList();
print(r5); // [1, 5, 3] 출력
}
- 대괄호 안에 …list 와 같이 앞에 ‘점3개+list’ 를 적게 되면 list의 모든 원소들을 적용합니다. 이를 전개 연산자라고 합니다.
Share article