Kotlin类型层次结构的旋风之旅

Mistaeks I Hav Made

Good judgement is the result of experience ... Experience is the result of bad judgement. — Fred Brooks

A Whirlwind Tour of the Kotlin Type Hierarchy

Kotlin has plenty of good language documentation and tutorials. But I’ve not found an article that describes in one place how Kotlin’s type hierarchy fits together. That’s a shame, because I find it to be really neat1.

Kotlin’s type hierarchy has very few rules to learn. Those rules combine together consistently and predictably. Thanks to those rules, Kotlin can provide useful, user extensible language features – null safety, polymorphism, and unreachable code analysis – without resorting to special cases and ad-hoc checks in the compiler and IDE.

Starting from the Top

All types of Kotlin object are organised into a hierarchy of subtype/supertype relationships.

At the “top” of that hierarchy is the abstract class Any. For example, the types String and Int are both subtypes of Any.

Any is the equivalent of Java’s Object class. Unlike Java, Kotlin does not draw a distinction between “primitive” types, that are intrinsic to the language, and user-defined types. They are all part of the same type hierarchy.

If you define a class that is not explicitly derived from another class, the class will be an immediate subtype of Any.

<span style="color:#000000"><code><span style="color:#333333"><strong>class</strong></span> <span style="color:#445588"><strong>Fruit</strong></span>(<span style="color:#333333"><strong>val</strong></span> ripeness: <span style="color:#0086b3">Double</span>)</code></span>

If you do specify a base class for a user-defined class, the base class will be the immediate supertype of the new class, but the ultimate ancestor of the class will be the type Any.

<span style="color:#000000"><code><span style="color:#333333"><strong>abstract</strong></span> <span style="color:#333333"><strong>class</strong></span> <span style="color:#445588"><strong>Fruit</strong></span>(<span style="color:#333333"><strong>val</strong></span> ripeness: <span style="color:#0086b3">Double</span>)
<span style="color:#333333"><strong>class</strong></span> <span style="color:#445588"><strong>Banana</strong></span>(ripeness: <span style="color:#0086b3">Double</span>, <span style="color:#333333"><strong>val</strong></span> bendiness: <span style="color:#0086b3">Double</span>): 
    Fruit(ripeness)
<span style="color:#333333"><strong>class</strong></span> <span style="color:#445588"><strong>Peach</strong></span>(ripeness: <span style="color:#0086b3">Double</span>, <span style="color:#333333"><strong>val</strong></span> fuzziness: <span style="color:#0086b3">Double</span>): 
    Fruit(ripeness)</code></span>

如果您的类实现了一个或多个接口,它将具有多个立即超类型,其中Any作为最终祖先。

<span style="color:#000000"><code><span style="color:#333333"><strong>interface</strong></span> <span style="color:#445588"><strong>ICanGoInASalad</strong></span>
<span style="color:#333333"><strong>interface</strong></span> <span style="color:#445588"><strong>ICanBeSunDried</strong></span>

<span style="color:#333333"><strong>class</strong></span> <span style="color:#445588"><strong>Tomato</strong></span>(ripeness: <span style="color:#0086b3">Double</span>): 
    Fruit(ripeness), 
    ICanGoInASalad, 
    ICanBeSunDried </code></span>

Kotlin类型检查器强制执行子类型/超类型关系。

例如,您可以将子类型存储到超类型变量中:

<span style="color:#000000"><code><span style="color:#333333"><strong>var</strong></span> f: Fruit = Banana(bendiness=<span style="color:#008080">0.5</span>)
f = Peach(fuzziness=<span style="color:#008080">0.8</span>)</code></span>

但是您不能将超类型值存储到子类型变量中:

<span style="color:#000000"><code><span style="color:#333333"><strong>val</strong></span> b = Banana(bendiness=<span style="color:#008080">0.5</span>)
<span style="color:#333333"><strong>val</strong></span> f: Fruit = b
<span style="color:#333333"><strong>val</strong></span> b2: Banana = f
<span style="color:#999988"><em>// Error: Type mismatch: inferred type is Fruit but Banana was expected </em></span></code></span>

