[25.06.24 TIL] Dart 심화 문법과 프로그래밍

김영민·2025년 6월 24일

🎯 오늘은 심화 문법 함수와 제네릭, 함수형 프로그래밍, 객체 지향형 프로그래밍에 대해 강의를 들었다.
함수 종류도 많고, 클래스에도 많은 속성이 있어서 학습량이 많았다.


💬 Dart 심화 문법

🔥 함수와 제네릭

✏️ 함수(Function)

✅ 입력을 받아서 특정 작업을 수행하고, 그 작업에 대한 결과를 반환하는 코드 블록

📁 반환타입
✔ 함수가 값을 함수가 반환하는 값의 데이터 타입
✔ 함수가 값을 반환하지 않는 경우 void 로 선언한다.


📁 함수 이름
✔ 함수를 호출할 때 사용한다.


📁 매개변수
✔ 함수 내부로 전달되어 코드 블록에서 사용하는 값


📁 실행할 코드
✔ 함수가 호출되면 실행되는 코드 블록


📁 반환값
✔ 함수의 코드 블록이 실행된 후 반환되는 값, 즉 결과값


📋 [반환 타입] [함수 이름]([매개변수 타입] [매개변수 이름]) { return [반환값]; }

int add(int a, int b) {
  return a + b;
}

반환 타입 : int
함수 이름 : add
매개변수 타입 : int, int
매개변수 이름 : a, b
반환값 : a + b


📋 [함수 이름]([매개변수 이름]) { return [반환값]; }

add(a, b) {
  return a + b;
}

❌ 타입 추론이 가능하기 때문에 반환 타입과 매개변수 타입을 생략해도 되지만, 사용하지 않는 것이 좋다.


📋 [반환 타입] [함수 이름]([매개변수 타입] [매개변수 이름]) ⇒ 반환값;

int multiply(int a, int b) => a * b;

⭕ 반환값이 하나의 표현식으로 나타낼 수 있는 경우만 사용 가능


📁 main()
✔ 최상위 함수로, main() 함수 코드 블록에 코드를 넣어야 실행된다.


📁 void

void main() {
	print('Hello'); // Hello
}

✔ 반환값이 없으면 반환 타입을 void로 설정한다.


📁 List<String>

void main(List<String> arguments) {
  print(arguments);
}

✔ Flutter 애플리케이션을 개발할 때는 사용하지 않고, Command Line (터미널, 명령 프롬프트) 을 실행할 때 주로 사용한다.


사용 예제

void main() {
	int one = 1;
	int two = 2;
	print(one + two); // 3
//	
	int three = 3;
	int four = 4;
	print(three + four); // 7
//	
	int five = 5;
	int six = 6;
	print(five + six); // 11
//	
	int seven = 7;
	int eight = 8;
	print(seven + eight); // 15
}
void printSum(int a, int b) {
	print(a + b);
}
//
void main() {
	printSum(1, 2); // 3
	printSum(3, 4); // 7
	printSum(5, 6); // 11
}

✏️ 제네릭(Generics)

✅ 클래스나 함수에서 데이터 타입을 일반화하여 다양한 타입을 지원할 수 있게 하는 기능

📁 제네릭 클래스


📁 제네릭 함수

📋 [타입 파라미터] [함수 이름]<타입 파라미터>([매개변수]) { … }

T getFirstElement<T>(List<T> list) {
  return list[0];
}

✔ 실제 List, Set, Map 은 각각
List<E>, Set<E>, Map<E, E> 형태로 정의되어 있고, 타입 파라미터인 E 에 여러 타입이 올 수 있다.


int getFirstNumber(List<int> numbers) {
  return numbers[0];
}
//
String getFirstWord(List<String> words) {
  return words[0];
}
//
void main() {
  var numbers = [0, 1, 2, 3];
  print(getFirstNumber(numbers)); // 0
//  
  var words = ['a', 'b', 'c'];
  print(getFirstWord(words)); // a
}
T getFirstElement<T>(List<T> list) {
  return list[0];
}
//
void main() {
  var numbers = [0, 1, 2, 3];
  print(getFirstElement(numbers)); // 0
//  
  var words = ['a', 'b', 'c'];
  print(getFirstElement(words)); // a
}

💡 특정 타입만이 아닌, 여러 타입에 대해 동일한 코드를 적용할 수 있어서 재사용성 높은 코드를 짤 수 있다.


💬 함수형 프로그래밍

🔥 함수형 프로그래밍(Functional Programming)

✅ 함수의 연속으로 프로그램을 구성하는 방식이다.

✏️ 매서드 체이닝(Method Chaining)

. 을 사용해서 여러개의 함수를 하나로 연결하는 방식이다.

int number = -12345
var result = number.abs().toString().contains('3');
print(result); // true

