What are Generics in Dart?
Generics in Dart are a powerful feature that allows you to define classes, methods, and interfaces with a placeholder for the type of data they operate on. This enables you to create reusable components that can work with any data type while maintaining type safety. Generics help to reduce code duplication and improve code readability and maintainability.
1. Why Use Generics?
- Type Safety: Generics provide compile-time type checking, which helps catch type-related errors early in the development process.
- Code Reusability: You can write a single implementation that works with different data types, reducing code duplication.
- Improved Readability: Generics make it clear what type of data a class or method is working with, improving code clarity.
2. Defining a Generic Class
You can define a generic class by specifying a type parameter in angle brackets (<T>
) after the class name. This type parameter can then be used within the class to define properties and methods.
Example of a Generic Class
class Box<t> {
T item;
Box(this.item);
T getItem() {
return item;
}
}
void main() {
// Creating a Box for integers
Box<int> intBox = Box<int>(10);
print(intBox.getItem()); // Output: 10
// Creating a Box for strings
Box<string> stringBox = Box<string>('Hello, Dart!');
print(stringBox.getItem()); // Output: Hello, Dart!
}
</string></string></int></int></t>
In this example, we define a generic class Box
that can hold an item of any type T
. We create instances of Box
for both integers and strings, demonstrating how the same class can be reused for different data types.
3. Defining a Generic Method
In addition to generic classes, you can also define generic methods. A generic method can have its own type parameters, independent of the class's type parameters.
Example of a Generic Method
T getFirst<t>(List<t> items) {
return items.isNotEmpty ? items[0] : throw Exception('List is empty');
}
void main() {
List<int> numbers = [1, 2, 3];
print(getFirst(numbers)); // Output: 1
List<string> words = ['Hello', 'World'];
print(getFirst(words)); // Output: Hello
}
</string></int></t></t>
In this example, we define a generic method getFirst
that takes a list of any type T
and returns the first element. We demonstrate its usage with both a list of integers and a list of strings.
4. Bounded Generics
Dart also supports bounded generics, which allow you to restrict the types that can be used as type parameters. You can specify a superclass or interface that the type parameter must extend or implement.
Example of Bounded Generics
class ComparableBox<t extends comparable> {
T item;
ComparableBox(this.item);
bool isGreaterThan(T other) {
return item.compareTo(other) > 0;
}
}
void main() {
ComparableBox<int> intBox = ComparableBox<int>(10);
print(intBox.isGreaterThan(5)); // Output: true
ComparableBox<string> stringBox = ComparableBox<string>('Dart');
print(stringBox.isGreaterThan('C++')); // Output: true
}
</string></string></int></int></t>
In this example, we define a generic class ComparableBox
that restricts the type parameter T
to types that implement the Comparable
interface. This allows us to use the compareTo
method to compare items.
5. Conclusion
Generics in Dart provide a powerful way to create flexible and reusable components while maintaining type safety. By using generics, you can write code that works with any data type, reducing duplication and improving readability. Understanding how to define and use generics effectively is essential for building robust and maintainable Dart applications.