可空类型

与Java不同,Kotlin区分“非null”和“nullable”类型。到目前为止我们看到的类型都是“非空”。Kotlin不允许null用作这些类型的值。您可以保证取消引用对“非null”类型的值的引用永远不会抛出NullPointerException。

类型检查器拒绝尝试使用null的代码或可预期非null类型的可空类型。

例如:

<span style="color:#000000"><code><span style="color:#333333"><strong>var</strong></span> s : String = <span style="color:#008080">null</span>
<span style="color:#999988"><em>// Error: Null can not be a value of a non-null type String</em></span></code></span>

如果您希望某个值可能为null,则需要使用值类型的可空等价项,用后缀“?”表示。例如,类型String?是可空的等价物String,因此允许所有String值加上null。

<span style="color:#000000"><code><span style="color:#333333"><strong>var</strong></span> s : String? = <span style="color:#008080">null</span>
s = <span style="color:#dd1144">"foo"</span>
s = <span style="color:#008080">null</span>
s = bar</code></span>

类型检查器确保您在不首先测试它不为空的情况下永远不会使用可空值。Kotlin为操作员提供了更方便的可空类型。有关示例,请参阅Kotlin语言参考Null Safety部分

当通过子类型关联非空类型时,它们的可空等价物也以相同的方式相关。例如,因为String是子类型AnyString?是子类型Any?,并且因为Banana是子类型Fruit,所以Banana?是子类型Fruit?

正如Any非null类型层次结构Any?的根一样,是可空类型层次结构的根。因为Any?是超类型AnyAny?是Kotlin类型层次结构的顶级。

非null类型是其可空等效的子类型。例如,String作为子类型Any,也是其子类型String?

This is why you can store a non-null String value into a nullable String? variable, but you cannot store a nullable String? value into a non-null String variable. Kotlin’s null safety is not enforced by special rules, but is an outcome of the same subtype/supertype rules that apply between non-null types.

This applies to user-defined type hierarchies as well.

Unit

Kotlin is an expression oriented language. All control flow statements (apart from variable assignment, unusually) are expressions. Kotlin does not have void functions, like Java and C. Functions always return a value. Functions that don’t actually calculate anything – being called for their side effect, for example – return Unit, a type that has a single value, also called Unit.

Most of the time you don’t need to explicitly specify Unit as a return type or return Unit from functions. If you write a function with a block body and do not specify the result type, the compiler will treat it as a Unit function. If you write a single-expression function, the compiler can infer the Unit return type, just like any other type.

<span style="color:#000000"><code><span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>example1</strong></span>() {
    println(<span style="color:#dd1144">"block body and no explicit return type, so returns Unit"</span>)
}

<span style="color:#333333"><strong>val</strong></span> u1: <span style="color:#0086b3">Unit</span> = example1()

<span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>example2</strong></span>() =
    println(<span style="color:#dd1144">"single-expression function for which the compiler infers the return type as Unit"</span>)

<span style="color:#333333"><strong>val</strong></span> u2: <span style="color:#0086b3">Unit</span> = example2()</code></span>

There’s nothing special about Unit. Like any other type, it’s a subtype of Any. It can be made nullable, so is a subtype of Unit?, which is a subtype of Any?.

The type Unit? is a strange little edge case, a result of the consistency of Kotlin’s type system. It has only two members: the Unit value and null. I’ve never found a need to use it explicitly, but the fact that there is no special case for “void” in the type system makes it much easier to treat all kinds of functions generically.

Nothing

At the very bottom of the Kotlin type hierarchy is the type Nothing.

As its name suggests, Nothing is a type that has no instances. An expression of type Nothing does not result in a value.

Note the distinction between Unit and Nothing. Evaluation of an expression type Unit results in the singleton value Unit. Evaluation of an expression of type Nothing never returns at all.

This means that any code following an expression of type Nothing is unreachable. The compiler and IDE will warn you about such unreachable code.

What kinds of expression evaluate to Nothing? Those that perform control flow.