abs() 는 절댓값을 반환하는 함수, `toString() 은 문자형으로 바꿔주는 함수이다.


String word = 'abcd';
var index = word.toUpperCase().indexOf('B');
print(index); // 1

toUpperCase() 는 문자열의 모든 문자를 대문자로 바꾼다.


✏️ 순수 함수(Pure Function)

✅ 가변적인 데이터의 사용을 최소화하여 프로그램을 구성하는 방식으로, 출력값이 항상 그 함수의 매개변수(입력값)에만 의존하게 한다.

int add(int a, int b) {
  return a + b;
}
//
void main() {
  int result = add(3, 4);
  print(result); // 7
}

add() 의 코드 블록을 보면 함수의 결과값이 함수의 매개변수에만 의존하고 있다.


int getTotal(List<int> numbers) {
  int result = 0;
  for (var number in numbers) {
    result += number;
  }
  return result;
}

✅ 같은 입력값에 대해서 항상 같은 출력값이 나오는 함수이다.


💡 같은 값을 입력하면 항상 같은 값이 나오기 때문에 부작용(Side Effect)이 발생할 일이 적다.


🔥 명령형 프로그래밍 방식

int number = 0;
//
void increaseNumber() {
  number += 1;
}
//
void main() {
  print(number); // 0
  increaseNumber();
  print(number); // 1
}

🚨 increaseNumber() 안에서 함수 외부의 값을 변경하고 있는 예상하지 못 했던 상황이 발생할 수 있어서 위험하다.
순수 함수가 아닌 명령형 프로그래밍 방식으로 그냥 넘어가도 된다.


🔥 함수

✏️ 형변환 함수 (Type Casting Function)

✅ 특정 타입의 데이터를 다른 타입의 데이터로 변환하는 함수

📁 toString()

int number = 42;
var result = number.toString();
print(result); // 42
print(result.runtimeType); // String
double number = 1.5;
var result = number.toString();
print(result); // 1.5
print(result.runtimeType); // String

✔ 값을 String 타입으로 변환한 값을 반환한다.
runtimeType 은 어떤 타입인지 반환해준다.


📁 int.parse('')

String number = '123';
var result = int.parse(number);
print(result); // 123
print(result.runtimeType); // int
String invalidNumber = 'abcd';
var result = int.parse(invalidNumber);
print(result);

✔ String 타입의 값을 int 타입으로 변환하여 값을 반환한다.
✔ 변환이 어려운 경우에는 오류가 발생한다.


int.tryParse(’’)

String number = '123';
var result = int.tryParse(number);
print(result); // 123
print(result.runtimeType); // int
String invalidNumber = 'abcd';
var result = int.tryParse(invalidNumber);
print(result); // null

✔ 형변환에 성공하면 int.parse() 와 같이 동작하며, 변환에 실패하면 오류 대신 null 을 반환한다.


📁 double.parse('')

String number = '123.4';
var result = double.parse(number);
print(result); // 123.4
print(result.runtimeType); // double
String invalidNumber = 'abcd';
var result = double.parse(invalidNumber);
print(result);

int.parse() 와 동일하게 String 타입의 값을 int 타입으로 변환하여 값을 반환한다.


double.tryParse(’’)

String invalidNumber = 'abcd';
var result = double.tryParse(invalidNumber);
print(result); // null

int.parse() 와 동일하게 성공하면 double.parse('') 같이 작동하고, 실패하면 null 을 반환한다.


📁 toList()

Set<String> fruitSet = {'사과', '오렌지', '수박'};
var fruitList = fruitSet.toList();
print(fruitList); // [사과, 오렌지, 수박]
print(fruitList.runtimeType); // List<String>
Map<String, String> people = {'Alice': 'Student', 'Bob': 'Teacher'};
var peopleList = people.toList();
print(peopleList);
// Error: The method 'toList' isn't defined for the class 'Map<String, String>'.

✔ 특정 Collection 타입의 값을 List 로 변환한 값을 반환한다.

Map 의 경우 키와 값 두가지가 존재하기 때문에 변환이 불가능하다.


📁 toSet()

List<String> fruitList = ['사과', '오렌지', '수박'];
var fruitSet = fruitList.toSet();
print(fruitSet); // {사과, 오렌지, 수박}
print(fruitSet.runtimeType); // Set<String>
Map<String, String> people = {'Alice': 'Student', 'Bob': 'Teacher'};
var peopleSet = people.toSet();
print(peopleSet);
// Error: The method 'toSet' isn't defined for the class 'Map<String, String>'.
List<String> fruitList = ['사과', '오렌지', '수박', '사과'];
var fruitSet = fruitList.toSet();
print(fruitSet); // {사과, 오렌지, 수박}

✔ 특정 Collection 타입의 값을 Set 으로 변환한 값을 반환하며, Set은 중복값을 허용하지 않기 때문에 중복값을 제외하고 Set 을 반환한다.

❌ 동일하게 Map 변환은 불가능하다.


📁 asMap()

List<String> fruitList = ['사과', '오렌지', '수박'];
var fruitMap = fruitList.asMap();
print(fruitMap); // {0: 사과, 1: 오렌지, 2: 수박}
List<String> fruitList = ['사과', '오렌지', '수박', '사과'];
var fruitMap = fruitList.asMap();
print(fruitMap); // {0: 사과, 1: 오렌지, 2: 수박, 3: 사과}
Set<String> fruitSet = {'사과', '오렌지', '수박'};
var fruitMap = fruitSet.asMap();
print(fruitMap);
// Error: The method 'asMap' isn't defined for the class 'Set<String>'.

✔ 특정 Collection 타입의 값을 Map 타입으로 변환한 값을 반환한다.

❌ Set 은 Index 가 없기 때문에 Set 에는 적용하지 못 한다.

Set<String> fruitSet = {'사과', '오렌지', '수박'};
var fruitList = fruitSet.toList();
var fruitMap = fruitList.asMap();
print(fruitMap); // {0: 사과, 1: 오렌지, 2: 수박}

💡 단, Set을 List로 변환한 뒤 asMap()으로 변환하면 가능하다.


✏️ 고차 함수 (Higher-order Function)

✅ 함수를 다루는 함수로, Collection 타입의 데이터에 있는 요소를 처리하거나 변환할 때 사용한다.

📁 map()

Collection 타입인 데이터의 각 요소에 특정 함수를 적용한 새로운 Collection 타입의 데이터를 반환한다.


📋 map(([매개변수]) { return [매개변수에 적용할 동작] });

List<String> fruitList = ['사과', '오렌지', '수박'];
var delicious = fruitList.map((fruit) {
	return '맛있는 $fruit';
});
print(delicious); // (맛있는 사과, 맛있는 오렌지, 맛있는 수박)
Set<String> carSet = {'BMW', '현대', '기아'};
var goodCar = carSet.map((car) {
	return '짱 멋진 $car';
});
print(goodCar); // (짱 멋진 BMW, 짱 멋진 현대, 짱 멋진 기아)
Map<String, int> fruits = {'사과': 5, '포도': 2, '귤': 3};
//
var delicious = fruits.keys.map((fruit) {
	return '맛있는 $fruit';
});
print(delicious); // (맛있는 사과, 맛있는 포도, 맛있는 귤)
//
var numbers = fruits.values.map((number) {
	return '$number개';
});
print(numbers); // (5개, 2개, 3개)

List, Set, Map 모두 적용 가능하다.


📋 map(([매개변수]) => [매개변수에 적용할 동작] );

List<int> numbers = [1, 2, 3, 4, 5];
var doubledNumbers = numbers.map((n) => n * 2);
print(doubledNumbers); // (2, 4, 6, 8, 10)
List<String> fruits = ['사과', '오렌지', '수박'];
var delicious = fruits.map((fruit) => '맛있는 $fruit');
print(delicious); // (맛있는 사과, 맛있는 오렌지, 맛있는 수박)

✔ 매개변수에 적용할 동작을 한줄로 표현 가능한 경우에만 사용할 수 있다.


💡 원본 데이터를 가공하지 않고 특정 함수를 적용한 새로운 데이터를 원본 데이터와 같은 크기로 반환한다.


📁 where()

Collection 타입의 데이터에 있는 각 요소들을 특정 조건에 넣었을 때, 참인 요소들만 필터링한 새로운 Collection 타입의 데이터를 반환한다.


📋 where(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.where((number) {
	return number > 5;
});
print(result); // (6, 7, 8, 9, 10)
List<String> fruits = ['사과', '오렌지', '수박'];
var result = fruits.where((fruit) {
	return fruit.length == 2;
});
print(result); // (사과, 수박)
List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.where((number) {
	return number.isEven
});
print(result); // (2, 4, 6)

isEven 은 짝수면 true 를, 홀수면 false 를 반환하며, number.isEvennumber % 2 == 0 와 같다고 보면 된다.


📋 where(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.where((n) => n > 5);
print(result); // (6, 7, 8, 9, 10)
List<String> fruits = ['사과', '오렌지', '수박'];
var result = fruits.where((f) => f.length == 2);
print(result); // (사과, 수박)
List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.where((n) => n.isEven);
print(result); // (2, 4, 6)

✔ 매개변수에 적용할 동작을 한줄로 표현 가능한 경우에만 사용할 수 있다.


List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.where((number) {
  return number > 10;
});
print(result); // ()

💡 원본 데이터를 가공하지 않고 특정 함수를 적용한 새로운 데이터를 반환하며, 조건식이 참인 요소가 없는 경우에는 빈 값을 반환한다.


📁 firstWhere()

Collection 타입의 데이터에 있는 각 요소들을 특정 조건에 넣었을 때, 참인 요소들 중 첫번째 요소를 반환한다.


📋 firstWhere(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.firstWhere((number) {
	return number > 5;
});
print(result); // 6
List<String> fruits = ['사과', '오렌지', '수박'];
var result = fruits.firstWhere((fruit) {
	return fruit.length == 2;
});
print(result); // 사과

📋 firstWhere(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.firstWhere((n) => n > 5);
print(result); // 6
List<String> fruits = ['사과', '오렌지', '수박'];
var result = fruits.firstWhere((f) => f.length == 2);
print(result); // 사과

✔ 매개변수에 적용할 동작을 한줄로 표현 가능한 경우에만 사용할 수 있다.


💡 원본 데이터를 가공하지 않고 특정 함수를 적용한 새로운 데이터를 반환한다.


List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.firstWhere((number) {
  return number > 10;
});
print(result); // StateError

🚨조건식이 참인 요소가 없는 경우 오류가 발생한다.


📁 lastWhere()

Collection 타입의 데이터에 있는 각 요소들을 특정 조건에 넣었을 때, 참인 요소들 중 마지막 요소를 반환한다.


📋 lastWhere(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.lastWhere((number) {
	return number > 5;
});
print(result); // 10
List<String> fruits = ['사과', '오렌지', '수박'];
var result = fruits.lastWhere((fruit) {
	return fruit.length == 2;
});
print(result); // 수박

📋 lastWhere(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers.firstWhere((n) => n > 5);
print(result); // 6
List<String> fruits = ['사과', '오렌지', '수박'];
var result = fruits.firstWhere((f) => f.length == 2);
print(result); // 사과

✔ 매개변수에 적용할 동작을 한줄로 표현 가능한 경우에만 사용할 수 있다.


💡 원본 데이터를 가공하지 않고 특정 함수를 적용한 새로운 데이터를 반환한다.

🚨조건식이 참인 요소가 없는 경우 오류가 발생한다.


📁 reduce()

Collection 타입의 데이터에 있는 요소들을 하나의 값으로 결합한다.


📋 reduce(([매개변수1], [매개변수2]) { return [적용할 동작] });

List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.reduce((a, b) {
	return a + b;
});

실행

List<int> numbers = [1, 2, 3, 4, 5];
int result = numbers.reduce((a, b) {
  print('a : $a');
  print('b : $b');
  print('a + b : ${a + b}');
  print('----------------------');
  return a + b;
});
print('result : $result');
/*
a : 1
b : 2
a + b : 3
----------------------
a : 3
b : 3
a + b : 6
----------------------
a : 6
b : 4
a + b : 10
----------------------
a : 10
b : 5
a + b : 15
----------------------
result : 15
*/

List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
var result = words.reduce((a, b) {
  return a + b;
});

실행

List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
int result = words.reduce((a, b) {
  print('a : $a');
  print('b : $b');
  print('a + b : ${a + b}');
  print('----------------------');
  return a + b;
});
print('result : $result');
/*
a : 다트는 
b : 참 
a + b : 다트는 참 
----------------------
a : 다트는 참 
b : 재미있군 ?
a + b : 다트는 참 재미있군 ?
----------------------
result : 다트는 참 재미있군 ?
*/

📋 reduce(([매개변수1], [매개변수2]) => [적용할 동작] );

List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.reduce((a, b) => a + b);
List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
var result = words.reduce((a, b) => a + b);

✔ 매개변수에 적용할 동작을 한줄로 표현 가능한 경우에만 사용할 수 있다.


List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.reduce((a, b) => '$a' + '$b');
// Error: A value of type 'String' can't be returned from a function with return type 'int'.

❌ 처음 실행될 때 초기값이 정수형인 1 이기 때문에 정수형과 문자형을 더할 수 없다.
 
💡 Collection 타입의 데이터와 같은 타입으로만 반환할 수 있다.


List<int> list = [];
var result = list.reduce((a, b) => a + b); // StateError

🚨 Collection 타입의 데이터에 요소가 없는 경우에는 오류가 발생한다.


📁 folde()

Collection 타입의 데이터에 있는 요소들을 하나의 값으로 결합한다.


📋 fold(초기값, ([매개변수1], [매개변수2]) { return [적용할 동작] });

List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
var result = words.fold('앗, ', (a, b) {
  return a + b;
});

실행

List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
int result = words.fold('앗, ', (a, b) {
  print('a : $a');
  print('b : $b');
  print('a + b : ${a + b}');
  print('----------------------');
  return a + b;
});
print('result : $result');
/*
a : 앗, 
b : 다트는 
a + b : 앗, 다트는 
----------------------
a : 앗, 다트는 
b : 참 
a + b : 앗, 다트는 참 
----------------------
a : 앗, 다트는 참 
b : 재미있군 ?
a + b : 앗, 다트는 참 재미있군 ?
----------------------
result : 앗, 다트는 참 재미있군 ?
*/

📋 fold(초기값, ([매개변수1], [매개변수2]) => [적용할 동작] );

List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
var result = words.fold('앗, ', (a, b) => a + b);

✔ 매개변수에 적용할 동작을 한줄로 표현 가능한 경우에만 사용할 수 있다.


reduce()fold()
초기값Collection 데이터의 첫번째 요소첫번째 매개변수에 넣어준 값
Collection 사용 가능 여부XO
타입의 유연성 여부(Collection의 요소 타입과 반환 타입이 달라도 되는지)XO

List<String> words = ['다트는 ', '참 ', '재미있군 ?'];
int result = words.fold(0, (a, b) {
  print('a : $a');
  print('b : $b');
  print('b length : ${b.length}');
  print('---------------');
  return a + b.length;
});
print('총 글자 수 : $result');
/*
a : 0
b : 다트는 
b length : 4
---------------
a : 4
b : 참 
b length : 2
---------------
a : 6
b : 재미있군 ?
b length : 6
---------------
총 글자 수 : 12
*/

✅ Collection 타입의 데이터와 다른 타입으로도 반환이 가능하다.


List<int> list = [];
var result = list.fold(0, (a, b) => a + b);
print(result); // 0

✅ Collection 타입의 데이터에 요소가 없어도 초기값이 있기 때문에 오류가 발생하지 않는다.


📁 any()

Collection 타입의 데이터에 있는 요소 중
하나라도 주어진 조건을 만족하면 true 를 반환한다.


📋 any(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.any((number) {
	return number.isEven;
});
print(result); // true
List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.any((number) {
 return number > 10;
 });
print(result); // false

📋 any(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.any((n) => n.isEven);
print(result); // true
List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.any((number) => number > 10);
print(result); // false

📁 every()

Collection 타입의 데이터에 있는 모든 요소가 주어진 조건을 만족하면 true 를 반환한다.


📋 every(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.every((number) {
 return number > 0;
});
print(result); // true
List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.every((number) {
	return number.isEven;
});
print(result); // false

📋 every(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.any((number) => number > 0);
print(result); // true
List<int> numbers = [1, 2, 3, 4, 5];
var result = numbers.every((n) => n.isEven);
print(result); // false

📁 takeWhile()

Collection 타입의 데이터에 있는 요소들을 주어진 조건에 넣었을 때, 참이 되는 동안은 해당 요소들을 반환하고, 조건이 처음으로 거짓이 되는 순간부터의 요소들은 모두 무시한다.


📋 takeWhile(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.takeWhile((number) {
 return number < 4;
});
print(result); // (1, 2, 3)
List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.takeWhile((number) {
 return number.isOdd;
});
print(result); // (1)

📋 takeWhile(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.takeWhile((number) => number < 4);
print(result); // (1, 2, 3)
List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.takeWhile((number) => number.isOdd);
print(result); // (1)

📁 skipWhile()

Collection 타입의 데이터에 있는 요소들을 주어진 조건에 넣었을 때, 참이 되는 동안은 해당 요소들을 건너뛰고, 조건이 처음으로 거짓이 되는 순간부터의 요소들을 모두 반환한다.


📋 skipWhile(([매개변수]) { return [조건식] });

List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.skipWhile((number) {
 return number < 4;
});
print(result); // (4, 5, 6)
List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.skipWhile((number) {
 return number.isOdd;
});
print(result); // (2, 3, 4, 5, 6)

📋 skipWhile(([매개변수]) => [조건식] );

List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.takeWhile((number) => number < 4);
print(result); // (4, 5, 6)
List<int> numbers = [1, 2, 3, 4, 5, 6];
var result = numbers.takeWhile((number) => number.isOdd);
print(result); // (2, 3, 4, 5, 6)

💻 매서드 체이닝 방식 함수형 프로그래밍

📁 map() + where()

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var doubledNumbers = numbers.map((number) => number * 2);
var result = doubledNumbers.where((number) => number > 5);
print(result); // (6, 8, 10, 12, 14, 16, 18, 20)

⬇ 매서드 체이닝

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers
  .map((number) => number * 2)
  .where((number) => number > 5);
print(result); // (6, 8, 10, 12, 14, 16, 18, 20)

List<String> words = ['apple', 'orange', 'watermelon', 'pineapple'];
var longerThanFiveWords = words.where((word) => word.length > 5);
var result = longerThanFiveWords.map((word) => word.toUpperCase());
print(result); // (ORANGE, WATERMELON, PINEAPPLE)

⬇ 매서드 체이닝

List<String> words = ['apple', 'orange', 'watermelon', 'pineapple'];
var upperCasedWords = words
    .where((word) => word.length > 5)
    .map((word) => word.toUpperCase());
print(upperCasedWords); // (ORANGE, WATERMELON, PINEAPPLE)

📁 where() + reduce()

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = numbers.where((number) => number.isEven);
var result = evenNumbers.reduce((a, b) => a + b);
print(result); // 30

⬇ 매서드 체이닝

List<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = numbers
  .where((number) => number % 2 == 0)
  .reduce((a, b) => a + b);
print(result); // 30

📁 where() + map() + fold()

List<int> numbers = [-10, 20, -30, 40, 50];
var positiveNumbers = numbers..where((number) => number > 0);
var plusTenNumbers = positiveNumbers.map((number) => number + 10);
var result = plusTenNumbers.fold(0, (a, b) => a + b);
print(result); // 140

⬇ 매서드 체이닝

List<int> numbers = [-10, 20, -30, 40, 50];
var result = numbers
  .where((number) => number > 0)
  .map((number) => number + 10)
  .fold(0, (a, b) => a + b);
print(result); // 140

💬 객체 지향 프로그래밍

🔥 클래스와 속성

✏️ 클래스

객체 지향 프로그래밍 (Object-Oriented Programming) 의 핵심 개념이자 기본 단위로, 객체(Object) 의 구조와 동작을 정의하는 틀이다.

📘 객체(Object)

클래스 에서 정의한 구조를 기반으로 생성된 실제 데이터


📋 class [클래스 이름] { … }

class Person {
  String name;
  int age;
//  
  Person(this.name, this.age);
//  
  void introduce() {
    print('안녕 ? 나는 $age살 $name !');
  }
}

👉 속성 : name, age
👉 생성자 : Person(this.name, this.age);
👉 메서드 : introduce()


✏️ 속성(Attribute)

✅ 클래스 안에서 작업을 수행할 때 사용하는 데이터

📁 인스턴스 변수(Instance Variable)

class Person {
  String name = 'Mini';
	int age = 20;
}

✔ 객체에 속해 있는 변수


🔍 this를 통해 접근 가능하다.

✔ 클래스 내부에서 현재 객체를 참조할 때 사용하며, 객체를 가리키는 키워드라고 보면 된다.


✅ 클래스에서 클래스 안의 속성이나 메서드를 사용할 때

class Person {
  String name = 'Mini';
  int age = 20;
//  
  void printName() {
    print(this.name);
  }
//  
  void printNameAndAge() {
    this.printName();
    print(this.age);
  }
}

✅ 클래스에서 인스턴스 변수와 메서드에 속한 변수를 구분할 때

class Person {
  String name = 'Mini';
  int age = 20;
//  
  void changeName(String name) {
    this.name = name;
  }
}

this.name = name; 에서
this.name 은 인스턴스 변수인 name ,
name 은 매개변수인 name 을 가리킨다.
✔ this 가 붙지 않은 변수는 더 가까이 있는 변수로 우선 취급된다.


class Person {
  String name = 'Mini';
  int age = 20;
//  
  void printName() {
    print(name);
  }
//  
  void printNameAndAge() {
    printName();
    print(age);
  }
}

💡 this최대한 덜 쓰는 것이 좋기 때문에, 변수를 구분할 필요가 없다면 굳이 this를 쓰지 않아도 된다.


🔍 클래스의 모든 곳에서 접근할 수 있다.

class Person {
  String name = 'Mini';
	int age = 20;
//
  void introduce() {
    print('안녕 ? 나는 $age살 $name !');
  }
}

Person 클래스의 인스턴스 변수인 nameageintroduce() 메서드 안에서 사용할 수 있다.


🔍 객체가 존재하는 동안 계속 메모리 상에 존재한다.

✔ 메모리 상에 존재한다는 것은 생명 주기, 즉 라이프 사이클을 말한다.


🔍 동일한 클래스로 생성한 객체들이어도 각 객체들은 값을 공유하지 않고, 개별적인 값을 가진다.

class Person {
  String name = '';
	int age = 0;
}
//
void main() {
	Person paul = Person();
	paul.name = 'Paul';
	paul.age = 25;
//	
  Person mark = Person();
  mark.name = 'Mark';
  mark.age = 30;
//  
  paul.age = 27;
  print(paul.age); // 27
  print(mark.age); // 30
}

class Person {
  final String name = 'Mini';
	int age = 25;
}
//
void main() {
	Person paul = Person();
	paul.name = 'Paul'; // 오류 발생
  paul.age = 30;
}

❗ 객체를 통해서 변수의 값을 final 로 지정할 수도 있는데, 이때 값을 변경하면 오류가 발생한다.


📁 지역 변수(Local Variable)

✔ 로컬 변수라고도 하며, 특정 코드 블록 안에 선언된 변수이다.

class Person {
	String name = 'Alice';
//
	void sayName() {
		String nameSentence = '내 이름은 $name !';
		print(nameSentence);
	}
}

🔍 변수가 선언된 코드 블록 안에서만 사용할 수 있다.

class Person {
	String name = 'Mini';
//	
	void sayName() {
		String nameSentence = '내 이름은 $name !';
		print(nameSentence);
	}
//	
	void sayNameAgain() {
		print(nameSentence); // 오류 발생
	}
}

🔍 변수 가 선언된 코드 블록의 실행이 끝나면 메모리 상에서 사라진다.

class Person {
	String name = 'Mini';
//	
	void sayName() {
		String nameSentence = '내 이름은 $name !';
		print(nameSentence);
	}
}

nameSentencesayName() 의 실행이 끝나면 메모리에서 해제되기 때문에 생명 주기가 짧다.
✔ 생명 주기가 짧기 때문에 보통 잠시동안 사용하는 데이터에 사용한다.


📁 정적 변수(Static Variable)

✔ 클래스 변수라고도 하며, 객체에 종속되지 않고, 클래스 자체에 속하는 변수이다.


class Circle {
  static double pi = 3.14159;
}

static 이 붙은 변수는 정적 변수이다.


🔍 클래스 이름을 통해 접근한다.

class Circle {
  static double pi = 3.14159;
  double radius = 0;
}
//
void main() {
	print(Circle.pi); // 3.14159
	print(Circle.radius); // Error: Member not found: 'radius'.
}

객체를 통해 접근할 수 없다.

class Circle {
  static double pi = 3.14159;
  double radius = 0;
}
//
void main() {
	Circle circle = Circle();
	print(circle.radius); // 0
	print(circle.pi); // 오류 발생
}

this 를 통해 접근할 수 없다.

class Circle {
  static double pi = 3.14159;
//
  void printPi() {
    print(this.pi); // 오류 발생
  }
}

✔ 객체에 종속되지 않기 때문에 this 는 사용할 수 없다.


🔍 객체마다 개별적인 값을 갖지 않고, 모든 객체가 서로 값을 공유한다.

class Circle {
  static double pi = 3.14159;
//  
  void printPi() {
    print(pi);
  }
}
//
void main() {
  Circle circle1 = Circle();
  circle1.printPi(); // 3.14159
//  
  Circle circle2 = Circle();
  circle2.printPi(); // 3.14159
//  
  Circle.pi = 3.14;
  circle1.printPi(); // 3.14
  circle2.printPi(); // 3.14
}

❗ 값을 변경하면 Circle 로 만든 객체들의 값이 모두 바뀌기 때문에 다른 객체에 미치는 영향을 고려해야한다.

💡 circle1과 circle2는 각각 다른 객체로 값을 공유할 뿐이다.


🎓 인스턴스 변수 vs 지역 변수

인스턴스 변수로컬 변수
사용 가능 범위클래스 내의 모든 범위해당 코드 블록 내의 모든 범위
생명 주기객체가 존재하는 동안
(객체가 소멸할 때까지)해당 코드 블록이 실행되는 동안

🎓 인스턴스 변수 vs 정적 변수

인스턴스 변수정적 변수
키워드static
접근 방법객체를 통해 접근클래스 이름을 통해 접근
객체끼리 값을 공유하는지 ?XO

🔥 클래스와 메서드

✏️ 메서드(Method)

✔ 객체의 동작을 정의하는 함수로, 속성을 변경하거나 객체를 가지고 특정 작업을 수행한다.

❕ 매서드는 클래스에 의존하고, 함수는 클래스에 의존하지 않는다.


📁 인스턴스 메서드(Instance Method)

class Person {
  String name = 'Mini';
	int age = 20;
//
  void introduce() {
    print('안녕 ? 나는 $age살 $name !');
  }
}

✔ 객체에 속해 있는 매서드로, introduce() 가 이에 해당된다.


🔍 this를 통해 접근할 수 있다.

class Person {
  String name = 'Mini';
  int age = 20;
//  
  void printName() {
    print(name);
  }
//  
  void printNameAndAge() {
    this.printName();
    print(age);
  }
}

this 없이 printName(); 도 가능하다.


🔍 클래스의 모든 곳에서 접근할 수 있다.

class Person {
  String name = 'Mini';
  int age = 20;
//  
  void printName() {
    print(name);
  }
//  
  void printNameAgain() {
    printName();
  }
}

📁 정적 메서드(Static Method)

class Circle {
	static double pi = 3.14159;
//	
	static void printPi() {
	  print('원주율은 $pi !');
  }
}

✔ 클래스 매서드라고도 불리며, 객체에 종속되지 않고, 클래스 자체에 속하는 매서드이다.
✔ 동일하게 static 을 앞에 붙여준다.


🔍 클래스 이름을 통해 호출한다.

class Circle {
	static double pi = 3.14159;
	double radius = 0;
//	
    static void printPi() {
	  print(pi);
  }
//  
    void printRadius() {
    print(radius);
  }
}
//
void main() {
	Circle.printPi(); // 3.14159
	Circle.printRadius(); // 오류 발생
}

🔍 객체를 통해 호출할 수 없다.
🔍 this를 통해 호출할 수 없다.

✔ 정적 변수와 동일하게 객체에 종속되지 않고, 클래스 자체에 속하기 때문에 객체, this 로 호출할 수 없다.


🔍 코드 블록에서 인스턴스 변수를 사용할 수 없다.

class Circle {
	double pi = 3.14159;
	double radius = 0;
//	
  static void printPi() {
	  print(pi); // 오류 발생
  }
}

class Circle {
	static double pi = 3.14159;
	double radius = 0;
//  
    void printArea() {
    print(pi * radius * radius);
  }
}

✅ 정적 메서드에서는 정적 변수만 사용 가능하며, 인스턴스 메서드의 코드 블록에서는 정적 변수를 쓸 수 있다.


🔍 객체마다 개별적으로 동작하지 않고, 모두 동일하게 동작한다.


✏️ 생성자 (Constructor)

✅ 객체를 생성하고, 초기화하기 위해 사용하는 특수한 메서드

📁 기본 생성자(Default Constructor)

✔ 매개 변수를 갖지 않는 생성자


📋 [클래스 이름]();

class Car {
  Car(); //기본 생성자 생략 가능
}

🔍 자동으로 정의되기 때문에 클래스에 따로 명시하지 않아도 된다.


🔍 인스턴스 변수 들이 모두 초기화되어 있는 상태여야 한다.

class Car {
	String name = '';
	List<String> models = [];
//  
  Car();
}
class Car {
	String name; // Error: Field 'name' should be initialized because its type 'String' doesn't allow null.
	List<String> models; // Error: Field 'models' should be initialized because its type 'List<String>' doesn't allow null.
//  
  Car();
}

❗ 초기값이 없다면 오류가 발생한다.


📁 매개변수 생성자 (Parameterized Constructor)

✔ 매개 변수를 갖는 생성자로, 매개 변수를 통해 외부에 인스턴스 변수들의 초기값을 설정한다.


📋 [클래스 이름](this.변수);

class Car {
	String name;
	List<String> models;
//  
  Car(this.name, this.models);
}

✔ 초기값이 없어도 Car(this.name, this.models); 가 있기 때문에 오류가 나지 않는다.


📋 [클래스 이름]([타입] [매개변수 이름]) : this.변수;

class Car {
  String name;
  List<String> models;
//
  Car(String name, List<String> models)
      : this.name = name,
        this.models = models;
}

Car(String name, List<String> models) 를 통해 받아온 namemodelsCar 의 변수인 namemodels 에 각각 대입된다.


📋 [클래스 이름]([타입] [매개변수 이름]) { this.변수; }

class Car {
  String name = '';
  List<String> models = [];
//
  Car(String name, List<String> models) {
    this.name = name;
    this.models = models;
  }
}

Car(String name, List<String> models) 를 통해 받아온 name 과 models 가 각각 Car 의 변수인 namemodels 에 대입된다.


class Car {
	String name;
	List<String> models;
//  
  Car(this.name, this.models);
}
//
void main() {
	Car car = Car(); // 여기에 값 필수! Error: Too few positional arguments: 2 required, 0 given.
}

🚨 객체 생성할 때 매개변수 를 넣지 않으면 오류가 발생한다.


📁 네임드 생성자 (Named Constructor)

✔ 클래스 메서드와 같은 형식으로 호출하는 생성자


📋 [클래스 이름].[메서드 이름]([타입] [매개변수 이름]) : this.변수;

class Car {
  String name;
  List<String> models;
//
  Car.fromList(List values)
      : this.name = values[0],
        this.models = values[1];
}

class Car {
  String name;
  List<String> models;
//
  Car.fromList(List values)
      : this.name = values[0],
        this.models = values[1];
//
  void speakName() {
    print('저희는 $name 입니다 !');
  }
//
  void speakModels() {
    print('$models 모델을 가지고 있습니다 !');
  }
}
//
void main() {
  Car car = Car.fromList([
    'BMW',
    ['320i', '340i', 'M3']
  ]);
  car.speakName(); // 저희는 BMW 입니다 !
  car.speakModels(); // [320i, 340i, M3] 모델을 가지고 있습니다 !
}

🚨 클래스에 있는 변수와 타입이 맞지 않는 값을 넣으면 오류가 난다.


📌 특징

🔍 클래스와 이름이 같다.
🔍 반환값이 없기 때문에 void 타입이다.
🔍 클래스를 통해 객체가 생성될 때 자동으로 호출한다.


🔍 생성할 수 있는 객체의 수에는 제한이 없다.

class Person {
  String name;
	int age;
//
  Person(this.name, this.age);
}
//
void main() {
  Person paul = Person('Paul', 25);
  Person mark = Person('Mark', 30);
}

🔍 생성한 객체들은 서로 같지 않은 독립된 개체이다.

class Person {
  String name;
	int age;
//
  Person(this.name, this.age);
}
//
void main() {
	Person paul1 = Person('Paul', 25);
	Person paul2 = Person('Paul', 25);
	print(paul1 == paul2); // false
}

✔ 같은 값이 들어갔어도 다른 객체로 인식한다.


✅ 하나의 클래스로 여러 객체를 생성할 수 있기 때문에 공통된 속성과 동작을 갖는 코드를 여러 번 작성하지 않아도 돼서 재사용성 높은 코드를 만들 수 있다.


✏️ 제네릭 클래스 (Generic Class)

✔ 클래스나 함수에서 데이터 타입을 일반화하여 다양한 타입을 지원할 수 있게 하는 기능으로, 특정 타입에 의존하지 않고, 여러 타입에 대해 동일한 코드를 적용할 수 있도록 한다.


📋 [클래스 이름]<타입 파라미터>

class Box<T> {
  T value;
//
  Box(this.value);
//
  T getValue() {
    return value;
  }
}

✅ 타입 파라미터는 보통 대문자(ex. E, T)로 표현하고, 실제 코드를 실행할 때 타입 파라미터에 실제 데이터 타입을 넣으면 된다.


class Box<T> {
  T value;
//
  Box(this.value);
//
  T getValue() {
    return value;
  }
}
//
void main() {
  var intBox = Box<int>(10);
  print(intBox.getValue()); // 10
//
  var stringBox = Box<String>('Hello');
  print(stringBox.getValue()); // Hello
}

제네릭 함수와 마찬가지로 특정 타입에 의존하지 않고, 여러 타입에 대해 동일한 코드를 적용할 수 있어서 재사용성 높은 코드를 짤 수 있다.


🌱 심화 문법 중 함수와 제네릭 부분은 크게 어렵지 않았지만, 함수형 프로그래밍이 학습량이 많아서 좀 힘들었다.
그래도 매서드 체이닝을 통해 코드를 구성하고, 가독성 좋게 프로그래밍 하는 방법을 배웠다.

형변환 함수의 경우 변환이 안되는 함수만 주의하면 될 것 같았으나, 고차 함수가 그 종류와 양이 많아서 기억을 다 할 수 있을지 모르겠다.
우선, 어떤 함수인지 이해는 했으며, 예제까지도 보면서 결과를 유추할 수 있었다.
다만, 실제 코딩을 할 때 떠올릴 수 있을지는 모르겠으니, 직접 입력해보면서 익히는 수 밖에 없을 것 같다.

그리고 제일 어려웠던? 헷갈렸던 건 클래스를 다루는 객체 지향 프로그래밍인 것 같다.
그래도 제일 헷갈리던 변수와 클래스의 개념을 확실히 잡은 것 같고, 변수나, 매서드, 생성자와 같이 용어가 조금 헷갈릴 뿐이다.

그 특징이라던가 정적 변수, 정적 메서드와 같이 종류들이 좀 많아서 차이점과 같이 특징들이 좀 헷갈렸던 것 같다.

오히려 그 변수 내에서, 메서드 내에서는 헷갈리지 않았는데, 변수와 메서드 이런식으로 묶어서 생각할 때 헷갈렸던 것 같다.

생각보다 함수나 코드? 변수와 같이 형태 등이 많아서 머리가 꽉찬 것 같다.
보면서 과제는 이걸 활용하는 건가 싶기도 하고... 좀 복잡하다.


🚀 내일은 객체 지향과 상속 1강을 듣고, 개인 과제를 시작해야될까 싶다.
그 외에도 3강이 더 남았는데, 과제를 완수하는데 얼마나 걸릴지 모를뿐더러 제출이 금요일 낮인 것을 감안하면 목요일까지 밖에 없다고 봐도 무방할 것 같다.

그리고 아마 강의는 이번주까지 들으면 되니, 금요일에 과제 해설 강의 이후에 나머지 강의를 들으면 되지 않을까..!

진짜.. 오늘은 너무너무.. 힘들었던 것 같다..
어제는 그래도 아는 내용이 대다수라서 개념을 다시 확인한 느낌이라면 오늘은 새로 배우는 것도 많았고 용어 자체가 익숙하지 않아서 머리가 터질 것 같다 💫

profile
💻 [25.05.26~] Flutter 공부중⏳

0개의 댓글