本节书摘来自华章出版社《Hack与HHVM权威指南》一书中的第2章,第2.4节,作者 Owen Yamauchi,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
在泛型实体的定义内,类型检查器对于类型形参一无所知,这是掌握泛型类型的要点所在。这就意味着,当一个值的类型是个类型形参时,你不能对它能做很多事情,你不能调用它,不能在它上调用方法或者访问属性,不能对它进行索引,不能在它上面做算术运算,或者其他诸如此类的事情。但一个重要的例外是相等和恒等比较(==,===,!=,!==)被允许。当然,可以通过对类型形参添加一个约束而做出一些改变。这个约束将限制类型形参允许是什么值。语法就是添加一个关键词“as”以及一个类型标注在类型形参列表的识别符后面。让我们回到那个关于Wrapper类的引导示例上来,并且对它的类型形参添加一个约束:
class Wrapper<Tval as num> { private Tval $value; public function __construct(Tval $value) { $this->value = $value; } public function setValue(Tval $value): void { $this->value = $value; } public function getValue(): Tval { return $this->value; } }在这里,对于一个和num兼容的类型值,任何能够使用这个类的代码都能够这样做:
function f(int $int, float $float, num $num, int $nullint, string $string, mixed $mixed): void { $w = new Wrapper($int); // OK $w = new Wrapper($float); // OK $w = new Wrapper($num); // OK $w = new Wrapper($nullint); // 错误 $w = new Wrapper($string); // 错误 $w = new Wrapper($mixed); // 错误 }这就意味着,在Wrapper定义内部,对于“类型为Tval的值可以进行的操作和对于类型为num的值可以进行的操作”是一样的。所以,我们可以添加这样一个方法:
class Wrapper<Tval as num> { private Tval $value; public function add(Tval $addend): void { // $this->value是个已知的num类型,所以可以在上面使用+=操作符 $this->value += $addend; } // ... }可以使用任何合法的类型标注作为约束。最常见的情况就是使用一个类或者接口的名字作为约束。这样做的话,你将可以调用在类或者接口中声明好的方法:
interface HasID { public function getID(): int; } function write_to_database<Tval as HasID>(Tval $value): void { $id = $value->getID(); // ... }每个类型形参至多有一个约束。如果你希望限制一个类型形参为某个单独的类,而这个类又实现了多个特定的接口,你可以通过对它们进行扩展,创建一个合并所有上述接口的新接口,然后在约束中使用这个新的接口。
interface HasID { public function getID(): int; } interface HasHashCode { public function getHashCode(): string; } interface HasIDAndHashCode extends HasID, HasHashCode { } function write_to_cache<Tval as HasIDAndHashCode>(Tval $value): void { $id = $value->getID(); $hash_code = $value->getHashCode(); // ... }这里并没有办法表述像Tval那样必须实现这个接口或者那个接口的约束。正如我们所看到的一样,约束的类型可以是任何合法的类型标注。这包括其他的类型形参,甚至在同一个形参列表中靠前的类型形参。例如,下面约束的使用方式是合法的:
class GenericClass<Tclass> { public function genericMethod<Tmethod as Tclass>(): Tmethod { // ... } } function lookup<Tvalue, Tdefault as Tvalue>(string $key, ?Tdefault $default = null): Tvalue { // ... }