For example, the throw keyword interrupts the calculation of an expression and throws an exception out of the enclosing function. A throw is therefore an expression of type Nothing.

By having Nothing as a subtype of every other type, the type system allows any expression in the program to actually fail to calculate a value. This models real world eventualities, such as the JVM running out of memory while calculating an expression, or someone pulling out the computer’s power plug. It also means that we can throw exceptions from within any expression.

<span style="color:#000000"><code><span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>formatCell</strong></span>(value: <span style="color:#445588"><strong>Double</strong></span>): String =
    <span style="color:#333333"><strong>if</strong></span> (value.isNaN()) 
        <span style="color:#333333"><strong>throw</strong></span> IllegalArgumentException(<span style="color:#dd1144">"<span style="color:#333333">$value</span> is not a number"</span>) 
    <span style="color:#333333"><strong>else</strong></span> 
        value.toString()</code></span>

It may come as a surprise to learn that the return statement has the type Nothing. Return is a control flow statement that immediately returns a value from the enclosing function, interrupting the evaluation of any expression of which it is a part.

<span style="color:#000000"><code><span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>formatCellRounded</strong></span>(value: <span style="color:#445588"><strong>Double</strong></span>): String =
    <span style="color:#333333"><strong>val</strong></span> rounded: <span style="color:#0086b3">Long</span> = <span style="color:#333333"><strong>if</strong></span> (value.isNaN()) <span style="color:#333333"><strong>return</strong></span> <span style="color:#dd1144">"#ERROR"</span> <span style="color:#333333"><strong>else</strong></span> Math.round(value)
    rounded.toString()</code></span>

A function that enters an infinite loop or kills the current process has a result type of Nothing. For example, the Kotlin standard library declares the exitProcess function as:

<span style="color:#000000"><code><span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>exitProcess</strong></span>(status: <span style="color:#445588"><strong>Int</strong></span>): <span style="color:#0086b3">Nothing</span></code></span>

If you write your own function that returns Nothing, the compiler will check for unreachable code after a call to your function just as it does with built-in control flow statements.

<span style="color:#000000"><code><span style="color:#333333"><strong>inline</strong></span> <span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>forever</strong></span>(action: ()-><span style="color:#0086b3">Unit</span>): <span style="color:#0086b3">Nothing</span> {
    <span style="color:#333333"><strong>while</strong></span>(<span style="color:#008080">true</span>) action()
}

<span style="color:#333333"><strong>fun</strong></span> <span style="color:#990000"><strong>example</strong></span>() {
    forever {
        println(<span style="color:#dd1144">"doing..."</span>)
    }
    println(<span style="color:#dd1144">"done"</span>) <span style="color:#999988"><em>// Warning: Unreachable code</em></span>
}</code></span>

Like null safety, unreachable code analysis is not implemented by ad-hoc, special-case checks in the IDE and compiler, as it has to be in Java. It’s a function of the type system.

Nullable Nothing?

Nothing, like any other type, can be made nullable, giving the type Nothing?Nothing? can only contain one value: null. In fact, Nothing? is the type of null.

Nothing? is the ultimate subtype of all nullable types, which lets the value null be used as a value of any nullable type.

Conclusion

When you consider it all at once, Kotlin’s entire type hierarchy can feel quite complicated.

But never fear!

I hope this article has demonstrated that Kotlin has a simple and consistent type system. There are few rules to learn: a hierarchy of supertype/subtype relationships with Any? at the top and Nothing at the bottom, and subtype relationships between non-null and nullable types. That’s it. There are no special cases. Useful language features like null safety, object-oriented polymorphism, and unreachable code analysis all result from these simple, predictable rules. Thanks to this consistency, Kotlin’s type checker is a powerful tool that helps you write concise, correct programs.


  1. “Neat” meaning “done with or demonstrating skill or efficiency”, rather than the Kevin Costner backstage at a Madonna show sense of the word

Copyright © 2016 Nat Pryce. Posted 2016-10-28. Share it.

猜你喜欢

转载自blog.csdn.net/a243920187/article/details/82499295
今日推荐