Dart 언어를 사용하는 flutter 어플리케이션Java를 사용하는 웹페이지, Python을 사용하는 서버 등을 연결하는 경우, 사용하는 언어가 다르기 때문에 각자가 사용하는 데이터의 형식을 읽어올 수 없음

이 때 사용하는 것이 JSON이다.

 

다트 객체를 JSON 형식으로, 또는 반대로 변환할 때는 직렬화/역직렬화를 거친다.

 

직렬화 : Dart 객체 -> Map -> String
역직렬화 : String -> Map -> Dart 객체

 

왜 Map을 거치는가? 

객체를 바로 String으로 바꾸려면 직접 구현 필요 < 복잡하고 어려움, 번거로움

Dart sdk의 dart:convert 라이브러리의 jsonEncode, jsonDecode를 사용하는 방식이 편리하고, 이 함수들은 Map을 사용하기 때문!

 

jsonEncode 

jsonEncode: Dart 기본 타입(Map, List, int, null, bool 등)JSON String 으로 변환하는 함수

 

문법

String jsonEncode(Object? object)
// object에는 Map, List, int, String 과 같은 다트 기본 자료형만 넣을 수 있음
// 반환 타입 : JSON String

예시

Map<String, dynamic> map = {
	"name": "Harry", 
	"age": 17
};

String jsonData = jsonEncode(map);

print(jsonData);

// {"name":"Harry","age":17}

 

jsonDecode 

jsonDecode: JSON StringDart 구조체 타입(Map, List 등)으로 변환하는 함수

 

문법

dynamic jsonDecode(String source)
// source는 JSON String 타입만 가능
// 반환 타입은 Map<String, dynamic> 또는 List<Dynamic>

예시

String jsonSampleData = """
{
  "name":"Harry",
  "age":17
}
""";

var decodedData = jsonDecode(jsonSampleData);
print(decodedData.runtimeType);		// _Map<String,dynamic>
print(decodedData);		// {name: Harry, age: 17}

 

 

객체에서 jsonEncodejsonDecode 사용하기

Map<String, dynamic> map = {"name": "Harry", "age": 17};

class Human {
  String name;
  int age;

  Human({
  required this.name, 
  required this.age
  });
  
  Human.fromJson(Map<String, dynamic> map)
    : this(name: map['name'], age: map['age']);

  Map<String, dynamic> toJson() {
    return {'name': name, 'age': age};
  }
}

 

1. class Human은 String 타입의 name, int 타입의 age 를 가진다.

2. name, age 는 non - nullable 타입으로, 반드시 값이 필요하기 때문에 required 키워드를 사용해 값을 주고 넘어간다.

3. Human.fromJson 함수로 map에서 'name', 'age'의 키 값을 가져와 Human 객체의 name, age에 설정한다.

4. toJson 함수로 Human 객체의 name, age를 값으로 가지는 Map을 생성한다.

 

 

여기서 fromJson, toJson 함수는 앞서 설명한 jsonEncode, jsonDecode와 비슷한 역할을 하지만, 약간의 차이점이 있다.

 

jsonEncode, jsonDecode : Map, List 같은 기본 자료형에 사용

fromJson, toJson : 클래스(객체)를 Map처럼 변환하거나, Map에서 객체를 만들 때 사용

 

 

또한, 1, 2, 3은 Human을 만들고 적절한 값을 부여하기 위해 필수적인 과정이지만, 4번은 그렇지 않다. 

결과만 놓고 본다면 4번 코드를 아예 삭제해도 class Human은 정상적으로 구현된다.

하지만 프로그램 확장성을 위해 언제든 객체를 Json으로 사용할 준비를 해 두는 것이 실제 개발에서는 필수적이다.

 

 

 

.generate 

주어진 길이만큼 리스트를 생성하고, 각 요소에 대해 반복적인 패턴의 값을 할당하는 데 사용한다.

 

List.generate() 문법

List.generate(length, (index) => expression);

 

length - 생성할 리스트의 길이

index - 인덱스. 0부터 시작함. (인덱스 사용하지 않는 경우 _ )

expression - 각 인덱스에 들어갈 값. 동일값 or 계산식

 

