我是Kotlin的初学者,最近读过有关密封课程的内容.但是从文档来看,我实际得到的唯一想法是它们存在.
该文件指出,他们"代表受限制的阶级等级制度".除此之外,我发现了一个声明,他们是超级大国的枚举.这两个方面实际上都不清楚.
那么你可以帮我解决以下问题:
更新: 我仔细检查了这篇博客文章,仍然无法理解这个概念.正如帖子中所述
效益
该功能允许我们定义在类型中受限制的类层次结构,即子类.由于所有子类都需要在密封类的文件中定义,因此编译器不了解未知子类的可能性.
为什么编译器不知道其他文件中定义的其他子类?即使IDE知道这一点.Ctrl+Alt+B
例如,只需在IDEA上按下List<>
定义,即使在其他源文件中也会显示所有实现.如果某个子类可以在某个第三方框架中定义,而该应用程序中未使用该框架,我们为什么要关心它?
Dav*_*son 34
假设您有一个域(您的宠物),您知道有一个明确的类型枚举(计数).例如,您有两只且只有两只宠物(您将使用所谓的类进行建模MyPet
).Meowsi是你的猫,Fido是你的狗.
比较该设计示例的以下两种实现:
sealed class MyPet
class Meowsi : MyPet()
class Fido : MyPet()
Run Code Online (Sandbox Code Playgroud)
因为你已经使用了密封类,当你需要根据宠物的类型执行一个动作时,可能MyPet
会在两个中耗尽,你可以确定MyPet
实例将是两个选项中的一个:
fun feed(myPet: MyPet) {
when(myPet) {
is Meowsi -> print("Giving cat food to Meowsi!")
is Fido -> print("Giving dog biscuit to Fido!")
}
}
Run Code Online (Sandbox Code Playgroud)
如果你不使用密封类,那么两种可能性并没有用尽,你需要包括一个else
声明:
open class MyPet
class Meowsi : MyPet()
class Fido : MyPet()
fun feed(myPet: MyPet) {
when(myPet) {
is Mewosi -> print("Giving cat food to Meowsi!")
is Fido -> print("Giving dog biscuit to Fido!)
else -> print("Giving food to someone else!") //else statement required or compiler error here
}
}
Run Code Online (Sandbox Code Playgroud)
换句话说,没有密封的类,就没有可能性的耗尽(完全覆盖).
请注意,使用Java可能会耗尽可能性,enum
但这些并不是完全成熟的类.例如,enum
不能是另一个类的子类,只实现一个接口(感谢EpicPandaForce).
完全耗尽可能性的用例是什么?举一个类比,想象一下你预算紧张,你的饲料非常珍贵,你要确保你最终不会喂养不属于你家庭的额外宠物.
没有sealed
课程,你家/应用程序中的其他人可以定义一个新的MyPet
:
class TweetiePie : MyPet() //a bird
Run Code Online (Sandbox Code Playgroud)
这个不需要的宠物将由你的feed
方法喂养,因为它包含在else
声明中:
else -> print("Giving food to someone else!") //feeds any other subclass of MyPet including TweetiePie!
Run Code Online (Sandbox Code Playgroud)
同样,在您的程序中,可能性的耗尽是可取的,因为它减少了应用程序所处的状态数,并减少了在行为定义不明确的可能状态下发生错误的可能性.
因此需要sealed
课程.
Yog*_*ity 10
当您了解密封类旨在解决的问题类型时,它们会更容易理解。首先我会解释问题,然后我将逐步介绍类层次结构和受限制的类层次结构。
我们将采用一个在线送货服务的简单示例,其中我们使用三种可能的状态Preparing
,Dispatched
并Delivered
显示在线订单的当前状态。
标记类
这里我们对所有状态使用一个类。枚举用作类型标记。它们用于标记 states Preparing
、Dispatched
和Delivered
:
class DeliveryStatus(
val type: Type,
val trackingId: String? = null,
val receiversName: String? = null) {
enum class Type { PREPARING, DISPATCHED, DELIVERED }
}
Run Code Online (Sandbox Code Playgroud)
以下函数借助枚举检查当前传递的对象的状态并显示相应的状态:
fun displayStatus(state: DeliveryStatus) = when (state.type) {
PREPARING -> print("Preparing for dispatch")
DISPATCHED -> print("Dispatched. Tracking ID: ${state.trackingId ?: "unavailable"}")
DELIVERED -> print("Delivered. Receiver's name: ${state.receiversName ?: "unavailable"}")
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我们能够正确显示不同的状态。when
多亏了枚举,我们还可以使用详尽的表达式。但这种模式存在各种问题:
多重责任
该类DeliveryStatus
具有代表不同状态的多重职责。因此,如果我们为不同的状态添加更多的函数和属性,它就会变得更大。
超出需要的属性
对象在特定状态下拥有的属性多于其实际需要的属性。例如,在上面的函数中,我们不需要任何属性来表示状态Preparing
。财产trackingId
仅用于国家Dispatched
,receiversName
财产仅与国家有关Delivered
。对于函数来说也是同样的道理。我没有展示与状态相关的函数,以保持示例较小。
不保证一致性
由于这些未使用的属性可以从不相关的状态设置,因此很难保证特定状态的一致性。例如,可以设置receiversName
状态的属性Preparing
。在这种情况下,这Preparing
将是非法状态,因为我们无法获得尚未交付的货物的收件人姓名。
需要处理null
值
由于并非所有属性都用于所有状态,因此我们必须保持属性可为空。这意味着我们还需要检查可空性。在函数中,displayStatus()
我们使用 (elvis) 运算符检查可空性,如果该属性为 ,则?:
显示。这使我们的代码变得复杂并降低了可读性。另外,由于可能存在可为空值,因此进一步降低了一致性的保证,因为in的值unavailable
null
null
receiversName
Delivered
是非法状态。
不受限制的类层次结构:abstract class
我们不是管理单个类中的所有状态,而是将状态分成不同的类。我们从 an 创建一个类层次结构,abstract class
以便我们可以在displayStatus()
函数中使用多态性:
abstract class DeliveryStatus
object Preparing : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
class Delivered(val receiversName: String) : DeliveryStatus()
Run Code Online (Sandbox Code Playgroud)
trackingId
现在仅与状态关联并且Dispatched
仅receiversName
与Delivered
。这解决了多重职责、未使用的属性、缺乏状态一致性和空值的问题。
我们的displayStatus()
函数现在如下所示:
fun displayStatus(state: DeliveryStatus) = when (state) {
is Preparing -> print("Preparing for dispatch")
is Dispatched -> print("Dispatched. Tracking ID: ${state.trackingId}")
is Delivered -> print("Delivered. Received by ${state.receiversName}")
else -> throw IllegalStateException("Unexpected state passed to the function.")
}
Run Code Online (Sandbox Code Playgroud)
由于我们摆脱了null
值,我们可以确定我们的属性将始终具有一些值。所以现在我们不需要null
使用?:
(elvis) 运算符检查值。这提高了代码的可读性。
因此,我们通过引入类层次结构解决了标记类部分中提到的所有问题。但不受限制的类层次结构有以下缺点:
无限制多态性
通过不受限制的多态性,我的意思是我们的函数displayStatus()
可以传递无限数量的DeliveryStatus
. 这意味着我们必须处理 中的意外状态displayStatus()
。为此,我们抛出一个异常。
需要else
分公司
由于多态性不受限制,我们需要一个else
分支来决定当意外状态发生时要做什么。如果我们使用某种默认状态而不是抛出异常,然后忘记处理任何新添加的子类,那么将显示默认状态而不是新创建的子类的状态。
没有详尽的when
表达
由于 的子类abstract class
可以存在于不同的包和编译单元中,因此编译器不知道 的所有可能的子类abstract class
。因此,如果我们忘记处理表达式中任何新创建的子类,它不会在编译时标记错误when
。在这种情况下,只有抛出异常才能帮助我们。不幸的是,只有在程序运行时崩溃后我们才会知道新创建的状态。
受限制的类层次结构:sealed class
sealed
在 a 上使用修饰符class
有两个作用:
abstract class
. 从 Kotlin 1.5 开始,您也可以使用 a sealed interface
。sealed
。sealed class DeliveryStatus
object Preparing : DeliveryStatus()
class Dispatched(val trackingId: String) : DeliveryStatus()
class Delivered(val receiversName: String) : DeliveryStatus()
Run Code Online (Sandbox Code Playgroud)
我们的displayStatus()
函数现在看起来更干净:
fun displayStatus(state: DeliveryStatus) = when (state) {
is Preparing -> print("Preparing for Dispatch")
is Dispatched -> print("Dispatched. Tracking ID: ${state.trackingId}")
is Delivered -> print("Delivered. Received by ${state.receiversName}")
}
Run Code Online (Sandbox Code Playgroud)
密封课程具有以下优点:
限制性多态性
sealed class
从某种意义上说,通过将 a 的对象传递给函数,您也密封了该函数。例如,现在我们的displayStatus()
函数被密封到对象的有限形式state
,也就是说,它将要么采用Preparing
,Dispatched
要么采用Delivered
。早些时候它能够接受 的任何子类DeliveryStatus
。修饰符sealed
对多态性施加了限制。因此,我们不需要从函数中抛出异常displayStatus()
。
不需要else
分行
由于多态性受到限制,我们不需要担心其他可能的子类,DeliveryStatus
并且当我们的函数接收到意外类型时抛出异常。因此,我们不需要表达式else
中的分支when
。
详尽的when
表达
就像 an 的所有可能值enum class
都包含在同一个类中一样,a 的所有可能子类型sealed class
也包含在同一个包和同一个编译单元中。因此,编译器知道该类的所有可能的子类sealed
。这有助于编译器确保我们已经覆盖(穷尽)表达式中所有可能的子类型when
。当我们添加一个新的子类并忘记在表达式中覆盖它时when
,它会在编译时标记一个错误。
请注意,在最新的 Kotlin 版本中,表达式和语句when
都非常详尽。when
when
为什么在同一个文件中?
自 Kotlin 1.5 起,相同的文件限制已被删除。现在您可以在不同的文件中定义 的子类sealed class
,但文件需要位于同一包和同一编译单元中。在 1.5 之前,a 的所有子类需要位于同一个文件中的原因sealed class
是它必须与其所有子类一起编译才能拥有一组封闭的类型。如果在其他文件中允许子类,那么像 Gradle 这样的构建工具就必须跟踪文件的关系,这会影响增量编译的性能。
IDE功能:Add remaining branches
当您只需键入when (status) { }
并按Alt+ Enter,时Enter,IDE 会自动为您生成所有可能的分支,如下所示:
when (state) {
is Preparing -> TODO()
is Dispatched -> TODO()
is Delivered -> TODO()
}
Run Code Online (Sandbox Code Playgroud)
在我们的小示例中,只有三个分支,但在实际项目中,您可能有数百个分支。因此,您可以节省手动查找在不同文件中定义的子类并将它们when
一一写入另一个文件中的表达式的精力。只需使用此 IDE 功能即可。只有sealed
修饰符才能实现此目的。
就是这样!希望这可以帮助您理解密封类的本质。
如果你曾经使用过enum
一个abstract method
只是为了你可以做这样的事情:
public enum ResultTypes implements ResultServiceHolder {
RESULT_TYPE_ONE {
@Override
public ResultOneService getService() {
return serviceInitializer.getResultOneService();
}
},
RESULT_TYPE_TWO {
@Override
public ResultTwoService getService() {
return serviceInitializer.getResultTwoService();
}
},
RESULT_TYPE_THREE {
@Override
public ResultThreeService getService() {
return serviceInitializer.getResultThreeService();
}
};
Run Code Online (Sandbox Code Playgroud)
实际上你想要的是这个:
val service = when(resultType) {
RESULT_TYPE_ONE -> resultOneService,
RESULT_TYPE_TWO -> resultTwoService,
RESULT_TYPE_THREE -> resultThreeService
}
Run Code Online (Sandbox Code Playgroud)
并且你只使它成为一个enum抽象方法来接收编译时保证你总是处理这个赋值,以防添加新的枚举类型; 那么你会喜欢密封的类,因为在那个when
语句的赋值中使用的密封类会收到"什么时候应该是详尽无遗的"编译错误,它会强制你处理所有情况而不是偶然只处理其中的一些情况.
所以现在你不能最终得到这样的东西:
switch(...) {
case ...:
...
default:
throw new IllegalArgumentException("Unknown type: " + enum.name());
}
Run Code Online (Sandbox Code Playgroud)
此外,枚举不能扩展类,只能扩展接口; 密封类可以从基类继承字段.您还可以创建他们的多个实例(和技术上可以使用object
,如果你需要的密封类的子类是单身).
归档时间: |
|
查看次数: |
3651 次 |
最近记录: |