Data collections
Learning objectives
- You practice the basics of programming with Dart.
- You know the basics of working with list, map, and set with Dart.
List, Map, and Set
Dart comes with basic data collections that are available in most programming languages. These include the List, which is an array that grows in size when needed, the Map, which is a collection of key-value -pairs where each key is associated with at most one value, and the Set, which is a collection of objects where each object can occur at most once.
Lists are created using []
and maps are created using {}
. Adding to a list is done using a method called add
of the created list object that receives a value as a parameter, while setting a key-value -pair into a map is done using an assignment operation map[key] = value;
. These work as follows.
Sets are created with the same shorthand as maps, i.e. {}
, and they have a method called add
that is used for adding values to the set. However, for Dart to know that we are creating a set instead of a map, we need to explicitly state that the created object is a set. This is demonstrated below.
Dynamic data type
Dart is a strongly-typed language, where each value is associated with a type. A list object is a list, a map object is a map, and a set is a set. Dart is also statically typed, which means that the type of a variable cannot change. In the previous examples, however, we did not explicitly state the type of data that the collections can hold.
When we do not state the type of data that is added to a collection, Dart attempts to infer the data type. When creating an empty collection, there is no information from where to infer the data type, and thus, Dart assigns the data type as dynamic
. This leads to a situation, where we can add any type of values to the collection.
The type dynamic
is a specific type that can hold different types of values. The data type dynamic
allows strong and static typing, while giving flexibility to the programs. The example below demonstrates the use of the data type dynamic
explicitly in a program, showing the possibility of assigning different types of values to the same variable.
Avoid dynamic
While the above example shows the use of the type dynamic
, explicitly using the type dynamic
in programs is not advised. The problem with the type dynamic
is that using it removes the possibility to automatically detect and prevent type errors.
Collection types and type annotations
Defining data types for collections can be done in two ways. First, we can provide a type annotation to the shorthand used to create the collections. Type annotations are provided within smaller <
than and bigger than >
symbols. In the following example, we create a list that contains only integers, and a map that has integers as keys and strings as values.
If we use the type annotation for creating a set, we can discard the explicit type declaration of the variable. This is demonstrated in the following example, we create a set that can only contain strings.
Type annotations and type-safety
Defining the type annotations for the collections creates a situation, where Dart can check that the collections only include specific types of values. This in turn increases type-safety, which then allows providing type-specific warnings and errors to the developer.
The use of type annotations increases type-safety.
As an example, compiling or running the following application would show an error indicating that a string cannot be assigned to the parameter type int -- this stems from us attempting to place a string into a list that can only contain integers.
Function parameters and type annotations
As discussed previously, when defining local variables like the variables list
, map
and set
above, we let Dart infer the variable types. When defining non-local variables like function parameters, we want to be explicit about the types. In the case of non-local collections, we declare both the collection type and the data within the collection, whenever possible and meaningful.
For lists, the data type is List
, for maps, the data type is Map
, and for sets, the data type is Set
. Type annotations are placed immediately after the data type using the smaller than and larger than symbols, which then include the data type. As an example, the following outlines a function that could be used to print a list that contains integers.
Note that the need to declare the type annotations for a function can also be usecase-specific as, in some cases, not declaring the type annotations can yield greater generalizability. As an example, if we would create a function used to swap two values in a list, it likely would not be meaningful to restrict the type of data that the list can contain.
The following example outlines a function that can be used for swapping two items in a list. Note that the function works despite the type of data in the list.
Passing a reference...
We previously mentioned that Dart is pass-by-value
. The above example outlines a program where the value that is passed is a reference (the list). As the parameter list
in the function swap
holds a reference as the value, the function has access to the list-object that is being referenced, and can thus access the list and its methods.
The example also demonstrates how indexes are used with lists -- e.g. the value at the index 3 of a list called numbers
can be accessed (and set) using numbers[3]
.