장점

반복문 대신 함수로 깔끔하게 리스트 생성 가능

반복적인 패턴이나 계산식을 사용하는 리스트를 생성 가능

 

예시

  • 0~9까지의 숫자 리스트 만들기
List<int> numbers = List.generate(10, (i) => i);  
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 

 

 

 

.filled

주어진 길이만큼 리스트를 생성하고, 모든 요소에 같은 값을 할당함

 

List.filled 문법

List.filled(length, E fill, {bool growable = false})

 

length - 생성할 리스트의 길이

fill - 각 요소에 채울 값

growable - 기본값 false. true로 설정하면 리스트 크기 변경 가능

 

장점

같은 값을 채운 리스트를 쉽게 생성할 수 있음.

리스트를 만들고 초기값을 부여할 때 유용함

 

예시

  • 기본 int 리스트 만들기
List<int> zeros = List.filled(1000, 0);

 

1000개나 되는 리스트의 초기값을 일일히 지정해주지 않고 함수로 표현할 수 있다.

 

 

 

.generate, .filled 동시 사용 예시

  • 빈 2차원 리스트 만들기

https://leetcode.com/problems/find-winner-on-a-tic-tac-toe-game/?envType=study-plan-v2&envId=programming-skills

// 3x3 빈 배열 보드 초기화
  List<List<String>> board = List.generate(3, (_) => List.filled(3,''));

// 리트코드 1275번 문제

 

// .filled 를 사용해 [' ', ' ', ' '] 생성

// .generate 를 사용해 [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']] 생성. 즉, 빈 3 x 3 보드 생성

 

다트 콘솔 어플리케이션으로 제작한 턴제 rpg 게임.

 

 

 

기본적으로

 

  • 캐릭터와 몬스터의 공격 기능
  • 캐릭터의 방어 태세 - 캐릭터 방어력 증가
  • 캐릭터의 아이템 사용 - 캐릭터 공격력 증가
  • 시작 시 30% 확률로 캐릭터에게 추가 체력 부여
  • 몬스터가 3턴마다 방어력을 얻는 기능

 

이렇게 기본 기능, 버프를 주는 기능만 있기 때문에

캐릭터에게 디버프를 주는 기능을 추가해 밸런스를 맞추고자 했다.

 

 

 

캐릭터 부상 기능

[설명]

  • 몬스터가 캐릭터를 공격해 데미지를 입힐 때마다 10%의 확률로 캐릭터에게 부상을 입힌다.
  • 캐릭터가 부상을 입으면 공격력이 2 감소한다.

[조건]

  • 몬스터가 캐릭터를 공격하더라도 데미지가 없으면 부상을 입히지 못한다.
  • Random함수를 이용해 확률 기능을 사용한다.

 

 

구현 코드

getInjured 메서드 구현

 

Character클래스 내부 메서드로 구현했다.

 

  1. Random nextInt를 이용해 10 미만인 양의 정수를 랜덤으로 호출한다
  2. int chance는 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 중 하나의 값을 가진다.
  3. if문에서는 chance값이 0 이하, 즉 0일 때만 이하 코드를 실행하도록 한다.
  4. 즉 1/10, 10%의 확률로 플레이어가 부상을 입어 공격력(atk)가 감소한다.

 

몬스터의 캐릭터 공격 메서드 내 getInjured 호출

Monster 클래스의 attackChar(몬스터의 캐릭터 공격 메서드)내부에서 getInjured를 호출한다.

 

  • 몬스터가 플레이어를 공격할 때
  • 몬스터가 캐릭터를 공격해서 1이상의 피해를 입힐 때

호출된다.

 

 

여기까지 구현하고 테스트를 하던 도중 재미있는 사실을 발견했다.

 

 

좌, 기존에 있던 기능. 우, 방금 추가한 캐릭터 부상 기능

몬스터는 3턴이 지날 때마다 방어력이 증가하고,

캐릭터는 몬스터를 처치하지 못하고 턴이 계속 늘어나면 확률에 따라 방어력이 감소한다.

 

따라서 배틀이 길어지면 몬스터의 방어력이 캐릭터의 공격력과 같거나 커지게 된다.\

 

