泛型¶
泛型允许您根据其他类型参数化类型。 泛型提供类型多态性。 考虑一个 Box 类型
class MyBox(T)
def initialize(@value : T)
end
def value
@value
end
end
int_box = MyBox(Int32).new(1)
int_box.value # => 1 (Int32)
string_box = MyBox(String).new("hello")
string_box.value # => "hello" (String)
another_box = MyBox(String).new(1) # Error, Int32 doesn't match String
泛型在实现集合类型时特别有用。 Array
、Hash
、Set
是泛型类型,Pointer
也是。
允许使用多个类型参数
class MyDictionary(K, V)
end
任何名称都可以用作类型参数
class MyDictionary(KeyType, ValueType)
end
泛型类方法¶
泛型类型的类方法中的类型限制在接收者的类型参数未指定时变成自由变量。 这些自由变量然后从调用的参数中推断出来。 例如,您也可以编写
int_box = MyBox.new(1) # : MyBox(Int32)
string_box = MyBox.new("hello") # : MyBox(String)
在上面的代码中,我们不必指定 MyBox
的类型参数,编译器根据以下过程推断它们
- 编译器从
MyBox#initialize(@value : T)
生成一个MyBox.new(value : T)
方法,它没有明确定义的自由变量 MyBox.new(value : T)
中的T
尚未绑定到类型,并且T
是MyBox
的类型参数,因此编译器将其绑定到给定参数的类型- 编译器生成的
MyBox.new(value : T)
调用MyBox(T)#initialize(@value : T)
,其中T
现在已绑定
通过这种方式,泛型类型在使用时更不容易冗长。 请注意,#initialize
方法本身不需要为此指定任何自由变量。
相同的类型推断也适用于除 .new
之外的类方法
class MyBox(T)
def self.nilable(x : T)
MyBox(T?).new(x)
end
end
MyBox.nilable(1) # : MyBox(Int32 | Nil)
MyBox.nilable("foo") # : MyBox(String | Nil)
在这些示例中,T
仅作为自由变量推断,因此接收者本身的 T
仍然未绑定。 因此,在无法推断 T
的情况下调用其他类方法将导致错误
module Foo(T)
def self.foo
T
end
def self.foo(x : T)
foo
end
end
Foo.foo(1) # Error: can't infer the type parameter T for the generic module Foo(T). Please provide it explicitly
Foo(Int32).foo(1) # OK
泛型结构体和模块¶
结构体和模块也可以是泛型的。 当模块是泛型时,您像这样包含它
module Moo(T)
def t
T
end
end
class Foo(U)
include Moo(U)
def initialize(@value : U)
end
end
foo = Foo.new(1)
foo.t # Int32
请注意,在上面的示例中,T
变成了 Int32
,因为 Foo.new(1)
使 U
变成了 Int32
,这反过来又通过包含泛型模块使 T
变成了 Int32
。
泛型类型继承¶
泛型类和结构体可以被继承。 继承时,您可以指定泛型类型的一个实例,也可以委托类型变量
class Parent(T)
end
class Int32Child < Parent(Int32)
end
class GenericChild(T) < Parent(T)
end
具有可变数量参数的泛型¶
我们可以使用 散列运算符 定义具有可变数量参数的泛型类。
让我们看一个定义名为 Foo
的泛型类的示例,然后我们将使用不同数量的类型变量来使用它
class Foo(*T)
getter content
def initialize(*@content : *T)
end
end
# 2 type variables:
# (explicitly specifying type variables)
foo = Foo(Int32, String).new(42, "Life, the Universe, and Everything")
p typeof(foo) # => Foo(Int32, String)
p foo.content # => {42, "Life, the Universe, and Everything"}
# 3 type variables:
# (type variables inferred by the compiler)
bar = Foo.new("Hello", ["Crystal", "!"], 140)
p typeof(bar) # => Foo(String, Array(String), Int32)
在下面的示例中,我们通过继承定义类,为泛型类型指定实例
class Parent(*T)
end
# We define `StringChild` inheriting from `Parent` class
# using `String` for generic type argument:
class StringChild < Parent(String)
end
# We define `Int32StringChild` inheriting from `Parent` class
# using `Int32` and `String` for generic type arguments:
class Int32StringChild < Parent(Int32, String)
end
如果我们需要用 0 个参数实例化一个 class
? 在这种情况下,我们可以这样做
class Parent(*T)
end
foo = Parent().new
p typeof(foo) # => Parent()
但我们不应该将 0 个参数与不指定泛型类型变量混淆。 以下示例将引发错误
class Parent(*T)
end
foo = Parent.new # Error: can't infer the type parameter T for the generic class Parent(*T). Please provide it explicitly
class Foo < Parent # Error: generic type arguments must be specified when inheriting Parent(*T)
end