This article is only for recording learning, so if there are some mistakes or wrong understanding, please correct.
Kotlin’s Study Notes (4)
Description:
- Declarations and conditions of data classes in Kotlin
- Sealed classes/enumerations in Kotlin
- Type judgment in Kotlin
- Generics and data covariance in Kotlin
- Type projection in Kotlin
1. Data classes
In Java we usually create a lot of Bean classes to store data, and in Kotlin we have our own data class, “data.”
data class User(val name: String, val age: Int)
Copy the code
The data class must satisfy several conditions
- The main constructor needs to take at least one argument;
- All arguments to the main constructor need to be marked as val or var;
- Data classes cannot be abstract, open, sealed, or internal;
The data class also automatically generates some code for us:
- The equals ()/hashCode () right;
- ToString () format “User(name=John, age=42)”;
- The componentN() functions correspond to all properties in the order they are declared;
- The copy () function
In the JVM, if the generated class needs to have a constructor that takes no arguments, then all attributes must specify default values.
data class User(val name: String = "".val age: Int = 0)
Copy the code
Let’s use code to make a simple connection
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
var json = User(name = "ymc",age = 1)
val json1 = json.copy(age = 2)
println(json1) // Call User toString () by default
}
Copy the code
In Java, we usually want to assign a value, but all we need to do is change the value of a particular item. Copy in Kotlin, just passing in different data, will automatically change and return all the data that you changed.
2. Sealed type
A sealed class, which can be understood as an enumeration, specifies a finite number of types and cannot have other types, but an enumeration can only have one example per enumeration constant, but a subclass of a sealed class can have multiple examples, so you can think of a sealed class as an extension of an enumeration, based on an enumeration, higher than an enumeration, and better than an enumeration.
To declare a sealed class, add the sealed modifier before the class name. Although a sealed class can have subclasses, all subclasses must be declared in the same file as the sealed class itself. Eg:
/ / sealed class
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
Copy the code
Whereas a subclass of a sealed class must be in a file, a class (indirect successor) of an extended sealed class subclass can be in any location without having to be in the same file.
Points of attention for sealing:
1. A sealed class is itself abstract. It cannot be instantiated directly, but it can have abstract members. 2. Sealed classes are not allowed to have non-private constructors (their constructor defaults to private).
The key benefit of using a sealed class is that when expressions are used, if you can verify that the statement covers all cases, you do not need to add an else clause to the statement.
fun eval(expr: Expr): Double{
return when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
}
Copy the code
2. Type detection and automatic type conversion
We can use the is operator to check whether an expression is an instanceof a type (similar to the instanceof keyword in Java).
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// Obj is automatically converted to String
return obj.length
}
// There is another way to use instanceof in Java. is
// if (obj ! is String){
// // XXX
// }
// Obj is still a reference of type Any
return null
}
Copy the code
Or you can use it in the expression
fun getStringLength(obj: Any): Int? {
// To the right of the && operator, the type of 'obj' is automatically converted to 'String'
if (obj is String && obj.length > 0)
return obj.length
return null
}
Copy the code
3. The generic
Generics in Kotlin are similar to those in Java, but in many ways more concise.
// Generics in Kotlin
class Box<T>(t: T) {
var value = t
}
Copy the code
If we want to create
val box: Box<Int> = Box<Int> (1)
Copy the code
// 1 has type Int, so the compiler knows we're talking about Box
.
val box = Box(1)
Copy the code
As we look at Kotlin’s generics features, let’s first review the use of generics in Java
- The upper bound of a wildcard, from which elements can only be read, but not added, is called Producers. Extends T> indicates.
-
indicates.
###3.1 Wildcard upper bound <? Extends T>(T represents the upper bound of a wildcard character). This means that you can accept arguments of T and subclasses of T. That is, you can safely read instances of T
List<String> strs = new ArrayList<String>();
strs.add("0");
strs.add("1");
List<? extends Object> objs = strs;
objs.get(0); // Yes
objs.add(1); // However, there is an error when adding
Copy the code
The above example shows that objs can read values, but when adding values to objs, there is no way to determine whether the added instance type matches the defined type.
###3.2 Lower bound of the wildcard
<? Super T>, where T represents the lower bound of the wildcard. For example: Collection<? Super String> is the parent type of Collection< String>, so you can add and set directly, but when you get, the type is Object instead of String.
List<String> strs = new ArrayList<String>();
strs.add("0");
List<? super String> objs = strs;
objs.add("1");
objs.set(0."2");
// Get the Object type. If you want a String, you need to strong-twist it
Object s = objs.get(0);
Copy the code
Generics in Kotlin
In both Java and Kotlin, generics are implemented using erasure, which means that when you use generics, the task specific type information is erased, and the only thing you know about it is that you’re using another object. For example, Box and Box are thought to be types at runtime, and are both instances of Box. In the use of generics, the specific type of information erasure is not something we do not understand and do not face, in Kotlin also provides us with some solutions for reference:
- Types of covariance
- Type the projection
- Generic constraint
3.3 Type covariant
Suppose we have a generic interface Source< in T, out R >, where T is decorated by the covariant annotation in and R by the covariant annotation out.
internal interface Source<in T, out R> {
// The in function can be used as an argument, consumed, but not as a return value
fun mapT(t: T): Unit
// The out function, which cannot be used as an argument, cannot be consumed, but can be used as a return value
fun nextR(a): R
}
Copy the code
In T: to ensure that member functions of Source can only consume T types, but cannot return T types
Out R: to ensure that member functions of Source can only return type R, and cannot consume type R
It is clear from the above explanation that the covariant in and out annotations actually define the purpose of the type parameter in the class or interface, whether it is used for consumption or return, and qualify it accordingly.
3.4 Type projection
From the code above we learned about the use of generics in and out. Now let’s learn what type projection really is with a piece of code.
fun copy(from: Array<out String>, to: Array<Any>) {
// ...
}
fun fill(dest: Array<in String>, value: String) {
// ...
}
Copy the code
The generic parameter from is decorated with the covariant annotation OUT, meaning that the parameter cannot be consumed in the function and is not allowed to operate on it in the method.
In the fill function, the dest generic parameter is modified with the covariant in annotation, and Array corresponds to the Java Array <? Super String> Same, that is, you can use either a CharSequence array or an Object array as an argument to fill(),
This declaration is called type projection in Kotlin. Type projection is mainly used to restrict parameters by relative factors and avoid unsafe operations on the parameter class.
3.5 Generic functions
Classes can have type parameters. The function can also have. The type argument should precede the function name:
fun <T> singletonList(item: T): List<T> {
/ /...
}
fun <T> T.basicToString(a) : String { // Extend the function
/ /...
}
// The call type is int
val l = singletonList<Int> (1)
Copy the code