이 경우, 캐릭터는 절대로 몬스터를 처치할 수 없다.

 

 

플레이어가 절대로 게임을 종료할 수 없는 것을 방지하기 위해 새로운 게임 종료 분기를 추가하고, 이를 활용해 게임을 끝내 주어야 한다.

 

 

 

1. isCalledGame 변수 생성

Game 클래스 내에 bool 타입으로 isCalledGame을 생성

기본값은 false

 

2. checkCalledGame 메서드 생성

Game 클래스 내에 checkCalledGame 메서드 생성

플레이어 공격력이 몬스터 방어력보다 작거나 같은 지 확인한다.

 

플레이어 공격 시 데미지 계산식은 

데미지 = {플레이어 공격력} - {몬스터 방어력}

이므로 플레이어 공격력이 몬스터 방어력보다 클 때에만 게임이 성립한다.

 

플레이어 공격력이 몬스터 방어력보다 작거나 같으면 isCalledGame 변수 값을 true로 바꾼다.

 

 

3. 전투 플로우를 담당하는 메서드에서 checkCalledGame 호출 

Game내의 battle메서드

 

몬스터 턴이 끝난 후, 즉

 

몬스터 공격이 끝난 후 => getInjured 호출 - 10퍼센트 확률로 캐릭터 공격력 감소
몬스터 턴 수가 올라간 후 => 몬스터 턴이 3의 배수인 경우 몬스터 방어력 증가

 

캐릭터의 공격력이 감소하고 몬스터의 방어력이 증가할 가능성이 있는 코드의 실행 직후에 checkColdGame을 실행한다.

 

isCalledGame 이 true면 break;로 battle메서드를 빠져나간다.

 

 

4. 게임 플로우를 담당하는 메서드에서 isCalledGame 확인

Game 내의 startGame메서드

 

battle메서드에서 break;를 사용하여 빠져나오자마자 다시 한 번 isCalledGame을 확인한다.

break;로 for문 escape,

endGame메서드로 게임을 종료한다.

 

 

배운 점

기능을 추가할 때 테스트에 따라 예상치 못한 결과가 나올 수도 있다는 것을 처음 체험했다.

여러 번 실행해 보고 다양한 값을 넣어 보면서 테스트하는 것의 필요성을 느꼈다.

 

 

예시코드

리스트 내용을 String형으로 변환하고 csv파일로 저장하는 메서드의 예시

 

writeAsStringSync 메서드

문자열 데이터를 파일에 저장할 때 사용

비동기 메서드

 

동기의 경우 writeAsString 으로 사용할 수 있다.

 

 

사용 방법

1. 파일 내용을 String 타입으로 생성한다.

2. 파일 경로를 String 타입으로 지정한다.

List 형식으로 된 파일 내용을 join()으로 String형으로 저장했다.

 

3. File 객체를 선언하고 경로를 지정한다.

 

4. writeAsStringSync 메서드를 사용하여 저장한다.

 

 

 

wirteAsStringSync 메서드 사용 형식

기본 형식

file.writeAsStringSync(
	저장할 문자열, 
    mode: FileMode.write
    encoding: utf8
);

 

( ) 내부의 코드에 대해 알아보자.

 

mode: 

 

파일 저장 방식을 지정

파일 내용을 덮어쓸지, 추가할지, 읽기 전용/쓰기 전용으로 할 지 결정

 

FileMode:write 

기본값. 기존 데이터를 삭제하고 새로운 데이터 저장.

 

FileMode.append

기존 데이터를 유지하고 새로운 데이터를 파일 끝에 추가.

 

FileMode.writeOnly

write와 동일하지만 읽기 불가능

 

FileModel.writeOnlyAppend

append와 동일하지만 읽기 불가능

 

FileMode.read

읽기 전용(쓰기 불가능)

 

 

encoding: 

문자열의 문자 인코딩 방식을 지정

 

utf8

기본값. 유니코드 기반으로, 한글, 이모지를 포함한 다양한 문자를 지원

 

ascii

ASCII문자. 영문과 숫자만 지원(한글, 이모지 불가)

 

 

지원하는 파일 형식

