目录
什么是泛型
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。
从字面的意思理解来看,泛型,泛就是模糊、暂不确定暂定的意思。可以这样理解,使用泛型就是定义的一个类型,类型暂不确定,给使用给一个占位符给代替,在使用的时候可以给确定其定义的类型。
语法
Collection_name <data_type> identifier = new Collection_name<data_type>
按照惯例,类型变量有单字母名称,例如E、T、S、K和V。
E、T、K、V是泛型中常用的几个名称,实际上定义泛型时完全可以不使用它们。不过这几个字母用得人多了,也就有了可读性上的意义,这样可以更好的进行协作。
- E - Element:元素
- T - Type:类型
- K - Key:键
- V - Value:值
为什么使用泛型
泛型通常是为了类型安全而必需的,它的好处不仅仅是保证代码的正常运行:
- 正确地指定泛型类型可以生成更好的代码
- 泛型来减少很多重复的代码
- 泛型可以在多种类型之间定义同一个实现
- 泛型使用时可以静态检查和推断类型,如果不符合泛型类型,会报编译错误
dart中所有基本类型数组和列表都是泛型,这样可以提高代码的可读性。
如果你打算使用一个仅仅包含字符串的List,你可以声明它为List(可理解为”字符串类型组成的List”),通过这种方式,你的程序员同事,以及你的工具(比如Dart编辑器和调试模式下的Dart虚拟机)能检测到将一个非字符串的变量分配到List中很可能是错误的:
var names = new List<String>();
names.addAll(["Seth","Kathy","Lars"]);
names.add(42);
以上代码会输出如下错误:
Compiler message:
lib/Genericity.dart:14:13: Error: The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42);
泛型可以减少代码重复。
// 官网代码
abstract class ObjectCache
{
Object getByKey(String key);
void setByKey(String key, Object value);
}
abstract class StringCache
{
String getByKey(String key);
void setByKey(String key, String value);
}
// 上面两个类,使用泛型可以精简为一个类
abstract class Cache<T>
{
T getByKey(String key);
void setByKey(String key, T value);
}
泛型方法
泛型方法可以约束一个方法使用同类型的参数、返回同类型的值,可以约束里面的变量类型。
void setData<T>(String key, T value)
{
print("key=${key}" + " value=${value}");
}
T getData<T>(T value)
{
return value;
}
main(List<String> args)
{
setData("name", "hello dart!"); // string类型
setData("name", 123); // int 类型
print(getData("name")); // string类型
print(getData(123)); // int 类型
print(getData<bool>("hello")); // Error,约束了类型是bool但是传入了String,所以编译器会报错
}
泛型类
声明泛型类,比如声明一个 Array 类,实际上就是 List 的别名,而 List 本身也支持泛型的实现。
class Array<T>
{
List _list = new List<T>();
Array();
void add<T>(T value)
{
this._list.add(value);
}
get value
{
return this._list;
}
}
main(List<String> args)
{
List l1 = new List<String>();
l1.add("aa");
l1.add("bb");
print(l1); // [aa, bb]
Array arr = new Array<String>();
arr.add("cc");
arr.add("dd");
print(arr.value); // [cc, dd]
Array arr2 = new Array<int>();
arr2.add(1);
arr2.add(2);
print(arr2.value); // [1, 2]
}
泛型接口
下面声明了一个 Storage 接口,然后 Cache 实现了接口,能够约束存储的 value 的类型:
abstract class Storage<T>
{
Map m = new Map();
void set(String key, T value);
void get(String key);
}
class Cache<T> implements Storage<T>
{
@override
Map m = new Map();
@override
void get(String key)
{
print(m[key]);
}
@override
void set(String key, T value)
{
m[key] = value;
print("set successed!");
}
}
main(List<String> args)
{
Cache ch = new Cache<String>();
ch.set("name", "123");
ch.get("name");
// ch.set("name", 1232); // type 'int' is not a subtype of type 'String' of 'value'x
Cache ch2 = new Cache<Map>();
ch2.set("ptbird", {"name": "pt", "age": 20});
ch2.get("ptbird");
// ch2.set("name", "23"); // type 'String' is not a subtype of type 'Map<dynamic, dynamic>' of 'value'
// 执行结果:
// set successed!
// 123
// set successed!
// {name: pt, age: 20}
}
其他
1)判断泛型对象的类型
可以使用is表达式来判断泛型对象的类型,如:
{
var names = new List<String>();
print(names is List<String>); // true
print(names is List<int>); // false
}
2)用于集合类型
List和map字面量也是可以参数化的:
- 参数化定义list,要在字面量前添加
- 参数化定义map,要在字面量前添加<keyType, valueType>
通过参数化定义,可以带来更安全的类型检查,并且可使用变量的自动类型推导,如下:
void main()
{
// 直接参数化
var animals = <String>["cat", "dog"];
var animalMap = <String, String>{
"cat": "tom",
"dog": "jerry"
};
// 使用构造函数参数化
var animalList = List<String>();
var animalMap2 = Map<String, String>();
animalList.addAll(['bird', 'dog']);
// 转换成set时,需要指定类型
var animalSet = Set<String>.from(animalList);
// 可以直接判定类型
print(animalList is List<String>); // true
}
3)构造函数中的泛型
在调用构造函数时,可以在类名后使用<…>来指定具体类型,如:
// 构造函数泛型
class Phone<T>
{
final T mobileNumber;
Phone(this.mobileNumber);
}
void main()
{
var p = Phone<String>("123456");
print(p.mobileNumber); // 123456
}
4)限制泛型参数类型
当实现一个泛型时,如果需要限制它参数的类型,可以使用extends
关键字。
class FootMassage
{
void doMassage()
{
print("走路");
}
}
// 泛型限制
class Massage<T extends FootMassage>
{
final T massage;
Massage(this.massage);
}
void main()
{
// 泛型限制, 通过extends关键字限定可泛型使用的类型
var footMassage = FootMassage();
var m = Massage<FootMassage>(footMassage);
m.massage.doMassage(); // 走路
}