联合

联合类型 属于 Crystal

1 个练习

关于 联合类型

Crystal 允许一个变量包含多种类型。 这叫做联合类型。 在 Crystal 中,联合类型通常由编译器推断出来。

Note

联合类型在运行时仍然是单一类型,即使它由多种类型组成。 这意味着,如果一个联合类型由String和Int32构成,它不会同时是两者。 相反,它要么是String,要么是Int32。

声明联合类型的方法是用竖线(|)分隔各个类型。 它们通常放在括号中,但并非强制要求。 最常见的联合类型是(T | Nil),其中T是一个类型,它出现在可以返回Nil的方法中。 这也称为变量可为 nil。

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

它并不局限于两种类型,你想要多少种都可以。

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof 与 Object#class

有两种方法可以获取变量的类型。 一种是使用 typeof,另一种是使用 Object#class。 区别在于,typeof返回变量在编译时的类型,而Object#class返回运行时的类型。 这意味着,如果你想判断一个变量是否为联合类型,Object#class无法告诉你,因为它在运行时只返回单一的类型。

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

联合类型的操作

由于联合类型在运行时是单一类型,所有标准操作都可以作用于它。 但在编译代码时,编译器需要知道它究竟是哪种类型。 因此,当你想使用特定于某个类型的操作时,代码必须安排成只能取其中某一种类型。

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

针对联合类型,Crystal 确实有一个特殊的方法:is_a? 方法,它接受一个类型作为实参并返回布尔值。 nil? 方法是 is_a?(Nil) 的简写。 把 is_a? 方法放进控制表达式,会告诉编译器它是什么类型,从而保证它就是那个类型。 而在 else 分支中,则可以保证它不是那个类型。

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

这个 is_a? 不局限于只接受单一类型作为实参,也可以接受联合类型。 它还可以结合 && 来同时检查多个类型。

Note

is_a? 方法在与控制表达式结合使用时,不能作用于实例变量或类变量。 需要先把它们赋值给一个局部变量。

as

把联合类型变成单一类型的一种方法,是让某个分支只有在类型为特定类型时才能进入。 另一种做法是使用 as 方法。 它会通过运行时检查,把联合类型变成单一类型。 如果类型不是预期类型,就会抛出异常。

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

这种做法只适用于你确定类型就是预期类型的情况,或者你希望在类型不符时抛出异常的情况。

在设置不当的情况下使用这种做法,可能会导致意外行为。

as?

as? 的用法与 as 非常相似,但如果类型不是预期类型,它会返回 nil 而不是抛出异常。 这意味着它会返回一个由预期类型和 Nil 组成的联合类型。

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

可为 nil 的简写

可为 nil 意味着变量要么是某种类型,要么是Nil。 它可以写成(T | Nil)。 但由于可为 nil 的类型比较常见,所以有一个简写:T?。

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

推断联合类型

如果编译器不确定是哪种类型,它就会推断出一个联合类型。 例如,在下面的代码中,编译器不知道foo是哪一种类型,因为它既可能是String,也可能是Int32。

if true
  foo = 1
else
  foo = "Hello"
end

typeof(foo) # => (Int32 | String)

这种推断是自动进行的,还有其他一些场景,例如编译器会推断某个方法返回联合类型。

character = "Hello world"[10]?
typeof(character) # => (Char | Nil)
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 联合类型

练习已锁定

再解锁 1 个练习即可练习 联合类型