csv, json, xml, txt, yaml

정규표현식

문자열에서 패턴을 검색하는 기능을 수행함

특정 문자가 포함되어 있는지를 찾고, 대체, 문자열 반환, bool 값 반환 등을 처리할 수 있다.

 

 

먼저 사용할 패턴(조건)을 선언한다.

RegExp pattern = RegExp(r'패턴');

 

RegExp는 클래스의 일종이다.

위 코드는 RegExp 객체를 생성한다. 

 

 

패턴의 정의

기호 의미 동일한 코드
\d 숫자 [0-9]
\w 문자(숫자, 특수문자'_' 를 포함)(한글 미포함) [a-zA-Z0-9_]
\s 공백 문자(공백, 탭, 줄 바꿈)  
^ 문자열의 시작  
$ 문자열의 끝  

 

예시

영어 소문자, 숫자만 이용해서 비밀번호를 입력받고 싶은 경우

RegExp pattern = RegExp(r'^[a-z0-9]+$');

 

닉네임에 한글, 공백만 허용하는 경우

RegExp pattern = RegExp(r'^[가-힣\s]+$');

 

패턴을 선언한 후 메서드를 사용해 문자열을 검사, 가공하면 된다.

 

 

주요 메서드는 다음과 같다. 

 

1. hasMatch 

주어진 문자열에 패턴과 일치하는 부분이 하나라도 있는지 확인하여 bool 타입을 반환

bool hasMatch = pattern.hasMatch('문자열');

 

 

2. firstMatch

주어진 문자열에서 패턴과 첫 번째로 일치하는 부분을 찾아 Match 객체로 반환

일치하는 부분이 없는 경우 null 반환

Match? firstMatch = pattern.firstMatch('문자열');

 

 

3. allMatches

주어진 문자열에서 패턴과 일치하는 모든 부분을 찾아 Match 객체들의 Iterable을 반환

일치하는 부분이 없는 경우 빈 Iterable 반환

Iterable<Match> matches = pattern.allMatches('문자열');

Iterable은 Match의 열거형 타입이다. 

toList를 사용하면 List<Match>로 사용하는 것도 가능하다.

 

 

 

다트 콘솔 프로그램 실제 사용예시

-영문 소문자, 영문 대문자, 한글, 공백만 입력 가능

 

https://api.dart.dev/stable/3.5.3/dart-io/File-class.html

 

File class - dart:io library - Dart API

A reference to a file on the file system. A File holds a path on which operations can be performed. You can get the parent directory of the file using parent, a property inherited from FileSystemEntity. Create a new File object with a pathname to access th

api.dart.dev

dart 공식 문서에서 임포트한 csv 파일을 변환해서 출력하는 예시 코드를 따왔다. 

여기서 사용한 monsters.csv 파일의 내용은 다음과 같다.

 

Batman,30,20
Spiderman,20,30
Superman,30,10

 

 

가져온 파일을 변환하는 코드에 대해 살펴보겠다.

 

 

1. openRead

openRead()는 int형식의 스트림을 반환한다. 사람이 읽을 수 있는 형태가 아닌, utf 형식의 데이터이다. 

 

코드

stream형태의 결과를 반환하므로 listen을 사용해 리턴받아야 한다.

 

결과

listen 없이 openRead()자체의 값은 Stream이라는 것만 확인할 수 있다. 

listen으로 내부의 데이터를 가져오면 사람이 이해할 수 없는 숫자의 나열을 결과로 반환한다. 

 

 

2. utf8.decoder

1 에서 받은 Stream을 사람이 이해할 수 있는 형태로 변환한다. 여기서는 UTF-8로 인코딩된 바이트 데이터를 변환한다.

 

 

3. LineSplitter()

1의 openRead()내부 값을 보면 알 수 있듯, 줄 구분 없이 Stream형태로 값을 가져온 것을 볼 수 있다.

LineSplitter()은 해당 결과를 원본의 줄바뀜을 기준으로 나누어 주어, 결과적으로 한 줄이 하나의 스트링이 된다.

 

 

