4.1.4内部类和嵌套类:默认是嵌套类

像Java—样,在Kotlin中可以在另一个类中声明一个类a这样做在封装-个辅 助类或者把一些代码放到靠近它被使用的地方时非常有用。区别是Kotlin的嵌套类不能访问外部类的实例,除非你特别地做出了要求。让我们通过一个例子来展示为什么这很重要。

设想一下你想定义一个View元素,它的状态是可以序列化的。想要序列化一个视图可能并不容易,但是可以把所有需要的数据复制到另一个辅助类中去,你声明了 State接口去实现Serializable。View接口声明了可以用来保存视图状态的getCurrentState 和 restoreState 方法:

interface State: Serializable
interface View {
    fun getCurrentState(): State 
    fun restoreState(state: State(){}
}

可以方便地定义一个保存按钮状态的Button类。让我们来看看在Java中是怎 么做的:

/* Java */

public class Button implements View {
@override
public State getCurrentState(){
    return new ButtonState();
}
@Override
public void restoreState(State state) { /*...*/ }

public class ButtonState implements State { /*...*/ }

}

定义了实现State接口的ButtonState类,并且持有Button的特定信息。 在getCurrentState方法中,创建了这个类的一个新的实例。在真实情况下, 你需要使用所有需要的数据来初始化ButtonState。

这个代码有什么问题?为什么你会得到一个java.io.NotSerializable Exception: Button异常,如果你试图序列化声明的按钮的状态?这最开始可能会看起来奇怪:你序列化的变量是ButtonState类型的state,并不是Button 类型。

当你想起来这是在Java中时所有的事情都清楚了,当你在另一个类中声明一个 类时,它会默认变成内部类。这个例子中的ButtonState类隐式地存储了它的外部Button类的引用。这就解释了为什么ButtonState不能被序列化:Button 不是可序列化的,并且它的引用破坏了 ButtonState的序列化。

要修复这个问题,需要声明ButtonState类是static的,将一个嵌套类声明为static会从这个类中删除包围它的类的隐式引用。

在Kotlin中,内部类的默认行为与我们刚刚描述的是相反的,就像接下来的例子:

clasB Button : View {

override fun getCurrentState(): State = ButtonState()


override fun restoreState(state: State){}

//这个类与Java中的静态 :嵌套类类似
class ButtonState : State { /*..**/ }

}

Kotlin中没有显式修饰符的嵌套类与Java中的static嵌套类是一样的。要把它变成一个内部类来持有一个外部类的引用的话需要使用inner修饰符。嵌套类和内部类在Java与KotJin中的对应关系:

类A在另一个类B中声明 在Java中 在Kotlin中
嵌套类(不存储外部类的引用) static class A class A
内部类(存储外部类的引用) class A inner class A

在Kotlin中引用外部类实例的语法也与Java不同。需要使用this@Outer从 Inner类去访问Outer类:

class Outer {
    inner class Inner {
        fun getOuterReference{): Outer = this@Outer
    }
}

到此.己经学习了 Java和Kotlin中内部类和嵌套类的区别。
现在让我们来讨论另一个可能在Kotlin中很有用的嵌套类使用场景:创建一个包含有限数置的类的继承结构:

4.1.5密封类:定义受限的类继承结构

关于继承结构表达式的例子。父类Expr有两个子类:表示数字的Num,以及表示两个表达式之和的Sum。在when表达式中处理所有可能的子类固然很方便,但是必须提供一个else分支来处理没有任何其他分支能匹配的情况:

interface Expr
class Num(val value: Int) : Expr
class Sum{val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.right) + eval(e.left) 
        else ->
        //必须检查else分支
            throw IllegalArgumentException("Unknown expression")
}

当使用when结构来执行表达式的时候,Kotlin编译器会强制检查默认选项, 在这个例子中,不能返回一个有意义的值.所以直接抛出一个异常。

总是不得不添加一个默认分支很不方便。更重要的是,如果你添加了一个新的 子类.编译器井不能发现有地方改变了。如果你忘记了添加一个新分支,就会选择默认的选项,这有可能导致潜在的bug,

Kotlin为这个问题提供了一个解决方案:sealed类。为父类添加一个sealed 修饰符,对可能创建的子类做出严格的限制。所有的直接子类必须嵌套在父类中。

//将基类标记为密封的 
sealed class Expr {     
    class Num(val value: Int) : Expr()
    //将所有可能的类作为嵌套类列出
    class Sum (val left: Expr, val right: Expr) : Expr ()   
}   

fun eval(e: Expr): Int =
//"when”表达式涵盖了所 有可能的情况,所以不再 需要"else"分支
    when (e) {
        is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right) + eval(e.left)

如果你在when表达式中处理所有sealed类的子类,你就不再需要提供默认 分支。注意,sealed修饰符隐含的这个类是一个open类,你不再需要显式地添加 open修饰符。

当你在when中使用sealed类并且添加一个新的子类的时候,有返回值的 when表达式会导致编译失败,它会告诉你哪里的代码必须要修改。

在这种情况下,Expr类有一个只能在类内部调用的private构造方法。你也不能声明一个sealed接口。为什么?如果你能这样做,Kotlin编译器不能保证任 何人都不能在Java代码中实现这个接口。

在Kotlin 1.0中,sealed功能是相当严格的例如,所有的子类必须是嵌套的,并且子类不能创建为data类, Kotlin 1.1解除了这些限制并允许在同一文件的任何位置定义 sealed类的子类。

你应该还记得,在Kotlin中,冒号既可以用来继承一个类也可以实现一个接口。 那我们再来仔细看看一个子类的声明:

class Num(val value: Int) : Expr()

这个简单的例子应该己经很清楚了,除了 Expr()中类名后面的括号的含义, 我会在下一节讲到它,下一节将包含Kotlin中类初始化相关的内容。

你可能感兴趣的:(4.1.4内部类和嵌套类:默认是嵌套类)