Generics in Dart allow you to write code that can work with different types without sacrificing type safety. They enable you to create reusable components like functions, classes, and interfaces that can operate on various data types while ensuring compile-time type checking.
Here's a breakdown of generics in Dart:
1. What are Generics?
Generics provide a way to parameterize types. Instead of writing separate code for each type you want to support, you write code once and specify the type as a parameter. This makes your code more flexible, reusable, and type-safe.
2. Generic Functions
You can define generic functions by placing type parameters inside angle brackets <> after the function name.
T first<T>(List<T> list) {
if (list.isEmpty) {
throw StateError('List is empty');
}
return list[0];
}
void main() {
var numbers = [1, 2, 3];
var firstNumber = first<int>(numbers); // Type is explicitly specified
print(firstNumber); // Output: 1
var strings = ['hello', 'world'];
var firstString = first(strings); // Type is inferred
print(firstString); // Output: hello
}
In this example, T is the type parameter. The function first can work with a list of any type T. When you call the function, you can either explicitly specify the type (e.g., first<int>(numbers)) or let Dart infer the type (e.g., first(strings)).
3. Generic Classes
You can also define generic classes. This is useful for creating data structures that can hold elements of different types.
class Box<T> {
T value;
Box(this.value);
T getValue() => value;
}
void main() {
var intBox = Box<int>(10);
print(intBox.getValue()); // Output: 10
var stringBox = Box<String>('Dart');
print(stringBox.getValue()); // Output: Dart
var doubleBox = Box(3.14); // Type inference works here as well
print(doubleBox.getValue()); // Output: 3.14
}
Here, the Box class can hold a value of any type T.
4. Generic Interfaces
Interfaces can also be generic, allowing you to define contracts that work with different types.
abstract class Cache<K, V> {
V? get(K key);
void set(K key, V value);
}
class InMemoryCache<K, V> implements Cache<K, V> {
final Map<K, V> _cache = {};
@override
V? get(K key) => _cache[key];
@override
void set(K key, V value) => _cache[key] = value;
}
void main() {
final cache = InMemoryCache<String, int>();
cache.set('count', 1);
print(cache.get('count')); // Output: 1
}
5. Type Constraints
You can use the extends keyword to put constraints on the type parameters. This allows you to specify that a type must be a subtype of a certain class or implement a certain interface.
class Printable<T extends Object> {
final T value;
Printable(this.value);
void printValue(){
print(value.toString());
}
}
class MyClass{
@override
String toString() {
return "This is my class";
}
}
void main(){
final printable = Printable<MyClass>(MyClass());
printable.printValue(); // Output: This is my class
}
In this example, T extends Object ensures that T must be a subtype of Object, meaning it must have a toString() method.
Benefits of using Generics:
Type safety: Generics provide compile-time type checking, which helps catch type errors early in the development process.
Code reusability: You can write code once and use it with different types, reducing code duplication.
Improved performance: In some cases, generics can improve performance by avoiding runtime type checks.
More expressive code: Generics make your code more expressive and easier to understand by clearly indicating the types being used.
Generics are a powerful feature in Dart that can significantly improve the quality and maintainability of your code. They are widely used in the Dart SDK and in many third-party packages.
Here are ten Dart programs that showcase the use of generic collections (List, Set, Map, and Queue) with various operations. These examples cover a variety of tasks, including sorting, filtering, and organizing data.
void main() {
List<int> numbers = [5, 2, 9, 1, 5, 6, 7];
// Sorting
numbers.sort();
print("Sorted List: $numbers");
// Filtering: keeping only even numbers
List<int> evenNumbers = numbers.where((n) => n % 2 == 0).toList();
print("Even Numbers: $evenNumbers");
}
class Product {
String name;
double price;
Product(this.name, this.price);
}
void main() {
Map<String, Product> products = {
"P001": Product("Laptop", 1500.0),
"P002": Product("Tablet", 500.0),
};
products.forEach((id, product) {
print("Product ID: $id, Name: ${product.name}, Price: \$${product.price}");
});
}
dart
Copy code
void main() {
Set<int> setA = {1, 2, 3, 4};
Set<int> setB = {3, 4, 5, 6};
print("Union: ${setA.union(setB)}");
print("Intersection: ${setA.intersection(setB)}");
print("Difference: ${setA.difference(setB)}");
}
dart
Copy code
import 'dart:collection';
void main() {
Queue<String> taskQueue = Queue<String>();
taskQueue.addAll(["Task 1", "Task 2", "Task 3"]);
while (taskQueue.isNotEmpty) {
print("Processing: ${taskQueue.removeFirst()}");
}
}
dart
Copy code
void main() {
List<Map<String, dynamic>> employees = [
{"id": 1, "name": "Alice", "salary": 3000},
{"id": 2, "name": "Bob", "salary": 2500},
];
employees.forEach((employee) {
print("Employee ID: ${employee['id']}, Name: ${employee['name']}, Salary: \$${employee['salary']}");
});
}
dart
Copy code
void main() {
List<List<int>> matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
matrix.forEach((row) {
print(row);
});
}
dart
Copy code
class CustomStack<T> {
List<T> _stack = [];
void push(T value) => _stack.add(value);
T pop() => _stack.removeLast();
T peek() => _stack.last;
}
void main() {
CustomStack<int> intStack = CustomStack<int>();
intStack.push(10);
intStack.push(20);
print("Top of stack: ${intStack.peek()}"); // 20
print("Popped: ${intStack.pop()}"); // 20
}
dart
Copy code
class Contact {
String name;
String phone;
Contact(this.name, this.phone);
}
void main() {
List<Contact> phoneBook = [
Contact("Alice", "123-456-7890"),
Contact("Bob", "098-765-4321"),
];
phoneBook.forEach((contact) {
print("${contact.name}: ${contact.phone}");
});
}
dart
Copy code
void main() {
List<String> names = ["Alice", "Bob", "Anna", "John", "Amanda"];
Map<String, List<String>> groupedNames = {};
for (var name in names) {
String firstLetter = name[0];
groupedNames.putIfAbsent(firstLetter, () => []).add(name);
}
print(groupedNames);
}
dart
Copy code
void main() {
String text = "hello world";
Map<String, int> charFrequency = {};
for (var char in text.split('')) {
charFrequency.update(char, (count) => count + 1, ifAbsent: () => 1);
}
print("Character Frequency: $charFrequency");
}