For group training, suitable for students with basic knowledge.
Kotlin Chinese website kotlin
1 Air safety
/ /? Operator,? : Elvis operator
vallength = b? .length ? : -1
// Secure type conversion
val code = res.code as? Int
// StringsKt
valcode = res.code? .toIntOrNull()// CollectionsKt
val list1: List<Int? > = listOf(1.2.3.null)
val list2 = listOf(1.2.3)
val a = list2.getOrNull(5)
// Note that null does not equal false
if(a? .hasB ==false) {}
Copy the code
2 Inline function
A function marked with the inline operator, the code inside the function is compiled to the point of call.
// kotlin
val list = listOf("a"."b"."c".null)
list.getOrElse(4) { "d"}? .let { println(it) }// Decompile, the getOrElse method is inlined to the call
List list = CollectionsKt.listOf(new String[]{"a"."b"."c", (String)null});
byte var3 = 4;
Object var10000;
if (var3 <= CollectionsKt.getLastIndex(list)) {
var10000 = list.get(var3);
} else {
var10000 = "d";
}
String var9 = (String)var10000;
if(var9 ! =null) {
String var2 = var9;
System.out.print(var2);
}
Copy the code
Noline: disables inline, used to mark parameters, which do not participate in inline.
// kotlin
inline fun sync(lock: Lock, block1: () -> Unit.noinline block2: () -> Unit) {}
Decompile, block1 is inlined to the call, but block2 generates the function object and generates the call
Function0 block2$iv = (Function0)null.INSTANCE; . block2.invoke()Copy the code
@ kotlin. Internal. InlineOnly: kotlin internal annotations, the annotations for inline function only, used to prevent a Java class called (principle is to compile the function is marked as private, inline for Java classes without meaning). If the extension function’s method argument contains higher-order functions, inline is required.
Nonlocal returns: Naked returns are prohibited inside lambda expressions because lambda expressions cannot make the containing function return. But if the function passed by a lambda expression is inline, then the return is also inline, so it is allowed. Such a return is called a nonlocal return. However, you can disable non-local returns by marking the expression arguments of an inline function with the Crossinline modifier.
public inline fun <T> observable(initialValue: T.crossinline onChange: (property: KProperty< * >,oldValue: T.newValue: T) - >Unit): ReadWriteProperty<Any? , T> =object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
Copy the code
3 The generic
(1) Basic usage
class A<T> {}fun <T> T.toString(a): String {
}
// Constrain the upper bound
class Collection<T : Number, R : CharSequence> : 可迭代<T> {}fun <T : Iterable> T.toString(a){}// Multiple constraints
fun <T> T.eat(a) where T : Animal, T : Fly {
}
Copy the code
In order to be compatible with Java versions prior to 1.5, the bytecode compiled with or without generics is the same. Generics are implemented by means of compiler type checking and casting, so Java generics are pseudo-generics. Although generics are erased at runtime, there are ways to get them.
(javaClass.genericSuperclass as? ParameterizedType) ? .actualTypeArguments ? .getOrNull(0)
?: Any::class.java
Copy the code
Both Fastjson’s TypeReference and Gson’s TypeToken are used to obtain generics in this way.
// fastjson
HttpResult<PrivacyInfo> httpResult = JSON.parseObject(
json,
new TypeReference<HttpResult<PrivacyInfo>>() {
}
);
// gson
Type type = new TypeToken<ArrayList<JsonObject>>() {
}.getType();
ArrayList<JsonObject> srcJsonArray = new Gson().fromJson(sourceJson, type);
Copy the code
In Kotlin, the Reified keyword enables generics to be retrieved at runtime. The reified keyword must be used in conjunction with an inline function.
// fastjson
inline fun <reified T : Any> parseObject(json: String) {
JSON.parseObject(json, T::class.java)
}
// gson
inline fun <reified T : Any> fromJson(json: String) {
Gson().fromJson(json, T::class.java)
}
// Get Serializable from the bundle
inline fun <reified T>Bundle? .getSerializableOrNull(key: String): T? {
return this? .getSerializable(key)as? T
}
// start activity
inline fun <reified T : Context> Context.startActivity(a) {
startActivity(Intent(this, T::class.java))
}
Copy the code
In Java, List is immutable, and the following operations are not allowed.
List<String> strList = new ArrayList<>();
List<Object> objList = strList;
Copy the code
But the List in Kotlin is covariant, so you can do this.
public interface List<out E> : Collection<E> {... }val strList = arrayListOf<String>()
val anyList: List<Any> = strList
Copy the code
Note that anyList is still of type List
. If you add data to anyList, you can’t receive it in String when you fetch it. This is type-unsafe, so covariant is not allowed to write, it is read-only. Kotlin uses out to denote covariation. Parameter types declared with OUT cannot be used as method parameter types, but only as return types, which can be understood as “producers”. In contrast, Kotlin uses in to denote inversion, which can only be written, not read. Parameter types declared in cannot be used as return types, but only for method parameter types, which can be understood as “consumers.” Note that the generic wildcard * in Kotlin is also covariant.
4 Higher-order functions
Higher-order functions: Functions that use functions as arguments or return values. I wrote a test method that covers common uses of higher-order functions.
valblock4 = binding? .title? .test( block1 = { numer -> setText(R.string.app_name) println(numer) }, block2 = { numer, checked ->"$numer : $checked"}, block3 = { toIntOrNull() ? :0} ) block4? .invoke(2)
fun <T: View, R> T.test(
block1: T. (Int) - >Unit,
block2: ((Int.Boolean) - >String)? = null,
block3: String. () - >R
): (Int) - >Unit {
block1(1) block2? .invoke(2.false)
"5".block3()
return { number ->
println(number)
}
}
Copy the code
5 Scope function
// with, used for shared scenarios
with(View.OnClickListener {
it.setBackgroundColor(Color.WHITE)
}) {
tvTitle.setOnClickListener(this)
tvExpireDate.setOnClickListener(this)}// apply, which modifies the properties of the value
return CodeLoginFragment().apply {
arguments = Bundle().apply {
putString(AppConstants.INFO_EYES_EVENT_ID_FROM, eventFrom)
}
}
// also, the value will continue to be used after the value is obtainedtvTitle = view.findViewById<TextView? >(R.id.tvTitle).also { displayTag(it) }// run, for scenarios where internal attributes need to be retrievedtvTitle? .run { text ="test"
visibility = View.VISIBLE
}
// let for use in its own scenariotvTitle? .let { handleTitle(it) }fun <T> setListener(listenr: T. () - >Unit){}Copy the code
6 A collection of
list.reversed().filterNotNull()
.filter {
it % 2! =0
}
.map {
listOf(it, it * 2)
}
.flatMap {
it.asSequence()
}.onEach {
println(it)
}.sortedByDescending {
it
}
.forEach {
println(it)
}
Copy the code
7 Operator overloading
All functions of the overload operator require the operator flag, and the operator modifier can be omitted if the overloaded operator is overridden. Here are a few of the more common ones.
Index access operators:
a[i, j] => a.get(i, j)
a[i] = b => a.set(i, b)
Copy the code
Note that I and j do not have to be numbers. They can also be strings and other types.
public interface List<out E> : Collection<E> {
public operator fun get(index: Int): E
}
public interface MutableList<E> : List<E>, MutableCollection<E> {
public operator fun set(index: Int, element: E): E
}
Copy the code
Invoke operator: Invoke is the name of the invoke operator function, which can be written as a function call expression.
val a = {}
a() => a.invoke()
a(i, j) => a.invoke(i, j)
Copy the code
The variable block: (Int) -> Unit can be called either block.invoke(2) or block(2) because invoke is overloaded:
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
Copy the code
GetValue, setValue, provideDelegate operators: For delegate properties, the variable’s get() method is delegated to the delegate’s getValue operator function, and the corresponding variable’s set() method is delegated to the delegate’s setValue operator function.
class A(var name: String? = null) {
operator fun getValue(thisRef: Any? , property:KProperty< * >): String? = name
operator fun setValue(thisRef: Any? , property:KProperty<*>, name: String?). {
this.name = name
}
}
/ / translation
var b by A() =>
val a = A()
var b:String?
get() = a.getValue(this, ::b)
set(value) = a.setValue(this, ::b, value)
Copy the code
Expression ::b evaluates to a property object of type KProperty.
The difference between these two operator functions is that the format of the arguments is strict. A function in a class can be treated as a delegate only if the format meets certain requirements. ProvideDelegate is mainly used for general handling of delegate objects, such as scenarios where multiple variables use the same delegate object and the variable name needs to be validated.
var b by ALoader()
class A(var name: String? = null) : ReadWriteProperty<Any? , String? > {override fun getValue(thisRef: Any? , property:KProperty< * >): String? {
return name
}
override fun setValue(thisRef: Any? , property:KProperty<*>, value: String?). {
this.name = value
}
}
class ALoader : PropertyDelegateProvider<Any? , A> {
override operator fun provideDelegate(thisRef: Any? , property:KProperty< * >) : A {
property.run {
when{ isConst -> {} isLateinit -> {} isFinal -> {} isSuspend -> {} ! property.name.startsWith("m") -> {}}}return A()
}
}
/ / translation
var b by ALoader() =>
val a = ALoader().provideDelegate(this.this::b)
var b: String?
get() = a.getValue(this, ::b)
set(value) = a.setValue(this, ::b, value)
Copy the code
8 entrust
8.1 Delegation Mode
/ / the singleton
companion object {
@JvmStatic
val instance by lazy { FeedManager() }
}
// Delegate implements multiple inheritance
interface BaseA {
fun printA(a)
}
interface BaseB {
fun printB(a)
}
class BaseAImpl(val x: Int) : BaseA {
override fun printA(a) {
print(x)
}
}
class BaseBImpl() : BaseB {
override fun printB(a) {
print("printB")}}class Derived(a: BaseA, b: BaseB) : BaseA by a, BaseB by b {
override fun printB(a) {
print("world")}}fun main(a) {
val a = BaseAImpl(10)
val b = BaseBImpl()
Derived(a, b).printB()
}
// Output: world
Copy the code
The Derived class inherits BaseAImpl, BaseBImpl, and overrides the printB() method. In real development, there are multiple implementations of an interface, and if you want to reuse the implementation of a class, you can use the delegate form. There is also a scenario where there are multiple implementations of an interface and the implementation of a class needs to be dynamically selected:
interface IWebView {
fun load(a)
}
// SDK internal SystemWebView
class SystemWebView : IWebView {
override fun load(a){... }fun stopLoading(a){... }}// SDK internal X5WebView
class X5WebView : IWebView {
override fun load(a){... }fun stopLoading(a){... }}abstract class IWebViewAdapter(webview: IWebView) : IWebView by webview{
abstract fun stopLoading(a)
}
class SystemWebViewAdapter(private val webview: SystemWebView) : IWebViewAdapter(webview){
override fun stopLoading(a) {
webview.stopLoading()
}
}
class X5WebViewAdapter(private val webview: X5WebView) : IWebViewAdapter(webview){
override fun stopLoading(a) {
webview.stopLoading()
}
}
Copy the code
8.2 Delegate Attributes
Format:
import kotlin.reflect.KProperty
public interface ReadOnlyProperty<in R, out T> {
public operator fun getValue(thisRef: R, property: KProperty< * >): T
}
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty< * >): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
public fun interface PropertyDelegateProvider<in T, out D> {
public operator fun provideDelegate(thisRef: T, property: KProperty< * >): D
}
Copy the code
Custom delegate object, getValue method parameters are exactly the same as above, return value type must be attribute type; The first two parameters of the setValue method must be the same as above, and the third parameter type must be an attribute type. The parameters of the provideDelegate method are exactly the same as those of the show. the return value type must be an attribute type. ReadOnlyProperty, ReadWriteProperty, and PropertyDelegateProvider are all classes in the Kotlin library, making it easier to inherit from them when you need to customize the delegate object.
9 How to write singleton?
Instead of “object” it could be:
// No parameter singleton
class A {
companion object {
@JvmStatic
val instance by lazy { A() }
}
}
// This parameter is not recommended
class Helper private constructor(val context: Context) {
companion object {
@Volatile
private var instance: Helper? = null
@JvmStatic
fun getInstance(context: Context?).: Helper? {
if (instance == null&& context ! =null) {
synchronized(Helper::class.java) {
if (instance == null) {
instance = Helper(context.applicationContext)
}
}
}
return instance
}
}
}
Copy the code
Singletons with parameters are not recommended first, because singletons are globally shared and remain unchanged after initialization. The required parameters should be set before the first use (for example, by lazy{A().apply {… }}), or the singleton can take the global parameters of the application internally, and then use context as a static variable. Android Studio will directly issue a yellow warning that this is a memory leak. Context can be set to a global applicationContext variable to fetch.
Then the above singleton without parameters can be directly replaced by object or directly wrapped without object, and written at the top of the file. You can compare the compiled code:
// kotlin
object A{
fun testA(a){}}// After compiling:
public final class A {
@NotNull
public static final A INSTANCE;
public final void testA() {
}
privateA() { } static { A var0 = new A(); INSTANCE = var0; }}// kotlin
var a = "s"
fun testB(a: String){
print(a)
}
// After compiling:
public final class TKt {
@NotNull
private static String a = "s";
@NotNull
public static final String getA() {
return a;
}
public static final void setA(@NotNull String var0) {
Intrinsics.checkNotNullParameter(var0, "
"
?>);
a = var0;
}
public static final void testB(@NotNull String a) {
Intrinsics.checkNotNullParameter(a, "a");
boolean var1 = false;
System.out.print(a); }}Copy the code
It can be found that the direct file top-level write, will not create objects, are static methods, if there are few methods and evaluation does not need encapsulation (mainly depends on whether the call needs to easily identify which object method) can be directly written in the top of the file.
In the same way, companion objects are not created unnecessarily.
// kotlin
class A {
companion object {
const val TAG = "A"
@JvmStatic
fun newInstance(a) = A()
}
}
/ / the compiled
public final class A {
@NotNull
public static final String TAG = "A";
@NotNull
public static final A.Companion Companion = new A.Companion((DefaultConstructorMarker)null);
@JvmStatic
@NotNull
public static final A newInstance() {
return Companion.newInstance();
}
public static final class Companion {
@JvmStatic
@NotNull
public final A newInstance() {
return new A();
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this(a); }}}Copy the code
As you can see, the companion object creates an object. This is important to know, because if there are no functions in the accompanying object, only constants, is it necessary to create the object? The function is just for newInstance, which is called at the top of the class file.