openRead()는 컴퓨터만 이해할 수 있는 형식의 결과를 가지고 비동기 방식으로 작동하기 때문에 속도가 빠르고 메모리를 적게 사용한다는 장점이 있다. 하지만 사람이 이해할 수 없기 때문에 결과를 사용하는 과정이 복잡하다. 

 

아래는 조금 더 직관적으로 csv 파일을 읽어오는 방법이다.

다만, 메모리 사용량이 openRead()보다 많기 때문에 크기가 작은 파일에 적합하다.

 

 

 

 

readAsString() 사용

코드

결과

결과 타입: String

줄나눔이 있어 각각 다른 String인 것 같지만 하나의 String이다. 

 

 

readAsLines() 사용

코드

결과

결과 타입: List<String>

줄별로 결과를 읽으므로 편리하나 메모리 사용량이 많다.

 

 

비동기 프로그래밍이란?

  • 동기 프로그래밍과 반대되는 개념.
  • 작업이 완료될 때까지 기다리지 않고, 미래의 특정 시점에 값을 반환함
  • 결과값이 나올 때까지 수행할 수 있는 다른 작업을 찾아서 진행함
  • 특정 코드를 실행할 때 다른 모든 작업이 멈추지 않음

=> 따라서 비동기 프로그래밍을 사용하면 코드의 효율을 높일 수 있다.

 

 

Future

비동기 프로그래밍에서 사용되는 클래스

작업이 성공적으로 완료되었을 때 결과를 반환하고 실행을 종료함

즉, 결과를 한 번만 반환할 수 있으므로 단일 비동기 작업에 사용한다.

 

사용 예시

Future<int> number = Future.value(1);
Future<String> name = Future.value('홍길동');

 

Future는 제너릭 클래스로, 다른 클래스를 감싸 주는 역할을 한다.

예시코드의 타입은 각각 int, String이다.

 

예시 코드

기본 코드와 결과

 

delayed()메서드

 

형식

Future.delayed(Duration(seconds: [지연 시간]));
Future.delayed(Duration(seconds: [지연 시간]), () {[지연 시간 후의 동작]});

 

예시 코드

2초 후에 delayed()메서드 안의 코드가 진행된다.

 

printOrder를 두 번 실행해 보면 작동 방식을 더욱 명확히 알 수 있다.

 

1.

printOrder('Latte')을 진행하여 delay가 없는 

print started
print completed

 

를 먼저 출력한다.

 

2.

delayed 메서드를 기다리는 사이 할 수 있는 다른 작업을 찾는다 => printOrder('Ice Cream')

마찬가지로 delay가 없는 

print started
print completed

 

를 출력한다.

 

3.

마지막으로 2초 후 각각의 delayed메서드 안의 코드를 실행한다.

your order is "Latte"
your order is "Ice Cream"

 

 

 

async와 await

async와 await는 짝으로 함께 사용되며, 비동기 코드를 동기 코드와 매우 유사해 보이게 한다.

예시와 함께 알아보는 것이 더욱 이해하기 쉽다.

 

https://dart-ko.dev/codelabs/async-await

 

비동기 프로그래밍: futures, async, await

DartPad로 비동기 코드를 작성법을 배우고 연습해봅시다!

dart-ko.dev

사용형식은 공식 문서를 참고.

 

예시코드

맨 처음 원본 코드처럼 코드가 순서대로 출력된 것을 확인할 수 있다. 

 

 

예시코드2

printOrder을 두 번 실행하면 어떤 차이가 생겼는지 더욱 명확히 보인다.

 

1.

printOrder('Latte')를 실행한다.

print started

를 출력한다

await가 있으므로 delayed메서드 안의 코드를 실행하기 위해 2초 기다린다.

 

2.

그 동안 실행할 수 있는다른 작업을 찾는다 =>printOrder('Ice Cream')

print started

를 출력한다.

await가 있으므로 delayed메서드 안의 코드를 실행하기 위해 2초 기다린다.

 

3.

2번의 delayed를 기다리는 동안 1번의 delayed가 완료된다.

your order is "Latte"
print completed

를 출력한다.

 

4.

2번의 delayed가 완료된다.

your order is "Ice Cream"
print completed

를 출력한다.

 

 

예시코드3

