Today, we'll explore two more important data structures — Map and Enum — and dive into the characteristics and applications of immutable collections.
I. Map: A Collection of Key-Value Pairs
A Map is a collection of key-value pairs, similar to dictionaries or hash tables in other languages. It allows quick access values quickly through unique keys, making it ideal for storing associated data.
1. Creating a Map
Method 1: Using literals (Recommended)
void main() {
// Create a Map with string keys and integer values
Map<String, int> ages = {'Zhang San': 20, 'Li Si': 22, 'Wang Wu': 19};
// Create a Map with integer keys and string values
Map<int, String> weekdays = {1: 'Monday', 2: 'Tuesday', 7: 'Sunday'};
print(ages); // Output: {Zhang San: 20, Li Si: 22, Wang Wu: 19}
print(weekdays); // Output: {1: Monday, 2: Tuesday, 7: Sunday}
}
Method 2: Using Map constructors
void main() {
// Create empty Maps
Map<String, String> emptyMap1 = {};
Map<String, String> emptyMap2 = Map<String, String>();
// Create a Map from entries
Map<String, int> numberMap = Map.fromEntries([
MapEntry('one', 1),
MapEntry('two', 2),
]);
print(numberMap); // Output: {one: 1, two: 2}
}
2. Key-Value Pair Operations
Map operations revolve around keys, including adding, accessing, modifying, and deleting:
void main() {
Map<String, String> capitals = {'China': 'Beijing', 'USA': 'Washington'};
// Accessing values (through keys)
print(capitals['China']); // Output: Beijing
// Adding new key-value pairs
capitals['Japan'] = 'Tokyo';
print(capitals); // Output: {China: Beijing, USA: Washington, Japan: Tokyo}
// Modifying values (through keys)
capitals['USA'] = 'Washington, D.C.';
print(
capitals,
); // Output: {China: Beijing, USA: Washington, D.C., Japan: Tokyo}
// Removing key-value pairs
capitals.remove('Japan');
print(capitals); // Output: {China: Beijing, USA: Washington, D.C.}
// Checking if a key exists
print(capitals.containsKey('China')); // Output: true
// Checking if a value exists
print(capitals.containsValue('London')); // Output: false
// Getting all keys/values
print(capitals.keys); // Output: (China, USA)
print(capitals.values); // Output: (Beijing, Washington, D.C.)
// Getting length
print(capitals.length); // Output: 2
// Clearing the Map
capitals.clear();
print(capitals); // Output: {}
}
3. Iterating Over Maps
There are several ways to traverse a Map, with the forEach method being most most commonly used:
void main() {
Map<String, double> prices = {'Apple': 5.99, 'Banana': 3.99, 'Orange': 4.50};
// Method 1: Iterating with forEach (Recommended)
prices.forEach((key, value) {
print('The price of $key is \$$value');
});
// Output:
// The price of Apple is $5.99
// The price of Banana is $3.99
// The price of Orange is $4.50
// Method 2: Iterating over keys and accessing values
for (String fruit in prices.keys) {
print('$fruit: \${prices[fruit]}');
}
// Method 3: Iterating over entries (MapEntry)
for (MapEntry<String, double> entry in prices.entries) {
print('Key: ${entry.key}, Value: \${entry.value}');
}
}
II. Enums: Named Constants for Fixed Collections
Enums are special types used to define fixed sets of named constants. They're perfect for representing scenarios with clearly defined options (like genders, states, types, etc.).
1. Defining Enums
Use the enum keyword to define enums:
// Define an enum for gender
enum Gender {
male, // Male
female, // Female
other, // Other
}
// Define an enum for order status
enum OrderStatus {
pending, // Pending payment
paid, // Paid
shipped, // Shipped
delivered, // Delivered
cancelled, // Cancelled
}
2. Using Enums
Enum values can be accessed directly and provide type safety:
enum Gender { male, female, other }
void main() {
// Declare an enum variable
Gender userGender = Gender.male;
// Comparing enum values
if (userGender == Gender.male) {
print('User is male');
}
// Using enums with switch-case (Recommended, as compiler checks for completeness)
switch (userGender) {
case Gender.male:
print('Gender: Male');
break;
case Gender.female:
print('Gender: Female');
break;
case Gender.other:
print('Gender: Other');
break;
}
// Getting all enum values
print(Gender.values); // Output: [Gender.male, Gender.female, Gender.other]
// Getting the name of an enum value (as string)
print(userGender.name); // Output: male
// Getting an enum value by name
Gender? gender = Gender.values.firstWhere(
(g) => g.name == 'female',
orElse: () => Gender.other,
);
print(gender); // Output: Gender.female
}
3. Use Cases for Enums
Enums are particularly suitable for:
- Representing fixed sets of options (genders, colors, seasons)
- State machines (order statuses, network states)
- Replacing "magic numbers" (improving code readability)
Anti-pattern (Not recommended):
// Using numbers for statuses (magic numbers, poor readability)
const int orderPending = 0;
const int orderPaid = 1;
Best practice (Recommended):
// Using enums for statuses (clear and understandable)
enum OrderStatus { pending, paid, shipped }
III. Immutable Collections (const/final) and Performance Impact
Collections are mutable by default (you can add/remove/modify elements), but in many scenarios we need immutable collections (cannot be modified after creation), which can be created using const or final.
1. Collections with final
A final collection has an immutable reference (cannot be reassigned), but its contents can be modified:
void main() {
final List<int> numbers = [1, 2, 3];
// Can modify collection contents
numbers.add(4);
print(numbers); // Output: [1, 2, 3, 4]
// Cannot reassign (compilation error)
// numbers = [5, 6, 7];
}
2. Collections with const
A const collection is completely immutable (neither contents nor reference can be modified) and is created at compile time:
void main() {
// Immutable List
const List<int> immutableList = [1, 2, 3];
// Immutable Set
const Set<String> immutableSet = {'a', 'b'};
// Immutable Map
const Map<String, int> immutableMap = {'one': 1, 'two': 2};
// Attempting to modify will cause errors
// immutableList.add(4); // Compilation error
// immutableSet.remove('a'); // Compilation error
// immutableMap['three'] = 3; // Compilation error
}
3. Performance Impact of Immutable Collections
Memory optimization: Identical const collections share memory (singleton), reducing memory usage.
void main() {
const list1 = [1, 2, 3];
const list2 = [1, 2, 3];
print(identical(list1, list2)); // Output: true (same memory address)
}
- Performance improvement: const collections are initialized at compile time, no need to recreate them at runtime, making them suitable for frequently used fixed data.
- Thread safety: Immutable collections are inherently thread-safe, no need to worry about concurrent modification in multi-threaded environments.
- Use cases: Configuration data, constant lists, fixed options, and other data that doesn't need modification.
IV. Collection Type Comparison and Selection Guide
Collection Type | Core Characteristics | Typical Use Cases |
---|---|---|
List | Ordered, allows duplicates, index access | Sequential data (list displays, array calculations) |
Set | Unordered, no duplicates, fast lookup | Deduplication, set operations (intersection/union) |
Map | Key-value pairs, fast access by key | Associated data (configuration tables, dictionaries, caches) |
Enum | Fixed constant collection, type-safe | Status representation, option selection, replacing magic numbers |
Selection recommendations:
- Use List when you need to store data in order
- Use Set when you need to ensure data uniqueness
- Use Map when you need to look up values by key
- Use Enum when you need to represent fixed options or states
Top comments (0)