4.3 两个特殊类型

4.3.1 Any 类型

在 Julia 的类型图中,Any是一个唯一的顶层类型。如果说超类型在上、子类型在下的话,那么它就处在类型图的最顶端。Any类型是所有类型的直接或间接的超类型。也就是说,对于任意类型的变量x,类型断言x::Any都必定是成功的。

还记得吗?我们在前面定义第一个sum1函数的时候,并没有为它的两个参数指定类型。然而,在这种情况下,这两个参数实际上都会有一个缺省的类型,即:Any类型。这也是为什么我们可以用任何类型的值作为参数值调用这个sum1函数的原因。

再比如,我们可以定义如下的原语类型(我们稍后会讲到这种类型):

  1. julia> primitive type MyWord 64 end
  2. julia>

注意,我们没有显式地指定它的超类型。然而,在这种情况下,MyWord类型会有一个缺省的超类型,同样是Any类型。也就是说,这个MyWord类型是Any类型的直接子类型。

更宽泛地讲,Any类型会在很多情况下担当默认类型并发挥其作用。我们在后面还会遇到类似的情形。另外,Any类型是一个抽象类型。因此它本身是不能被实例化的。但所有的值却都是它的实例。

4.3.2 Union{} 类型

在 Julia 的类型图中,还有一个与Any完全相对的类型。它就是Union{}类型。由于这个类型是所有类型的子类型,所以它是一个底层类型,并且也是唯一的一个。它处在类型图的最底端。也就是说,对于任意类型的变量x,类型断言x::Union{}都必定是失败的。另外,与Any一样,Union{}也是一个抽象类型。

从字面上我们就可以看出,Union{}是一个被参数化的类型。它的源类型是Union{Types...}类型,其中的Types...代表任意个类型参数。如果这里有多个类型参数,那么它们之间需要用英文逗号分隔开。

这个Union{Types...}类型有着一种很特殊的用途。我们可以利用它,让一个单一的类型字面量代表多个类型。换句话说,把多个类型联合在一起形成一个类型,并让后者作为前者的统一代表。因此,我们也可以把这个类型称为联合类型。而每一个类型参数的组合都可以代表一种联合类型。示例如下:

  1. julia> IntOrString = Union{Integer, AbstractString}
  2. Union{AbstractString, Integer}
  3. julia> 2020::IntOrString
  4. 2020
  5. julia> "2020"::IntOrString
  6. "2020"
  7. julia>

类型Union{Integer, AbstractString}表示的是Integer类型和AbstractString类型的联合。因此,任何Integer类型或AbstractString类型的实例都可以被视为这个联合类型的实例。这就是类型断言2020::IntOrString"2020"::IntOrString可以成功的原因。

另外,由于 Julia 中的类型属于一类特殊的值(DataType类型的值),所以上述的联合类型自然也就可以与标识符IntOrString绑定在一起。这时,我们可以说IntOrString是那个联合类型的别名(alias)。

搞清楚了联合类型以及它的用途,我们就很容易理解“Union{}类型处在类型图的最底端”的原因了。由于它的花括号中没有任何类型参数,所以这种联合类型也就代表不了任何类型,相当于一个“虚无”的类型。而任何类型都比“虚无”包含了更多的东西,所以它们都是这种联合类型的超类型。如果我们使用操作符<:在这些类型之间做判断的话,就可以很形象地看到这种关系:

  1. julia> Union{} <: Integer
  2. true
  3. julia> Union{} <: Union{Integer}
  4. true
  5. julia>

此示例中的两个表达式的结果值都是true。这说明整数类型Integer和联合类型Union{Integer}都是“虚无”类型Union{}的超类型。

至此,我们已经较为充分地了解了 Julia 类型图中的两端,即:最顶端的Any和最底端的Union{}。下面,我们一起来看看在它们之间的类型都有哪些。