六、Kotlin 类型进阶
1. 类的构造器 & init
代码块
1.1 主构造器 & 副构造器在使用时的注意事项 & 注解 @JvmOverloads
-
推荐在类定义时为类提供一个主构造器;
-
在为类提供了主构造器的情况下,当再定义其他的副构造器时,要求副构造器必须调用到主构造器,否则报语法错误;
-
在继承时,如果父类中定义了主构造器或者副构造器,那么子类在继承父类时要在子类的构造器后面指明所调用的父类构器;
-
可以在定义构造器时为形参提供默认参数值;
-
如果要在
Java
中调用Kotlin
类的带默认参数值的构造器,需要使用注解@JvmOverloads
修饰Kotlin
类的带默认参数值构造器。此时就相当于Java
把带默认参数值的构造器分解成多个重载的构造器。
1.2 init
代码块
-
init{...}
代码块相当于Java
类中的非静态代码块{...}
。 -
二者都会在实例化对象的过程中执行,区别是
init{...}
代码块中可以访问到主构造器的形参。 -
先执行
init{...}
代码块,再执行副构造器的函数体。 -
Kotlin
类的成员属性必须进行初始化。可以在定义时就初始化;也可以放到init{...}
代码块中初始化。 -
init{...}
代码块和Java
类中的非静态代码块{...}
一样,都可以定义多个。
1.3 示例 1:init
代码块 & 主/副构造器的使用
1.4 示例 2:在声明继承关系的同时指定所调用的父类构造器
1.5 示例 3:带默认参数的构造器 & 注解 @JvmOverloads
的使用场景
2. 使用与类同名的工厂函数创建类对象
在 Kotlin
中可以定义一个全局的与某个类同名的函数作为 工厂函数 来实例化这个类。从外观上看,就好像是重载了类的构造器一样。
这样做的好处是:对于无法被继承的类,可以通过定义一个这样的同名的全局函数来实例化这个类,从而能够加上一些我们自己的业务代码。
示例:
3. 访问权限修饰符
3.1 Java
与 Kotlin
的访问权限修饰符对比
Java | Kotlin | |
---|---|---|
public | 公开 | 公开,是 Kotlin 中的默认权限 |
internal | 无 | 模块内可见 |
default | 包内可见,是 Java 中的默认权限 | 无 |
protected | 包内及子类可见 | 子类可见 |
private | 仅类内可见 | 文件内及类内可见 |
3.2 Kotlin
中的访问权限修饰符的作用对象
顶级声明 | 类 | 成员 | |
---|---|---|---|
public | √ | √ | √ |
internal | √ | √ | √ |
protected | × | × | √ |
private | √ | √ | √ |
3.3 Kotlin
中模块的概念
IntelliJ IDEA
工程中的一个 Module
可以看成是一个 Kotlin
模块。
也就是说:一个 jar
包;一个 aar
库,都可以看成是一个 Kotlin
模块。
3.4 Kotlin
中修饰符 internal
& Java
中修饰符 default
3.4.1 Java
中修饰符 default
的使用场景 & 缺陷
在封装 SDK
或对外提供公共组件时,对于 SDK
或公共组件中的核心类,若我们不想让使用者访问到,通常会使用 default
修饰,即包内可见。
但是,如果使用者一定要访问这些核心类的话,也可以创建一个与核心类同包名的包路径,将预访问这些核心类的外部类定义在同包名的包路径下即可。
也就是说,Java
中的 default
权限修饰符是存在缺陷的:
-
我们在封装
SDK
或封装公共组件时,无法通过default
修饰符将核心类给彻底的对外隐藏起来。 -
并且,对于
default
修饰的多个核心类,如果它们之间要相互访问,那么也必须放到同一个包路径下,即包名要相同。这就意味着即使这些核心类可分为不同的功能模块、不同的抽象层次,我们也无法按照功能或抽象层次不同将它们分别放到不同的包路径下。从而导致了同一个包路径下聚集了大量的功能不同、抽象层次不同的类文件,使工程结构变得臃肿、不明确、难以维护。
3.4.2 Kotlin
中使用修饰符 internal
解决 Java
中修饰符 default
的缺陷
在 Kotlin
中使用 internal
修饰符可以解决 Java
中的 default
修饰符的缺陷:
-
被
internal
修饰的核心类或成员只是在模块内可见。这意味着我们在一个模块中封装的SDK
或公共组件,在提供给其他模块使用时,在其他模块中是无法访问到SDK
或公共组件中被internal
修饰的核心类或成员的。 -
并且,在模块中被
internal
修饰符的核心类并没有限制都要放在同一个包路径中才能相互访问(只要它们在同一个模块中就可以相互访问)。所以,对于可分为不同功能模块、不同抽象层次的各个核心类,可以放在同一个模块下的不同包路径中。保证了工程结构清晰明确。
3.4.3 修饰符 internal
在 Java
中无效 & 注解 @JvmName
的作用
Kotlin
中的 internal
修饰符对 Java
是无效的。也就是说,在其他模块的 Java
代码中是可以访问到 SDK
或公共组件模块中被 internal
修饰的核心类或成员的。
需要指出的是:在其他模块的 Java
中访问 Kotlin
中被 internal
修饰的成员方法时,默认会为该成员方法的方法名添加额外的后缀 “$moduleName_buildType
”。
如果不想使用这种添加了后缀的默认方法名,我们可以使用注解 @JvmName
声明在 Java
中调用时的方法名称。
特别地:如果我们不想让其他模块中的 Java
代码能够访问到被 internal
修饰的 Kotlin
中的成员方法,那么可以使用注解 @JvmName
为这个成员方法声明一个 不合法的方法名称(如声明一个非字母或下划线开头的方法名称),于是 Java
中在调用这个不合法的方法名时就会报语法错误。
示例:
3.5 为构造器添加访问权限修饰符
当为 Kotlin
类的主构造器添加访问权限修饰符时,不能省略主构造器中的关键字 constructor
。
当为 Kotlin
类的副构造器添加访问权限修饰时,直接在构造器定义的最前面加修饰符即可。
3.6 为 Kotlin
类的属性添加访问权限修饰符
可以在主构造器中定义成员属性时直接为该属性声明访问权限修饰符。
对于属性的 setter
/getter
方法:
getter
方法的访问权限修饰符必须同属性的访问权限修饰符一致;setter
方法的访问权限修饰符的权限不得超过属性的访问权限修饰符。
3.7 什么是顶级声明 & 为顶级声明添加访问权限修饰符
顶级声明是指:文件中定义的全局变量、全局函数、类。
顶级声明不能被 protected
修饰。
顶级声明被 private
修饰时,表示仅当前文件内可见。
4. 属性的延迟初始化
4.1 Kotlin 中属性必须赋初值 & 特殊情况下的延迟初始化
一般情况下,Kotlin
要求类属性必须赋以初始化值,具体分为两种情形:
- 在定义属性时赋以初始化值;
- 在
init
代码块中赋以初始化值。
但是在一些特殊情况下,是无法为类属性赋以初始化值的,如:Activity
类中的视图属性只有在 onCreate
方法调用 setContentView
方法加载了布局文件后才能进行初始化。此时,就需要对类属性进行 延迟初始化。
4.1 类属性延迟初始化的三种方式
4.1.1 定义为可空类型并赋初值 null
(类似 Java
)
将属性定义为可空类型,并赋以初始值为 null
(类似于 Java
):
private var tvName: TextView? = nulloverride fun onCreate(...) {...tvName = findViewById(R.id.tv_name)tvName?.text = "Hello World"
}
注意:由于该方式创建的属性为可空类型,所以调用该属性对象的成员时都需要写成 “?.
”
4.1.2 使用关键字 lateinit
在定义属性时添加关键字 lateinit
,指明该属性可以延迟初始化:
private lateinit var tvName: TextViewoverride fun onCreate(...) {...tvName = findViewById(R.id.tv_name)tvName.text = "Hello World"
}
注意:
-
关键字
lateinit
其实就是告诉编译器 忽略对属性的初始化检查; -
被
lateinit
修饰的属性如果后面没有进行初始化就使用的话,会报UninitializedPropertyAccessException
异常; -
如果我们无法明确属性的初始化时机(即:在使用属性时无法确定该属性是否已经初始化了),那么不建议使用
lateinit
,这会降低我们代码程序的稳定性。 -
如果一定要使用关键字
lateinit
对不确定初始化时机的属性进行延迟初始化,那么在使用该属性时建议使用Lateinit.kt
中定义的全局变量KProperty0<*>.isInitialized
来判断该属性是否初始化了,如:class Activity {lateinit var tvName: TextViewfun onCreate() {if (::tvName.isInitialized) { // tvName.isInitialized 是全局属性,所以需要加 "::"println(tvName.text)}} }
-
关键字
lateinit
不能修饰Int
等基本类型的属性。
4.1.3 使用 Lazy<T>
代理(推荐)
在定义属性时,使用 Lazy<T>
类型的代理(Delegate
)对属性进行延迟初始化:
private val tvName = lazy {findViewById<TextView>(R.id.tv_name)
}override fun onCreate(...) {...tvName.text = "Hello World" // 在第一次访问 tvName 之前,会执行 lazy 函数中传入的 Lambda 表达式
}
示例:
5. 代理
5.1 什么是代理?
“我” 代替 “你” 处理 “它”,于是可将 “我” 称为 “你的代理”。即:
- “我” 作为 代理者;
- “你” 作为 被代理者;
- “它” 作为 代理业务。
5.2 代理的分类:接口代理 & 属性代理
代理的分类:
-
接口代理:
若对象 x 代替类 A 实现接口 B 的方法,则: 1. 对象 x 作为代理者; 2. 类 A 作为被代理者; 3. “实现接口 B 的方法” 作为代理业务。
-
属性代理:
若对象 x 代替属性 a 实现 setter/getter 方法,则: 1. 对象 x 作为代理者; 2. 属性 a 作为被代理者; 3. “实现 setter/getter 方法” 作为代理业务。
5.3 接口代理
如下代码所示,类 ApiDelegate
代理类 ApiImpl
实现接口 Api
中的方法。
需注意:
-
代理类
ApiDelegate
和被代理类ApiImpl
都要实现相同的接口Api
。 -
被代理类
ApiImpl
需要依赖代理类ApiDelegate
的实例对象。此时,可以将代理类ApiDelegate
的实例对象作为被代理类ApiImpl
的构造器实参传给被代理类ApiImpl
。 -
被代理类
ApiImpl
可以在继承接口Api
时,通过 “by + 代理类 ApiDelegate 实例对象
” 的语法,使得被代理类ApiImpl
中的接口方法默认都由代理类ApiDelegate
完全实现。当然,也可以在被代理类
ApiImpl
中重写被代理的接口方法,加上自己的业务逻辑。
5.4 属性代理
5.4.1 lazy
(只能代理 val
只读属性)
5.4.1.1 lazy
的源码分析
lazy
是一个全局函数,返回一个代理接口 Lazy<T>
的实现类 SynchronizedLazyImpl
的实例对象。
/* LazyJVM.kt */
package kotlinpublic actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
代理接口 Lazy<T>
的实现类 SynchronizedLazyImpl
中重写了接口中的只读属性 value
的 getter
方法。在重写的 value
属性的 getter
方法中,value
的属性值就是全局函数 lazy
中作为实参的 Lambda
表达式的返回值。
/* LazyJVM.kt */
package kotlinprivate class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {private var initializer: (() -> T)? = initializer@Volatile private var _value: Any? = UNINITIALIZED_VALUEprivate val lock = lock ?: thisoverride val value: Tget() {val _v1 = _valueif (_v1 !== UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")return _v1 as T}return synchronized(lock) {val _v2 = _valueif (_v2 !== UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T)} else {val typedValue = initializer!!() // 将 Lambda 表达式的返回值作为 getter 方法的返回值_value = typedValueinitializer = nulltypedValue}}}override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE...
}
代理接口 Lazy<T>
的扩展方法 getValue
是一个运算符重载函数,对应运算符 “by
”:
/*参数 property 表示 被代理的属性参数 thisRef 表示被代理的属性所属的实例对象。
*/
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
运算符 “by
” 的作用效果就是在调用对象 thisRef
中的属性 property
的 setter
/getter方法
时,就相当于是在调用运算符重载函数 setValue
/getValue
方法。
5.4.1.2 lazy
的使用语法 & 示例
属性代理 lazy
的使用语法为:
val property by lazy {...}
即:通过运算符 “by
”,使得在访问只读属性 property
(即调用 getter
方法)时,就相当于在调用函数 lazy{...}
返回的 Lazy<T>
接口的实例对象的扩展方法 getValue
。
而
getValue
方法中返回Lazy<T>
接口的实例对象中的只读属性value
(即调用只读属性value
的getter
方法)。于是,访问只读属性
property
(即调用property
的getter
方法)就相当于是在访问Lazy<T>
接口的实例对象中的只读属性value
(即调用只读属性value
的getter
方法)。
注意:
-
所谓的属性代理
lazy
,其实就是代理接口Lazy<T>
的实例对象代理了属性property
实现了getter
方法; -
在使用属性代理
lazy
时,由于代理接口Lazy<T>
只是代理了getter
方法,所以属性property
应该声明为 只读属性val
; -
属性
property
的类型就是代理接口Lazy<T>
的泛型T
,也就是全局函数lazy
的实参Lambda
表达式的返回值类型; -
从代理接口
Lazy<T>
的实现类SynchronizedLazyImpl
重写的getter
方法中可知,全局函数lazy
的实参Lambda
表达式只会在第一次执行getter
方法时调用一次。
示例:
5.4.2 observable
5.4.2.1 observable
的源码分析
observable
是单例类 Delegates
的成员方法,返回一个代理接口 ReadWriteProperty<Any?, T>
的实现类 ObservableProperty<T>
的实例对象:
/* Delegates.kt */
package kotlin.propertiespublic object Delegates {.../*参数 initialValue 表示属性的初始值参数 onChange 可以传入一个 Lambda 表达式,在 afterChange 方法中执行,用于处理 setValue 之后的业务逻辑返回值类型为 ReadWriteProperty<Any?, T> 接口类型*/public inline fun <T> observable(initialValue: T, // crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):ReadWriteProperty<Any?, T> = // 函数的简写形式object : ObservableProperty<T>(initialValue) { // 返回一个继承自 ObservableProperty 的匿名内部类对象override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)}...
}
代理接口 ReadWriteProperty<Any?, T>
的实现类 ObservableProperty<T>
重写了接口中的运算符重载函数 setValue
/getValue
(都对应运算符 “by
”):
/* Interfaces.kt */
package kotlin.propertiespublic interface ReadWriteProperty<in R, T> {public operator fun getValue(thisRef: R, property: KProperty<*>): Tpublic operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
/* ObservableProperty */
package kotlin.propertiespublic abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {private var value = initialValueprotected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = trueprotected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}public override fun getValue(thisRef: Any?, property: KProperty<*>): T {return value}public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {val oldValue = this.valueif (!beforeChange(property, oldValue, value)) {return}this.value = valueafterChange(property, oldValue, value)}
}
运算符 “by
” 的作用效果就是在调用对象 thisRef
中的属性 property
的 setter
/getter
方法时,就相当于是在调用运算符重载函数 setValue
/getValue
方法。
在重写的 setValue
方法中,会在赋值(this.value = value
)前后依次回调 beforeChange
和 afterChange
方法,其中:
-
回调函数
beforeChange
用于根据返回值判断是否进行赋值。默认返回true
,表示允许赋值。 -
回调函数
afterChange
用于赋值完成后的业务处理。
代理接口
ReadWriteProperty<Any?, T>
的实现类ObservableProperty<T>
被设计为抽象类的目的就是:建议调用者重写beforeChange
/afterChange
方法,从而能够在赋值前后添加自己的业务代码。
5.4.2.2 observable
的使用语法 & 示例
属性代理 observable
的使用语法为:
// Lambda 表达式中的参数 property 就是被代理的属性 property
var property by Delegates.observable(initValue) { property, oldValue, newValue -> /*在给属性 property 赋值时(调用 setter 方法),会调用到 observable 方法返回的 ObservableProperty<T> 实例的 afterChange 方法,在 afterChange 方法中就会执行 Lambda 表达式的函数体。*/}
即:通过运算符 “by
”,使得在访问属性 property
(即调用 setter
/getter
方法)时,就相当于在调用函数 Delegates.observable(initValue, onChange)
返回的代理接口实现类 ObservableProperty<T>
实例的 setValue
/getValue
方法。
注意:
-
所谓的属性代理
observable
,其实就是代理接口ReadWriteProperty<Any?, T>
的实例对象代理了属性property
实现了setter
/getter
方法; -
在使用属性代理
observable
时,由于代理接口ReadWriteProperty<Any?, T>
代理了setter
/getter
方法,所以属性property
可以声明为 可读可写属性var
; -
属性
property
的类型就是函数Delegates.observable(initValue, change)
中作为属性初始值的参数initValue
的类型; -
从代理接口
ReadWriteProperty<Any?, T>
的实现类ObservableProperty<T>
重写的setValue
方法中可知:作为函数Delegates.observable(initValue, change)
中参数change
的Lambda
表达式会在每次setValue
赋值完后调用。
示例:
5.4.3 vetoable
5.4.3.1 vetoable
的源码分析
vetoable
是单例类 Delegates
的成员方法。返回一个代理接口 ReadWriteProperty<Any?, T>
的实现类 ObservableProperty<T>
的实例对象。
/* Delegates.kt */
package kotlin.propertiespublic object Delegates {...public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):ReadWriteProperty<Any?, T> = // 函数的简写形式object : ObservableProperty<T>(initialValue) { // 返回一个继承自 ObservableProperty 的匿名内部类对象override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)}...
}
从函数 vetoable
的源码中可知,属性代理 vetoable
和属性代理 observable
的唯一区别就在于:
-
作为函数
observable(initialValue, onChange)
中参数onChange
的Lambda
表达式相当于实现了afterChange
方法; -
作为函数
vetoable(initialValue, onChange)
中参数onChange
的Lambda
表达式相当于实现了beforeChange
方法。
5.4.4 notNull
5.4.4.1 notNull
的源码分析
notNull
是单例类 Delegates
的成员方法,从相关源码中可以看出:
-
使用属性代理
notNull
时,不需要传入初始值; -
在调用属性
property
的getter
方法之前,必须先调用setter
方法赋以非空值,否则会报IllegalStateException
异常。
/* Delegates.kt */
package kotlin.propertiespublic object Delegates {...public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()...
}private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {private var value: T? = nullpublic override fun getValue(thisRef: Any?, property: KProperty<*>): T {return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")}public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {this.value = value}
}
5.4.4.2 notNull
的使用示例
5.4.5 案例:使用属性代理读写 Properties
5.4.5.1 相关知识点
5.4.5.1.1 属性代理中运算符 “by
” 的使用语法
var/val property by delegateObj
注意:
- 在定义属性时使用;
by
的左操作数为被代理的属性;by
的右操作数为提供了运算符重载函数setValue
/getValue
的代理对象。
5.4.5.1.2 运算符重载函数 setValue
/getValue
的函数原型
运算符重载函数 setValue
/getValue
的函数原型为:
/*其中:1. 参数 thisRef 表示被代理的属性所在的对象2. 参数 property 表示被代理的属性3. 参数 value 表示赋给被代理属性 property 的值
*/
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)operator fun getValue(thisRef: Any?, property: KProperty<*>): T
5.4.5.1.3 属性代理中运算符 “by
” & 运算符重载函数 setValue
/getValue
只要是提供了运算符重载函数 setValue
/getValue
的类都可以通过运算符 “by
” 作为代理类,实现对属性 property
的 setter
/getter
方法的代理。
5.4.5.2 代码实现
6. 单例类
6.1 使用关键字 object
定义单例类
定义语法:
//使用 关键字object 定义一个单例类
object KotlinSingleton {...
}
注意:KotlinSingleton
既是单例类的类名,又是单例类的对象名。因此可以直接通过 KotlinSingleton
作为对象访问其中的成员。
6.2 Java
代码中访问 Kotlin
单例类:KolinSingleton.INSTANCE
Java
代码中通过 KolinSingleton.INSTANCE
访问 Kotlin
中通过关键字 object
定义的单例类 KolinSingleton
的对象。
从 Java
中的访问方式可以看出,Kotlin
中的单例类 KotlinSingleton
编译成字节码文件后,相当于多了一个名为 INSTANCE
的静态成员变量,即:
public static final KolinSingleton INSTANCE = new KolinSingleton();
示例:
6.3 注意事项
6.3.1 Kotlin
单例类相当于 Java
中的饿汉式单例类
使用关键字 object
定义的单例类,相当于 Java
中的饿汉式单例类
6.3.2 Kotlin
单例类不允许自定义主副构造器,但可以定义 init
代码块
使用关键字 object
定义的单例类中,不允许另外添加主构造器或副构造器。但是可以使用 init{...}
代码块。
6.3.3 Kotlin
单例类可以继承父类、实现接口
使用关键字 object
定义的单例类,可以像普通类那样继承父类,实现接口。
7. 伴生对象
7.1 伴生对象的定义 & 使用
在关键字 class 定义的普通类中,可以使用 companion object {…} 声明一个与该普通类 “相伴” 的伴生对象,即:
class Foo {// 定义普通类的成员属性和成员方法var x: Int = 1fun func() { println("normal func") }/* 声明一个 Foo 类的伴生对象,伴生对象名为 Foo.Companion */companion object { // // 定义伴生对象的成员属性和成员方法var x: Int = 2fun func() { println("companion func") }}
}
访问 Foo
类的伴生对象中的 func()
方法:
fun main() {val a = Foo.x // a = 2Foo.func() // 打印 companion func
}
7.2 Kotlin
中访问伴生对象 & Java
中访问伴生对象
Kotlin
中可以通过 “普通类类名.伴生对象成员
” 的方式访问伴生对象中的成员,等价于 “普通类类名.Companion.伴生对象成员
”。
Java
中只能通过 “普通类类名.Companion.伴生对象成员
” 的方式访问。
7.3 单例类中不存在伴生对象
关键字 object
定义的单例类是不存在伴生对象的。
8. 注解 @JvmStatic
8.1 Kotlin
中不存在 static
修饰的静态成员 & 注解 @JvmStatic
的作用
static
修饰的静态成员是 Java
特有的。
Kotlin
作为一门跨平台语言(JavaScript
,Java
,C/C++
),是不存在 static
修饰的静态成员的。
为了兼容 Java
平台,在 Kotlin
中使用注解 @JvmStatic
来标记一个 Kotlin
的类成员,使其在 JVM
平台中会额外生成对应的静态成员。
8.2 @JvmStatic
的使用场景
8.2.1 使用场景 1:修饰单例类的成员,额外生成相应的静态成员
修饰关键字 object
定义的单例类中的成员属性或成员方法,使其在 JVM
平台中额外生成对应的静态成员属性或静态成员方法。
8.2.2 使用场景 2:修饰伴生对象的成员,额外生成相应的静态成员
修饰关键字 class
定义的普通类的伴生对象中的成员属性或成员方法,使其在 JVM
平台中额外生成对应的静态成员属性或静态成员方法。
8.3 注解 @JvmStatic
只在 Java
代码中起作用
注解 @JvmStatic
只是在 Java
代码中起作用,使得:
-
对于普通类
FooNormal
的伴生对象中被注解@JvmStatic
修饰的成员,在Java
中可以直接通过类名FooNormal
访问,即被注解@JvmStatic
修饰的伴生对象中的成员在Java
中相当于普通类的静态成员; -
对于单例类
KotlinSingleton
中被注解@JvmStatic
修饰的成员,在Java
中可以直接通过类名KotlinSingleton
访问,即被注解@JvmStatic
修饰的单例类中的成员,在Java
中相当于单例类的静态成员。
9. 注解 @JvmField
9.1 @JvmField
的作用:使属性在 Java
中公有化并移除 setter
/getter
Kotlin
类中的属性 property
在 Java
中访问时,相当于:
一个 Kotlin 类的属性 property 对应一个 Java 类的私有成员变量 property + setter/getter 方法
当使用注解 @JvmField
修饰 Kotlin
属性 property
时,相当于:
一个被 @JvmField 修饰的 Kotlin 类的属性 property 对应一个 Java 类的公有成员变量 property
即注解 @JvmField
的作用就是:
-
使属性在
Java
中公有化; -
在
Java
中移除对应的setter
/getter
方法。
9.2 @JvmField
的使用场景
9.2.1 使用场景 1:修饰单例类的属性,在 Java
中作为公有化的静态属性
注解 @JvmField
修饰单例类的成员属性时,在 Java
中该属性作为静态成员变量。
9.2.2 使用场景 2:修饰伴生对象的属性,在 Java
中作为公有化的静态属性
注解 @JvmField
修饰伴生对象的成员属性时,在 Java
中该属性作为静态成员变量。且只能通过类或类对象访问,不能通过伴生对象访问。
9.2.3 使用场景 3:修饰普通类的属性,在 Java
中作为公有化的非静态属性
注解 @JvmField
修饰普通类的成员属性时,在 Java
中该属性作为 非静态成员变量。
10. 内部类
10.1 Kotlin
中默认定义静态内部类 & 关键字 inner
声明非静态内部类
Kotlin
中定义的内部类默认是静态内部类。
若要定义非静态内部类,需要使用关键性 inner
进行声明。
和 Java
一样:
-
Kotlin
中的非静态内部类也持有外部类的引用; -
Kotlin
中的静态内部类实例需要通过外部类的类名来创建; -
Kotlin
中的非静态内部类实例需要通过外部类的实例来创建。
10.2 单例类中不能定义非静态内部类
关键字 object
定义的单例类中不存在非静态的情况,不能定义非静态内部类。
10.3 示例
10.4 匿名内部类可以实现多个接口
Kotlin
中在定义匿名内部类时,可以实现多个接口(Java
中是不支持的)。
10.5 本地类 & 本地函数
Java
和 Kotlin
都支持定义本地类(即在函数中定义类)。
Kotlin
中还支持定义本地函数(即在函数中定义函数,即函数嵌套定义)。但 Java
不支持。
11. 数据类(data class
)
11.1 数据类的使用说明 & 示例
说明:
-
使用关键字 “
data class
” 声明一个数据类; -
数据类必须定义主构造器,且至少有一个属性定义在主构造器中;
-
在主构造器中定义的属性作为数据类的组件(
Component
)。一个属性就是一个数据类组件; -
数据类会根据组件(
Component
)生成toString
/hashCode
/equals
/copy
这四个方法; -
数据类不能作为父类(不可继承),所以不能用关键字
open
修饰数据类; -
可以像二元对组
Pair
或三元对组Triple
那样,通过解构的方式获取数据类中作为组件的属性:val (component1, component2, ...) = dataObj
其中: 1. dataObj 表示数据类对象; 2. 变量列表 component1, component2, ... 对应数据类中定义在主构造器中的属性列表。
注意:
- 一般情况下,定义一个数据类只需要提供主构造器,并在主构造器中定义作为组件的属性即可(即不需要
{...}
); - 在数据类中的主构造器中定义的作为组件的属性的类型建议是基本类型、
String
、或其他数据类类型; - 作为组件的属性建议声明为
val
; - 作为组件的属性不可以为其自定义
setter
/getter
方法。
示例:
11.2 查看 Kotlin
代码对象的 Java
代码
在 IntelliJ IDEA
中,对于编辑器中当前显示的 Kotlin
文件/类,可以通过如下步骤显示出该 Kotlin
代码对应的由 JVM
编译生成的字节码:
Tools -> Kotlin -> Show Kotlin Bytecode
再点击字节码代码上方的 “Decompile
” 按钮,可以将该字节码反编译生成对应的 Java
代码。
示例:
11.3 NoArg
插件(为数据类提供无参构造)
插件 NoArg
为数据类提供无参构造。
11.4 AllOpen
插件(使数据类可继承)
插件 AllOpen
移除数据类的 final
特性,使数据类可继承。
12. 枚举类(enum class
)
12.1 使用 enum class
定义枚举类
enum class KotlinState1 {IDLE,ACTIVE
}
12.2 为枚举类自定义构造器
可以为枚举类自定义构造器:
enum class KotlinState2(val id: Int) {IDLE(0),ACTIVE(1)
}
12.3 为枚举类实现接口
可以为枚举类实现接口:
interface IState {fun work()
}enum class KotlinState3(val id: Int): IState {/*可以分别为每个枚举对象重写方法。*/IDLE(0) {override fun work() {println("---> idle work")}},ACTIVE(1);/*也可以为没有重写方法的枚举对象提供一个公共的重写方法。*/override fun work() {println("---> common work")}
}
12.4 为枚举类定义扩展方法
可以为枚举类定义扩展方法:
fun KotlinState3.next(): KotlinState3 {return KotlinState3.values().let {it[(this.ordinal + 1) % it.size] // 返回下一个枚举对象}
}
12.5 枚举类不能继承类,只能实现接口
枚举类的父类默认都是 Enum
,所以枚举类不能再继承其他父类,只能实现接口。
12.6 示例
12.6 枚举类的使用
12.6.1 在 when
条件语句中使用枚举
可以在 when
条件语句中使用枚举:
12.6.2 同类型的枚举值之间可以比较
12.6.3 同类型的枚举值可以构造区间
13. 密封类(sealed class
)
使用关键字 sealed class
定义一个密封类。
13.1 密封类是抽象类 & 密封类的构造器是私有的
通过反编译生成的 Java
代码可知:
-
密封类是一个抽象类;
-
密封类的构造器默认是私有的且只能是私有的。所以 密封类的子类只能与密封类定义在同一个
Kotlin
文件中。
13.2 密封类的使用:密封类 + 多态 + 智能转换
可以结合 “密封类 + 多态 + 智能转换” 来使用密封类:
14. 内联类(inline class
)
说明:
-
使用关键字
inline class
定义一个内联类; -
内联类必须定义一个主构造器,且主构造器中必须定义且只能定义一个
val
修饰的只读属性; -
内联类的作用就相当于一个包装类,对定义在主构造器中的属性的类型进行包装;
-
内联类中不允许定义带 “
backing field
” 的属性(即属性的setter
/getter
方法中不能使用 “field
”); -
内联类可以实现接口,但不能作为父类,也不能继承其他类;
-
内联类在
Kotlin
的1.3
版本中处于公测阶段,不建议使用。
相关文章:
六、Kotlin 类型进阶
1. 类的构造器 & init 代码块 1.1 主构造器 & 副构造器在使用时的注意事项 & 注解 JvmOverloads 推荐在类定义时为类提供一个主构造器; 在为类提供了主构造器的情况下,当再定义其他的副构造器时,要求副构造器必须调用到主构造器…...
Chrome 浏览器插件 runtime 字段解析
运行时 runtime 使用 chrome.runtime API 检索 Service Worker,返回有关 manifest.json 的详细信息监听和响应应用或扩展程序生命周期中的事件还可以使用此 API 将网址的相对路径转换为完整的一个 URL 一、权限 Runtime API 上的大多数方法都不需要任何权限 但是…...
七分钟交友匿名聊天室源码
多人在线聊天交友工具,无需注册即可畅所欲言!你也可以放心讲述自己的故事,说出自己的秘密,因为谁也不知道对方是谁。 运行说明: 安装依赖项:npm install 启动:node app.js 运行:直接…...
Aleo项目详细介绍-一个兼顾隐私和可编程性的隐私公链
Aleo上线在即,整理一篇项目的详细介绍,喜欢的收藏。有计划做aleo节点的可交流。 一、项目简介 Aleo 最初是在 2016 年构思的,旨在研究可编程零知识。公司由 Howard Wu、Michael Beller、Collin Chin 和 Raymond Chu 于 2019 年正式成立。 …...
qt学习:实战 http请求获取qq的吉凶
目录 利用的api是 聚合数据 的qq号码测吉凶 编程步骤 配置ui界面 添加头文件,定义网络管理者和http响应槽函数 在界面的构造函数里创建管理者对象,关联http响应槽函数 实现按钮点击事件 实现槽函数 效果 利用的api是 聚合数据 的qq号码测吉凶 先…...
【NodeJS JS】动态加载字体的各方式及注意事项;
首先加载字体这个需求基本只存在于非系统字体,系统已有字体不需要加载即可直接使用; 方案1:创建 style 标签,写入 font-face{font-family: xxx;src: url(xxx)} 等相关字体样式;将style标签添加到body里;方…...
每次请求sessionid变化【SpringBoot+Vue】
引言:花了一晚上的时间,终于把问题解决了,一开始后端做完后,用apifox所有接口测试都是可以的,但当前端跑起来后发现接收不到后端的数据。 当我写完前后端,主页面和获取当前页面信息接口后,配置了cros注解 CrossOrigin…...
勤学苦练“prompts“,如沐春风“CodeArts Snap“
前言 CodeArts Snap 上手一段时间了,对编程很有帮助。但是,感觉代码编写的不尽人意。 我因此也感到困惑,想要一份完整的 CodeArts Snap 手册看看。 就在我感觉仿佛"独自彷徨在这条悠长、悠长又寂寥的雨巷"时,我听了大…...
springboot(ssm线上医院挂号系统 在线挂号预约系统Java系统
springboot(ssm线上医院挂号系统 在线挂号预约系统Java系统 开发语言:Java 框架:springboot(可改ssm) vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.7&a…...
万界星空科技可视化数据大屏的作用
随着科技的不断发展和进步,当前各种数据化的设备也是如同雨后春笋般冒了出来,并且其可以说是给我们带来了极大的便利的。在这其中,数据大屏就是非常具有代表性的一个例子。 数据大屏的主要作用包括: 数据分析:数据大屏…...
5月22日比特币披萨日,今天你吃披萨了吗?
比特币披萨日 1. Laszlo Hanyecz2. 最贵披萨诞生记3. 梭哈买披萨4. 未完待续 2010年5月22日,美国佛罗里达州的程序员Laszlo Hanyecz(拉兹洛哈涅克斯)用10000个比特币购买了棒约翰(Papa Johns)比萨店一个价值25美元的奶…...
内网穿透、远程桌面、VPN的理解
最近在研究内网穿透的相关技术,然后回想起一些相关的技术,比如说要远程桌面公司的电脑,VPN连入内网等。然后想着在此处记录一下,各个的区别,这个纯粹是从技术层面的理解,此处不详细解释怎么去实现或者用什么…...
如何发布自己的npm包,详细流程
发布自己的npm包需要遵循以下具体流程: 创建npm账号:打开浏览器,访问npm官网,注册一个npm账号。 创建项目文件夹并进入:在本地创建一个项目文件夹,并使用终端进入该文件夹。 初始化包信息管理文件&#x…...
【书生·浦语大模型实战】“PDF阅读小助手”学习笔记
1 参考资料 《新版本Lmdeploy量化手册与评测》 2 项目资料 项目主页:【tcexeexe / pdf阅读小助手】 3 模型运行测试 在InternStudio平台中选择A100 (1/4)的配置,镜像选择Cuda11.7-conda,可以选择已有的开发机langchain; 3.1…...
用ChatGPT写申请文书写进常春藤联盟?
一年前,ChatGPT 的发布引发了教育工作者的恐慌。现在,各大学正值大学申请季,担心学生会利用人工智能工具伪造入学论文。但是,聊天机器人创作的论文足以骗过大学招生顾问吗? ChatGPT简介 ChatGPT,全称聊天生…...
uni-app导航栏自定义“返回按钮”多种方法设置原生返回
方法一、 导航栏返回按钮事件 onBackPress监听页面返回,返回 event = {from:backbutton、 navigateBack} ,backbutton 表示来源是左上角返回按钮或 android 返回键;navigateBack表示来源是 uni.navigateBack;详见app、H5、支付宝小程序onBackPress() { this.back1(); …...
【kubernets】kubelet证书单独更新
前言说明 接上一篇文章https://blog.csdn.net/margu_168/article/details/132584109关于kubernets中的证书管理。本篇文章将单独说明一下kubelet的证书更新。在1.19.16版本中,默认情况下使用 kubeadm alpha certs renew all 不能更新kubelet的证书,其他…...
【STM32】STM32学习笔记-硬件SPI读写W25Q64(40)
00. 目录 文章目录 00. 目录01. SPI简介02. W25Q64简介03. SPI相关API3.1 SPI_Init3.2 SPI_Cmd3.3 SPI_I2S_SendData3.4 SPI_I2S_ReceiveData3.5 SPI_I2S_GetFlagStatus3.6 SPI_I2S_ClearFlag3.7 SPI_InitTypeDef 04. 硬件SPI读写W25Q64接线图05. 硬件SPI读写W25Q64示例06. 程序…...
防御保护---安全策略
文章目录 目录 一.安全策略概述 概述: 安全策略的作用: 安全策略与传统防火墙的区别 二.案例分析 练习 一.安全策略概述 概述: 防火墙安全策略的作用在于加强网络系统的安全性,保护网络免受恶意攻击、非法访问和数据泄露的威胁。…...
RustDesk私有化部署,自建远程桌面搭建教程
以linux操作系统为例: 解压安装 # 使用wget进行下载1.1.8-2版本(最新版本可以看上述发布地址) wget https://github.com/rustdesk/rustdesk-server/releases/download/1.1.8-2/rustdesk-server-linux-amd64.zip # 使用unzip解压 unzip rust…...
Flutter环境搭建【win10虚拟机】+夜神模拟器【主机】
Flutter环境搭建 0 Android Studio 与 VS Code 资源消耗对比1 系统配置要求2 Flutter SDK2.1 获取 Flutter SDK2.2 解压2.3 更新 path 环境变量Dart SDK 要兼容 Flutter SDK双击 flutter_console.bat 输入 flutter doctor 检测环境 3 VS code 与插件3.1 安装 VS code3.2 安装 f…...
【数据结构和算法】种花问题
其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一:贪心 2.2 贪心算法一般思路 三、代码 3.1 方法一…...
Vite+Electron快速构建一个VUE3桌面应用(一)
一. 简介 首先,介绍下vite和Electron。 Vite是一种新型前端构建工具,能够显著提升前端开发体验。Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入Chromium和Node.js到二进制的 Electron 允许您保持一个 JavaScript 代码代码…...
第二百八十九回
文章目录 1. 概念介绍2. 方法与细节2.1 实现方法2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何混合选择多个图片和视频文件"相关的内容,本章回中将介绍如何通过相机获取视频文件.闲话休提,让我们一起Talk Flutter吧。 1. …...
Likeshop多商户商城源码系统,支持二开
在电商行业高速发展的当下,拥有一套功能强大、易于操作的开源商城系统至关重要。Likeshop多商户商城系统正是这样一款集H5、小程序、独立APP于一体的开源电商解决方案,助力商家实现智能营销。 一、产品简介 Likeshop多商户商城系统为商家提供了丰富的营…...
Excel:将截面数据转换成面板数据
原始截面数据如下: 步骤:数据——自表格/区域 点击确定,出现下图: 然后,在这个界面选择:“转换”——“逆透视列”下选择逆透视其他列。会出现面板数据形式。 然后,点击“主页”——关闭并上载即…...
209.长度最小的子数组(力扣LeetCode)
文章目录 209.长度最小的子数组题目描述暴力滑动窗口 209.长度最小的子数组 题目描述 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, …, numsr-1, numsr] ,并返回其长度…...
Docker容器部署OpenCV,打造高效可移植的计算机视觉开发环境
推荐 海鲸AI-ChatGPT4.0国内站点:https://www.atalk-ai.com 前言 在计算机视觉领域,快速部署和测试算法是研究和开发的关键。OpenCV作为一个强大的开源计算机视觉库,广泛应用于各种图像处理和视频分析任务。然而,配置OpenCV环境可…...
【Linux】Linux系统编程——pwd命令
文章目录 1.命令概述2.命令格式3.常用选项4.相关描述5.参考示例 1.命令概述 pwd(Print Working Directory)命令用于显示用户当前工作目录的完整路径。这是一个常用的命令,帮助用户确定他们目前所在的目录位置。 2.命令格式 基本的 pwd 命令…...
暴力破解
暴力破解工具使用汇总 1.查看密码加密方式 在线网站:https://cmd5.com/ http://www.158566.com/ https://encode.chahuo.com/kali:hash-identifier2.hydra 用于各种服务的账号密码爆破:FTP/Mysql/SSH/RDP...常用参数 -l name 指定破解登录…...
做百度网站要注意什么/seo网站推广助理
原文地址:http://www.codeproject.com/aspnet/ajax_scribble.asp源代码:http://www.codeproject.com/aspnet/ajax_scribble/ajax_scribble_src.zipAtlas指南: 建立一个AJAX 涂鸦程序(二) Global.asax我们从Global.asax开始我们的编码过程。 1…...
网页设计学到了什么/seo查询系统源码
五百多页,我干到三百多页了。 每个知识点都有说明,操作,解释。 学SPRING MVC,有它就够了。 遗憾的是,这个PDF的文档格式太稀松啦,且,无中文版~~~ 我都想作汉化翻译工作了。。。算了,…...
石家庄专业信息门户网站定制/网站宣传的方法有哪些
人的一生,从推开生命之门那一刻开始,于是,你走进生命的殿堂,像一个好奇的旅客,每一件新奇的事物都想去探究,每一个美好的东西都想拥为己有。殿堂太大了,而旅程始终是有时间限定的,不…...
辛集市住房和城乡建设厅网站/seo云优化是什么意思
netstat -tunlp|grep 8889转载于:https://www.cnblogs.com/412013cl/p/11395415.html...
中国建设银行网站下载/对网站外部的搜索引擎优化
前言 转载自:https://blog.csdn.net/wangzibai/article/details/115283755 场景 在学习谷粒商城的过程中,我这边使用的springboot 版本和spring cloud版本比较高,所以一些配置不一样。 我这边springboot版本是2.5.0,spring cloud版…...
有了网站怎样做公众号/做网站排名服务热线
2019独角兽企业重金招聘Python工程师标准>>> 相信大家对代码质量规范已经不陌生,一般大公司都会进行代码质量检查,用来管理N多项目的质量,如果达不到要求,那么不好意思,请去搞搞代码,从今往后就…...