main 함수 안에 await를 넣고 main의 실행 코드 앞에  async를 넣어 주었다. 

main  내부의 printOrder실행 시 delay가 있는 작업을 기다려 준다는 뜻이므로, 

printOrder('Latte') 메서드가 완료된 후에야 printOrder('Ice Cream') 메서드가 실행된다는 것을 볼 수 있다.

개인과제 콘솔 쇼핑몰에서 제시된 기능을 모두 구현한 후

새로운 기능을 더 늘리기보다는 showTotal에서 사용자에게 구체적인 정보를 제공할 수 있게끔 기존 기능을 개선했다..

 

기존

상품명, 총액만 출력되기 때문에 정보가 부족함.

 

개선(목표)

쇼핑몰같은 느낌을 살리고 사용자에게 더 많은 정보를 줌

 

 

 

 

기존 코드

addToCart 장바구니에 물건담기 내부 코드

 

addToCart에서 물건을 담을 때 그 금액을 총액에 합산하고 있다. addToCart 실행 시 사용자에게 입력받은 상품 수량은 메서드 내 인스턴스 변수라 다른 메서드에서 출력할 수 없기 때문이다. 

 

showTotal 장바구니 총 가격보기totalPrice를 출력만 해 준다.

결과적으로 showTotal메서드가 addToCart에 의존하고 있다.

 

 

 

개선 코드

어디서든 카트에 담긴 상품 수량을 알 수 있도록 클래스에 새로운 변수를 만들었다.

이제 클래스 객체마다 각각의 수량(amount)를 저장할 수 있다.

 

 

 

showTotal 장바구니 총 가격 보기

이제 메서드가 호출될 시의 productList를 불러와 직접 총액을 계산한다.

다른 메서드에 의존하지 않는다.

 

clearCart 장바구니 초기화

기존엔 totalPrice만 0으로 초기화시켜 주는 방식을 사용하였으나,

모든 productamount를 0으로 초기화시키는 코드로 수정했다.

 

 

결과적으로 메서드 간 의존성을 줄이고 사용자의 입력을 더 효율적으로 저장, 사용하는 구현으로 수정했다.

 

 

 

 

추가 개선점

showTotal에서 세 개의 List를 선언하지 않고 함수만으로 구현할 수 있을 것 같은데, 개인적으로 리스트 인덱스 사용이 편해서 일단 리스트를 선언하는 방법을 채택했다. 

리스트를 따로 생성하는 것과 여러 개의 함수를 호출하는 것 중 어느 것이 더 비효율적인지 알아봐야 할 듯하다.

class Product {
  String name;
  int price;

  Product(this.name, this.price);
}

class ShoppingMall {
  List<Product> productList = [];

  void printProducts() {
    for (Product p in productList) {
      print('${p.name} / ${p.price} 원');
    }
  }
}

void main() {
  ShoppingMall mall = ShoppingMall();
  
  Map<String, int> products = {
    '셔츠': 10000,
    '바지': 20000,
    '치마': 30000,
    '양말': 1000,
    '신발': 50000,
  };

  List<String> keys = products.keys.toList();
  List<int> values = products.values.toList();

  for (var i = 0; i < products.length; i++) {
    Product product = Product(keys[i], values[i]);
    mall.productList.add(product);
  }
  
  mall.printProducts();
}

앱개발종합반에서 진행한 개인과제 중 일부 코드.

 

다양한 메서드를 사용했지만  마지막 4개의 메서드를 만드는 것보다 첫 메서드를 제대로 동작하도록 하는 것이 훨씬 오래 걸렸다. 메서드의 호출 때문이다. 

 

메서드

객체가 특정한 행동을 할 수 있게 해주는 함수의 일종.

 

class ShoppingMall {
  List<Product> productList = [];

  void printProducts() {
    for (Product p in productList) {
      print('${p.name} / ${p.price} 원');
    }
  }
}

printProducts() 가 메서드이다.

클래스 ShoppingMall내에 선언된 것을 볼 수 있다.

 

메서드는 클래스 내부에 존재하기 때문에 클래스에 의존한다.

따라서 메서드의 호출은 함수와 다른 방식으로 작동한다.

 

