In my last article, Sealed and RemoteMediator, I covered how to use Sealed Classes to handle network requests on a Flow basis. This article is a more in-depth analysis of Sealed Classes, combined with the powerful function of functional programming, and requires a lot of practice to master and flexibly use it.
In this article you will learn the following:
- Sealed Classes
- What are the limitations of enumerations and abstract classes?
- Why can enumerations be singletons? What are the advantages of enumerations as singletons?
- When are enumerations and Sealed Classes used?
- What exactly are Sealed Classes?
- Why Sealed Classes are used to represent a restricted class hierarchy?
- Sealed Classes is an extension of an enumeration class.
- A subclass of Sealed Classes can represent instances of different states. How can it be used in a project?
- How did Kotlin do this?
Limitations of enumerations and abstract classes
Before we look at Sealed Classes, let’s look at the limitations of enums and abstract Classes. These limitations are relative to Sealed Classes, but they are advantages in themselves, and Sealed Classes were created to solve these problems. Let’s look at the limitations of enumerations:
- Restrict enumerations to one instance per type
- Restrict all enumeration constants to use the same type of value
Restrict enumerations to one instance per type
enum class Color(val value: Int) {
Red(1)
}
fun main(args: Array<String>) {
val red1 = Color.Red
val red2 = Color.Red
println("${red1 == red2}") // true
}
Copy the code
Final output
red1 == red2 : true
Copy the code
As you can see, we define a single-element enumeration type. No matter how many objects color.red has, they end up with one instance. Each enumeration constant exists only as one instance, whereas a subclass of a sealed class can have multiple instances that contain state, which is both a limitation of enumeration and an advantage of enumeration.
The advantages of enumerating constants as an instance: enumerations not only prevent multiple instantiations, but they also prevent deserialization, and they avoid multithreaded synchronization issues, so they are also listed as one of the ways to implement singletons. Just to summarize.
Whether there is only one instance | Deserialize or not | Thread safe or not | Lazy loading |
---|---|---|---|
is | is | is | no |
Josh Bloch, author of Effective Java, suggests using enumerations as singletons, and while enumerations have not yet been widely adopted, enumerations of single-element types have become the best way to implement singletons.
Let’s take a look at how to implement a singleton with enumeration (the same way Java does). We won’t go into the details here, because this is not the focus of this article.
interface ISingleton {
fun doSomething()
}
enum class Singleton : ISingleton {
INSTANCE {
override fun doSomething() {
// to do
}
};
fun getInstance(): Singleton = Singleton.INSTANCE
}
Copy the code
However, the use of enumerations as singletons in real projects is rare. I look at many open source projects and the use of enumerations as singletons is rare, in large part because of the inconvenience of using enumerations.
I have a suggestion that if you’re creating objects with deserialization, you should use enumerations, because Java says that the serialization and deserialization of enumerations are customized, Therefore, disable the compiler from using writeObject, readObject, readObjectNoData, writeReplace, readResolve, and other methods.
Restrict all enumeration constants to use the same type of value
Restrict all enumeration constants to use the same type of value, that is, each enumeration constant type has the same value. Let’s use the example above to illustrate this.
enum class Color(val value: Int) {
Red(1),
Green(2),
Blue(3);
}
Copy the code
As you can see, we define three constants Red, Green, and Blue in the enumeration Color, but they can only use values of type Int, not other types. What if we used values of other types? As follows:
The compiler will tell you that you can only accept a value of type Int and you can’t change its type, which means that you can’t add extra information to an enumerated type.
Limitations of abstract classes
An abstract class can be inherited by subclasses, but subclasses are not fixed and can be extended at will without the limitation of enumeration constants.
Sealed Classes includes the advantages of abstract Classes and enums: the flexibility of abstract class representation and the limitations of enum constants
If Sealed Classes don’t have the limitations of enums and abstract Classes, what are the real benefits of Sealed Classes in real projects? Before we get into the benefits of Sealed Classes, let’s take a look at what they say about Sealed Classes.
Sealed Classes?
Sealed Classes (Sealed Classes
Let’s briefly summarize the above paragraph:
- Sealed Classes are used to represent a restricted class hierarchy
- In a sense, Sealed Classes is an extension of the enum class
- The difference with enums is that each enumeration constant exists only as a single instance, whereas a subclass of Sealed Classes can represent instances in different states
So what do these three paragraphs mean? Next, we will analyze the three aspects.
Sealed Classes are used to represent a restricted class hierarchy
Sealed Classes is used to describe a class hierarchy that is restricted.
- Sealed Classes are used to represent hierarchies: subclasses can be any class, data class, Kotlin object, generic class, or even another Sealed class
- Sealed Classes are restricted: they must be used within the same file, or inside the Sealed Classes class. Prior to Kotlin 1.1, the rules were stricter and subclasses could only be used inside the Sealed Classes class
Sealed Classes is pretty straightforward to use, so let’s see how to use Sealed Classes.
sealed class Color {
class Red(val value: Int) : Color()
class Green(val value: Int) : Color()
class Blue(val name: String) : Color()
}
fun isInstance(color: Color) {
when (color) {
is Color.Red -> TODO()
is Color.Green -> TODO()
is Color.Blue -> TODO()
}
}
Copy the code
Mac/Win/Linux: Alt + Enter completes all branches of the WHEN statement.
For more shortcuts to AndroidStudio, see the previous two articles
- Shortcuts to AndroidStudio that few people know
- Shortcuts to AndroidStudio that few people know
Sealed Classes is an extension of the enum class
In a sense, Sealed Classes is an extension of enums. In fact, Sealed Classes is a lot like enums, so let’s take a look at an example.
As you can see, when using object declarations within Sealed Classes, we can reuse them without having to create a new instance each time, and when used this way, it looks very similar to enumerations.
Note: this is rarely used, and is not recommended, because enums are better than Sealed Classes in this case
When are enumerations used
Enumerations are more appropriate if you don’t need to instantiate multiple times, don’t need to provide special behavior, or don’t need to add additional information, and only exist as a single instance.
Let’s take a look at how enumerations are used in Paging3 and look at the source code for the androidx.Paging.LoadType class.
enum class LoadType {
REFRESH,
PREPEND,
APPEND
}
Copy the code
Enumerated constants | role |
---|---|
refresh | Use of initialization refresh |
append | Used when loading more |
prepend | Used when adding data to the current list header |
They don’t need to be instantiated multiple times, they don’t need to add any extra information, they just represent a certain state, and they are used in many places such as RemoteMediator, PagingSource, etc. For more information on Paging3 principles and practical examples, see the previous articles.
- Jetpack member Paging3 Database practice and source code Analysis (1)
- Jetpack Member Paging3 Network Practice and Principle Analysis (II)
- Jetpack member Paging3 uses RemoteMediator to load network page data and update it to the database.
- PokemonGo Jetpack + MVVM minimalism
4. The Sealed Classes subclass can represent instances of different states
Unlike enums, each enumeration constant exists only as a single instance. A subclass of Sealed Classes can represent instances of different states.
Following the example we used in our previous article on Google’s recommendation to use sealed and RemoteMediator in a project, let’s look at how to do this with sealed Classes.
sealed class PokemonResult<out T> {
data class Success<out T>(val value: T) : PokemonResult<T>()
data class Failure(val throwable: Throwable?) : PokemonResult<Nothing>()
}
Copy the code
Here only posted part of the code, the core implementation can view the project PokemonGo GitHub address: https://github.com/hi-dhl/PokemonGo code path: PokemonGo/app/… /com/hi/dhl/pokemon/data/remote/PokemonResult.kt
Let’s see how to use it
When (result) {is pokemonresult. Failure -> {// make Failure alert} is pokemonresult. Success -> {// make Success handle}}Copy the code
Let’s see Sealed Classes for another example of how a list may contain different types of data, such as pictures, text, etc.
sealed class ListItem {
class Text(val title: String, val content: String) : ListItem()
class Image(val url: String) : ListItem()
}
Copy the code
These are two common examples, but Sealed Classes is more powerful than that, and there are many more scenarios waiting to be explored.
Sealed Classes in Kotlin (Sealed Classes in Kotlin) In an interesting example shared by Enums with Super-Powers, a series of operations on a View can be wrapped in Sealed Classes to see what happens.
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
class TranslateX(val px: Float): UiOp()
class TranslateY(val px: Float): UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
is UiOp.TranslateX -> view.translationX = op.px
is UiOp.TranslateY -> view.translationY = op.px
}
Copy the code
In Sealed Classes, we define a series of View operations Show, Hide, TranslateX, and TranslateY. Now we create a class that combines these operations on the View.
class Ui(val uiOps: List = emptyList()) {
operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}
Copy the code
I declare a List in the Ui class that stores all of the operations, and I rewrite the plus operator, which you can see in the previous article and a little bit of Kotlin’s technique and how it works, and I use the plus operator to concatenate these operations to the view, This will not only improve the readability of the code, but also make it easier to use. With this defined, let’s take a look at how to use this class.
val ui = Ui() +
UiOp.Show +
UiOp.TranslateX(20f) +
UiOp.TranslateY(40f) +
UiOp.Hide
run(view, ui)
Copy the code
Once you have defined a series of operations and then performed them through the run method, look at the implementation of the run method.
fun run(view: View, ui: Ui) {
ui.uiOps.forEach { execute(view, it) }
}
Copy the code
The code is very simple and won’t be explained here. In Kotlin, functions can be passed as arguments, you can pass the run method to another function or to a class, and these operations are completely interchangeable, making them very powerful when combined.
Sealed Classes is more powerful than that, and there are many, many more scenarios that are very useful. At the moment, MY understanding of Sealed Classes is very limited, and I am not flexible enough to use it. I believe that in more projects and more scenarios, I will see more practical techniques.
Principle of Sealed Classes
(Sealed Classes) (Sealed Classes) (Sealed Classes)
sealed class Color {
object Red : Color()
object Green : Color()
object Blue : Color()
}
Copy the code
Let’s take a look at what the decompiled Java code does. PS: Tools → Kotlin → Show Kotlin Bytecode
. @metadata (mv = {1, 1, 13}, bv = {1, 0, 3}, k = 1, d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0004\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u 0018 \ u0002 \ n \ u0000 \ b6 \ u0018 \ u00002 \ u00020 \ u0001: \ u0003 \ u0003 \ u0004 \ u0005B \ u0007 \ \ b u0002 ¢\ u0006 \ u0002 \ u0010 \ u0002 \ u0082 \ u 0001 \ u0003 \ u0006 \ u0007 ¨ \ \ b u0006 \ t "}, d2 = {" Lcom/hidhl leetcode/test/saledvsemun/Color;" , "", "()V", "Blue", "Green", "Red", "Lcom/hidhl/leetcode/test/saledvsemun/Color$Red;" , "Lcom/hidhl/leetcode/test/saledvsemun/Color$Green;" , "Lcom/hidhl/leetcode/test/saledvsemun/Color$Blue;" . "Java-kotlin"} ) public abstract class Color { private Color() { } public Color(DefaultConstructorMarker $constructor_marker) { this(); }}... // Omit some codeCopy the code
The @metadata annotation appears in any class file generated by the Kotlin compiler and can be reflected to retrieve @metadata information. The parameter names are very short and can help reduce the size of the class file.
@metadata stores Kotlin’s main syntax information such as extension functions, TypeAlias, etc. This information is stored as annotations in Java bytecode by the Kotlinc compiler. If the Metadata is discarded, an exception will be thrown when running on the JVM. So how to determine the corresponding relationship between them, in fact, through the @metadata annotation provided information.
Because metadata can’t be thrown away, R8 brings new optimizations that record metadata information in R8’s internal data structures. When R8 is done optimizing and shrinking a third library or application, it synthesizes new and correct Kotlin metadata for all Kotlin classes, with the goal of reducing the size of the application. I am working on it and will share it in the future.
In this case, @metadata keeps a list of subclasses that the compiler uses when it uses them. As you can see, Sealed Class is compiled to an abstract class. It cannot be instantiated itself, only its subclasses can be used to instantiate the object.
The default constructor of the abstract Color class was privatized, so prior to Kotlin 1.1, subclasses had to be nested inside Sealed Classes, but they were later relaxed to not be used outside the Sealed Classes file. How did Kotlin do this? What if we use it outside of the Sealed Classes file?
As you can see, this causes a compilation error, so why Sealed Classes can be used within the same file? Take a look at the decompiled code.
// Sealed class Color // sealed class Color Red: Color() Public Final Class Red extends Color {public Red() {super((DefaultConstructorMarker)null); public Final Class Red extends Color {public Red() {super((DefaultConstructorMarker)null); }}... Public abstract class Color {private Color() {} $FF: synthetic method public Color(DefaultConstructorMarker $constructor_marker) { this(); }}... // Omit some codeCopy the code
The Red class is compiled into a final class, Sealed class is compiled into an abstract class, and the compiler generates a public constructor that other classes cannot call directly, only the Kotlin compiler can use. The Red class is compiled into a final class, and the public Color class constructor is called in its constructor, all done for us by the Kotlin compiler.
- The constructor is private, restricting subclasses to be nested in Sealed Classes
- The compiler generates a public constructor, calls the parent public constructor in the subclass constructor, and the Kotlin compiler does that for us
conclusion
Limitations of enumerations
- Restrict enumerations to one instance per type
- Restrict all enumeration constants to use the same type of value
Limitations of abstract classes
An abstract class can be inherited by subclasses, but subclasses are not fixed and can be extended at will without the limitation of enumeration constants.
Advantages of enumerations as singletons
Whether there is only one instance | Deserialize or not | Thread safe or not | Lazy loading |
---|---|---|---|
is | is | is | no |
Sealed Classes?
Sealed is an abstract class. It cannot be instantiated by itself. It can only be instantiated by its subclasses. Sealed’s constructor is private and cannot be used outside the files defined by Sealed.
- Sealed Classes are used to represent a restricted class hierarchy
- In a sense, Sealed Classes is an extension of the enum class
- The difference with enums is that each enumeration constant exists only as a single instance, whereas a subclass of Sealed Classes can represent instances in different states.
When can you use an enumeration or Sealed?
- Enumerations are recommended when creating objects that involve deserialization, because Java states that the serialization and deserialization of enumerations are customized and therefore disabled by the compiler
writeObject
、readObject
、readObjectNoData
、writeReplace
、readResolve
Methods. - Enumerations are more appropriate if you don’t need to instantiate multiple times, don’t need to provide special behavior, or don’t need to add additional information, and only exist as a single instance.
- In other cases, Sealed Classes are used, and to a certain extent, they can be used instead of enums
Auto-complete all branches under the WHEN statement
Mac/Win/Linux: Alt + Enter completes all branches of the WHEN statement.
reference
- Sealed classes in Kotlin: enums with super-powers
- Sealed Classes
- Kotlin Vocabulary | seal type sealed class
- Kotlin Metadata
conclusion
The official account has been opened: ByteCode, welcome to check Android 10 series source code, Jetpack, Kotlin, translation, LeetCode/Offer/domestic and foreign large factory algorithm, etc. A series of articles, if you are useful, please help me click a star, Thanks!! Welcome to study together and advance together on the road of technology.
Is building a most complete and latest AndroidX Jetpack related components of the actual combat project and related components of the principle of analysis article, currently has included App Startup, Paging3, Hilt and so on, is gradually adding other Jetpack new members, the warehouse continues to update, Check it out: AndroidX-Jetpack-Practice. If this warehouse is helpful, please give me a like on the top right corner of the warehouse.
algorithm
Since LeetCode has a large question bank, hundreds of questions can be selected for each category. Due to the limited energy of each person, it is impossible to brush all the questions. Therefore, I sorted the questions according to the classic types and the difficulty of the questions.
- Data structures: arrays, stacks, queues, strings, linked lists, trees…
- Algorithms: Search algorithm, search algorithm, bit operation, sorting, mathematics,…
Each problem will be implemented in Java and Kotlin, and each problem has its own solution ideas, time complexity and space complexity. If you like algorithms and LeetCode like me, you can pay attention to my LeetCode problem solution on GitHub: Leetcode-Solutions-with-Java-And-Kotlin, come to learn together And look forward to growing with you.
Android 10 source code series
I’m writing a series of Android 10 source code analysis articles. Knowing the system source code is not only helpful in analyzing problems, but also very helpful in the interview process. If you like to study Android source code as MUCH as I do, You can follow my Android10-source-Analysis on GitHub, and all articles will be synchronized to this repository.
- How is APK generated
- APK installation process
- 0xA03 Android 10 source code analysis: APK loading process of resource loading
- Android 10 source code: APK
- Dialog loading and drawing process and use in Kotlin, DataBinding
- WindowManager View binding and architecture
- 0xA07 Android 10 source code analysis: Window type and 3d view hierarchy analysis
- More……
Android Apps
- Few people know Kotlin’s technique and principle analysis
- AndroidX App Startup practice and principle analysis of Jetpack’s latest member
- Jetpack member Paging3 Practice and Source Code Analysis (PART 1)
- Jetpack New Member Paging3 Network Practice and Principle Analysis (II)
- Jetpack’s new member Hilt practices (1) Starting a pit
- Jetpack new member Hilt’s App Startup practice (2) Advanced chapter
- New member of Jetpack Hilt and Dagger are very different
- All aspects of Hilt and Koin performance were analyzed
- PokemonGo Jetpack + MVVM minimalism
- Google recommends using Kotlin Flow in the MVVM architecture
Select a translation
At present, I am sorting out and translating a series of selected foreign technical articles. Besides translation, many excellent English technical articles provide good ideas and methods. Every article has a part of the translator’s thinking and a deeper interpretation of the original text. You can pay attention to my Technical-Article-Translation on GitHub, and articles will be synchronized to this warehouse.
- [Google engineers] just released a new Fragment feature, “New ways to transfer Data between Fragments” and source code analysis
- How does FragmentFactory elegantly use Koin and partial source code analysis
- [2.4K Start] Drop Dagger to Koin
- [5K +] Kotlin’s performance optimization stuff
- Decrypt RxJava’s exception handling mechanism
- [1.4K+ Star] Picasso
- More……
Tool series
- Shortcuts to AndroidStudio that few people know
- Shortcuts to AndroidStudio that few people know
- All you need to know about ADB commands
- 10 minutes introduction to Shell scripting
- Dynamically debug APP based on Smali file Android Studio
- The Android Device Monitor tool cannot be found in Android Studio 3.2