void main() {
  ShoppingMall mall = ShoppingMall();
  
  Map<String, int> products = {
    '셔츠': 10000,
    '바지': 20000,
    '치마': 30000,
    '양말': 1000,
    '신발': 50000,
  };

  List<String> keys = products.keys.toList();
  List<int> values = products.values.toList();

  for (var i = 0; i < products.length; i++) {
    Product product = Product(keys[i], values[i]);
    mall.productList.add(product);
  }
  
  mall.printProducts();
}

main() 내부에서 printProducts() 를 호출하고 있다.

 

 

ShoppingMall mall = ShoppingMall();

Class ShoppingMall 내부의 메서드이므로 ShoppingMall 객체인 mall을 생성한다.

 

mall.printProducts();

mall 로 클래스를 호출하여 메서드를 불러온다.

Dart 는 List, Set, Map 이라는 세 가지 Collection을 기본으로 지원한다.

이 중 유사한 개념인 List와 Set의 차이점을 확실히 정리했다.

 

List (배열)

:순서가 있는 값의 묶음

 

 

선언 규칙

 

List<[타입]> [변수 이름] = [요소]; 

List<String> colors = ['red', 'blue', 'green'];

 

[변수 이름] = [요소];

var numbers [1, 2, 3, 4, 5];

 

리스트 타입을 정해주지 않거나 var로 선언해도 된다. 

위 코드의 경우 타입을 int로 추론한다.

 

 

리스트의 타입을 정한 경우, 다른 타입의 요소를 넣으면 오류가 발생한다.

타입을 정하지 않은 경우 모든 요소를 포함하는 상위 클래스로 타입을 추론한다.

 

List<String> colors = ['red', 'blue', 'green', 000];
//The element type 'int' can't be assigned to the list type 'String'

String 타입을 정해주었으므로 int 타입의 000을 넣으면 오류가 발생한다.

 

var numbers = [1, 2, 10, 3.14];
print(numbers.runtimeType);
CONSOLE

List<num>

타입을 정해주지 않았으므로 int와 double타입의 상위 타입인 Numbers로 추론한다.

 

 

 

Set (집합)

: 순서가 없는 중복되지 않은 값의 묶음

 

 

선언 규칙

 

Set<타입> [변수 이름] = {요소};

Set<String> fruits = {'apple', 'orange', 'banana'};

 

변수 이름 = {요소};

var numbers = {1, 2, 3, 4, 5};

 

List와 마찬가지로 타입을 정해 줄 수도, 정하지 않을 수도 있다.

타입을 정하지 않은 경우 모든 요소를 포함하는 상위 타입을 자동으로 추론한다.

 

단, Set안에 값을 넣지 않는 경우 타입 지정이 필요하다. 

var names = <String>{}; 
print(names.runtimeType); // Set<String>
var names = {}; 
print(names.runtimeType); // Map<dynamic, dynamic>

 

타입 지정한 경우 Set, 지정하지 않은 경우 Map으로 선언됨

 

 

 

List와 Set의 차이점

 

한글명인 배열, 집합에서 알 수 있듯이

 

List는 순서가 정해진 값들의 묶음,

Set은 순서가 없는 값들의 묶음이다. 

 

 

 

일반적인 리스트, 집합의 이미지를 떠올리면 이해가 쉽다.

 

리스트는 순서대로 작성되어 있고, 집합은 순서 없이 하나의 틀 안에 모여있을 뿐이다.

 

또한, '순서(index)' 가 있기 때문에 리스트는 같은 값이라도 순서에 따라 구별되지만, 집합에서는 같은 값은 하나로 처리된다.

따라서 List에서만 index 관련 기능을 사용할 수 있다.

 

  List Set
선언방법 List<[타입]> [변수 이름] = [요소];  Set<타입> [변수 이름] = {요소};
[변수 이름] = [요소]; 변수 이름 = {요소};
Index 유무 O X
중복 허용 유무 O X
공통 사용 가능한 코드 .isEmpy, add(), addAll(), remove(), length, contains(), clear()
각 타입에서만 사용 가능한 코드 indexOf(), removeAt() containsAll()

 

+ Recent posts