类的构造器
-
构造器
-
主构造器
//age 全局可用,name 只是普通的构造器参数 class Person(var age: Int, name: String) { }
-
init 块
//age 全局可用,name 只是普通的构造器参数 class Person(var age: Int, name: String) { val n: String init { n = name } }
init 块会在构造方法的最前面执行,可用于初始化
-
属性初始化
可在 init 块中进行初始化
-
重写 get / set
class Person() { var name: String = "" get() { return "hello $field" } set(value) { field = "hello $value" } }
-
-
副构造器
-
不定义主构造器
open class A { constructor(name: String) { println("A的 一个参数构造器") } constructor(name: String, age: Int) { println("A的 两个参数构造器") } init { println("A的 init") } } class B : A { constructor(name: String) : this(name, 20) { println("B的 一个参数构造器") } constructor(name: String, age: Int) : super(name, age) { println("B的 两个参数的构造器") } } fun main() { var lv = B("张三") }
A的 init A的 两个参数构造器 B的 两个参数的构造器 B的 一个参数构造器
-
主构造器默认参
open class A(age: Int, name: String = "张三") { } fun main() { var a = A(20) }
-
-
工厂函数
open class A(age: Int, name: String = "张三") { fun A(): A { return A(20) } fun A(age: Int, name: String): A { return A(age, name) } }
函数的名字可以和类名相同
可见性对比
可见性对比 | java | Kotlin |
---|---|---|
public | 公开 | 与 Java 相同,默认 |
internal | X | 模块内可见 |
default | 包内可见,默认 | X |
protected | 包内及子类可见 | 类内及子类可见 |
private | 类内可见 | 类或文件可见 |
模块的概念:直观的讲,大致可以认为是一个 Jar包,一个 aar
-
IntelliJ IDEA 模块
-
Maven 模块
-
Gradle SourceSet
-
Ant 任务中一次调用 的文件
如果定义了 internal 的函数,但是不想让 java 访问,可以使用 @JvmName("") 注解来实现,在 java 中调用的时候注解测参数将会成为方法的名字,且报错,不可以调用。
构造器的可见性
- 给构造器加注解的时候 constructor 就必须写出来
- 如果使用单例,或者工厂等,就需要将构造器弄成私有的
顶级声明的可见性
- 指文件内直接定义的属性,函数,类等
- 顶级声明不支持 protected
- 顶级声明被 private 修饰后表示文件内部可见
类属性的延时初始化
-
类属性必须在构造时初始化
-
某些成员只有在类构造后才会被初始化,例如android 中在 onCrate 中进行 findViewById
解决办法
-
直接判空,或者类型强转
-
使用 lateinit 关键字
lateinit 会让编译器忽略变量的初始化,不支持 Int 等基本类型
开发者必须在完全确定变量值生命周期的情况下使用 lateinit
不要在复杂的逻辑中使用 lateinit
-
使用 lazy
只有在首次使用的时候被访问执行
代理Delegate
接口代理:对象 X 代替当前类 A 实现接口 B 的方法
属性代理:对象 x 代理属性 a 实现 getter/setter 方法
//接口代理
interface Api {
fun a()
fun b()
fun c()
}
class ApiImpl : Api {
override fun a() {}
override fun b() {}
override fun c() {}
}
class ApiWrapper(val api: Api)
//对象 api 代替类 ApiWrapper 实现接口 API
: Api by api {
override fun c() {
println("-------- c")
api.c()
}
}
属性代理:
属性代理是借助于代理设计模式,把这个模式应用于一个属性时,他可以将访问器的逻辑代理给一个辅助对象
可以简单理解为属性的 setter,getter 访问器内部实现是交给一个代理对象来实现,相当于一个代理对象替换了原来字段的读写过程,而暴露在外部属性的操作还是不变的,照样是属性赋值和读取,只是 setter,getter内部实现变了
1,Lazy
当变量第一次使用是进行初始化,可以实现懒加载
//属性代理
class Person(val name: String) {
//lazy返回的对象代理了属性 firstName 的 getter
val firstName by lazy {
println("初始化-----")
"345"
}
}
fun main() {
val p = Person("")
println(p.firstName)
println(p.firstName)
}
初始化-----
345
345
只有在第一次调用的时候才会执行。感觉比较适合安卓
初始化默认是线程安全的,通过 synchronized 锁来保证
不过还是会影响性能,如果 lazy 的初始化不会涉及到多线程,那么可以传入 LazyThreadSafetyMode.NONE 来取消同步锁
LazyThreadSafetyMode有三种模式:SYNCHRONIZED(默认模式)、PUBLICATION、NONE
其中PUBLICATION模式使用了AtomicReferenceFieldUpdater(原子操作)允许多个线程同时调用初始化流程。
2,自定义属性代理
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any, property: KProperty<*>): String {
return "代理 get"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("代理 set")
}
}
fun main() {
val e = Example()
println(e.p)
e.p = "345"
}
代理 get
代理 set
3,observable
变量被赋值时会发出通知
Delegates.observable 可以传入两个参数,一个是初始值,另一个是变量被赋值时的 handle 方法
class User {
var name: String by Delegates.observable("345") { property, oldValue, newValue ->
println("$oldValue -> $newValue")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
输出
345 -> first
first -> second
只要 user.name 被赋值,监听就会触发
类似的还有 vetoable ,只不过 vetoable 是在赋值前触发,observable 是在赋值后触发
4,vetoable
使用 vetoable 进行拦截
class User {
var name: String by Delegates.vetoable("345") { property, oldValue, newValue ->
if (newValue == "first") {
return@vetoable true //返回 true 表示first 可以赋值给 name
}
return@vetoable false //返回false 表示拦截其他赋值操作
}
}
fun main() {
val user = User()
user.name = "first"
println(user.name)
user.name = "second"
println(user.name)
}
first
first
5,对 map 的代理
fun main() {
val map = mutableMapOf("name" to "张三", "age" to 1)
var name: String by map
println(name)
name = "李四"
println(map["name"])
}
张三
李四
6,局部变量代理
fun main() {
a {
345
}
}
fun a(sum: () -> Int) {
val s by lazy(sum)
println(s) //345
}
案例
:使用属性代理读写 Properties
class PropertiesDelegate(private val path: String, private val defaultValue: String = "") {
private lateinit var url: URL
private val properties: Properties by lazy {
val prop = Properties()
url = try {
// 查找具有给定名称的资源
javaClass.getResourceAsStream(path).use {
//从输入流中读取属性列表(键值对)
prop.load(it)
}
javaClass.getResource(path)
} catch (e: Exception) {
try {
ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
prop.load(it)
}
ClassLoader.getSystemClassLoader().getResource(path)!!
} catch (e: Exception) {
FileInputStream(path).use {
prop.load(it)
}
URL("file://${File(path).canonicalPath}")
}
}
prop
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
//获取属性信息
return properties.getProperty(property.name, defaultValue)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
//设置属性信息
properties.setProperty(property.name, value)
//写入到文件,第二个参数为注释信息
File(url.toURI()).outputStream().use {
properties.store(it, "Hello!!l")
}
}
}
abstract class AbsProperties(path: String) {
protected val prop = PropertiesDelegate(path)
}
class Config : AbsProperties("Config.properties") {
var author by prop
var version by prop
var desc by prop
}
fun main() {
val config = Config()
print(config.author) //getValue
config.author = "345" //setValue
println(config.author)
}
单例
//单例的定义,默认是恶汉模式
object Signleton {
var x = 2
//模拟 java 中静态方法,只有在 object 中可以使用
@JvmStatic
fun y() {
println("---------")
}
}
// Signleton.INSTANCE.getX(); 从 java 中调用该类
// Signleton.y(); JvmStatic :静态的
class Foo {
//伴生对象,与普通类同名的 object
//相当于java中的 public static void y(){}
companion object {
@JvmStatic
fun y() {
println("y")
}
//生成静态 Field
@JvmField
var y: Int = 2
}
//属性可以使用 JvmField,生成非静态 Field,
@JvmField
var x: Int = 2
}
fun main() {
Signleton.x
Signleton.y()
println(Foo.y)
println(Foo.y())
}
内部类
fun main() {
//创建非静态内部类对象
val inner = Outer().Inner()
//创建静态内部类对象
val staticInner = Outer.StaticInner()
//匿名内部类,可以实现多个接口,并且继承一个类
object : Runnable, Cloneable {
override fun run() {
}
}
}
class Outer {
//非静态内部类
inner class Inner {
}
//静态内部类
class StaticInner {
}
//一旦定义出来,就会被初始化
object HH {
}
}
数据类
data class Book(val id: Long, val name: String, val author: String) {
}
JavaBean | data class | |
---|---|---|
构造方法 | 默认无参构造 | 属性作为参数 |
字段 | 字段私有,get/set 公开 | 属性 |
继承性 | 可继承,可被继承 | 不可被继承 |
component | 无 | 相等性,解构等 |
data class 为我们做了什么
编译器会根据我们在构造函数中声明的属性自动导出下列成员:
-
equals / hashCode
-
toString
-
componentN()
-
copy()
如果在类中明确定义或继承了上面的基础方法,则不会自动生成
限制:
- 构造函数至少要一个参数
- 所有构造函数的参数都必须使用 val 或者 var 标记
- data class 不可以是 abstract,open sealed or inner
- 不可以实现接口
数据类自带的结构方法:componentN()
fun main() {
val book = Book(1L, "百科", "张三")
val (id, name, author) = book
println("$id $name $author")
}
实际上,结构是调用了 Book 类的 component1() 和 component2() ,component3()。
如何作为 Java Bean 使用呢
需要解决两个限制:
至少一个构造器,和 final。data class 是不可被继承的。
如果解决呢:NoArg 插件,AllPoen 插件
在 build.gradle 中添加插件
plugins {
......
id("org.jetbrains.kotlin.plugin.noarg").version("1.3.50")
id("org.jetbrains.kotlin.plugin.allopen").version("1.3.50")
}
noArg {
//执行 init 块中的代码
invokeInitializers = true
annotation("com.kotlin.study.five.PoKo")
}
allOpen {
annotation("com.kotlin.study.five.PoKo")
}
注意build.gradle 使用的是 kt 的代码
还需要定义一个注解:就是 annotation指定路径下的 PoKo 注解
public @interface PoKo {
}
空的即可
使用
//使用该注解后会在字节码中生成一个无参构造,
//可被继承
@PoKo
data class Book(val id: Long, val name: String, val author: String) {
init {
println("------")
}
}
fun main() { val b = Book::class.java.newInstance()}
枚举
fun main() {
S1.Busy.run()
S1.Idle.run()
val state = State.Busy
//条件分支
val value = when (state) {
State.Idle -> {
0
}
State.Busy -> {
1
}
}
//枚举的比较,因为是有顺序的,所以可以。需要重载运算符
//枚举的区间,因为有顺序
}
//枚举,父类是 Enum,所以不能继承其他类
enum class State {
Idle,
Busy
}
//带参数的构造
enum class S(val id: Int) {
Idle(0),
Busy(1)
}
//实现接口的枚举
enum class S1 : Runnable {
Idle {
//实现字节的 run 方法
override fun run() {
println("For Idle")
}
},
Busy;
override fun run() {
println("For every state.")
}
}
//为枚举定义扩展
fun State.next(): State {
return State.values().let {
val next = (ordinal + 1) % it.size
it[next]
}
}
- 枚举
- 枚举的基本知识:定义方法,属性,构造器,接口实现
- 为枚举定义扩展
- 分支表达式
- 比较与区间
密封类
- 密封类是一种特殊的抽象类,不能被实例化,但是可以有抽象成员
- 密封类的子类定义在与自身相同的文件中
- 密封类的子类个数是有限的
fun main() {
//只能通过子类实例化对象
val play: PlayerState = Start("小鸟")
}
//首先是一个抽象类,其次是一个密封类,构造器是私有的
sealed class PlayerState {
}
class Start(val song: String) : PlayerState() {
}
class Stop : PlayerState() {
}
- 密封类
- 概念:抽象,子类可数,子类封闭
- 构造器私有:无法在外部文件中继续密封类
- 子类定义:必须定义在当前文件中
- 与枚举对比:enum:实例可数,sealed:子类可数
内联类
- 内联类是对某一个类型的包装
- 内联类是类似于 Java 装箱类型的一种类型
- 编译器会尽可能使用被包装的类型进行优化
fun main() {
var x = BoxInt(3)
val newValue = x.value * 200
println(newValue) //600
x++
println(x) //BoxInt(value=4)
}
//内联类
//对 Int 做了包装,Int 必须定义在主构造器,且必须是 val 类型 和 public
//可实现接口。且不能继续和被继承
inline class BoxInt(val value: Int) {
//内联类属性:不应改包含状态。也就是只能定义方法或者函数
//重写自增运算符
operator fun inc(): BoxInt {
return BoxInt(value + 1)
}
}
内联类目前还在公测。
限制
-
主构造器必须有且只有一个只读属性。因为要对其进行包装
-
不能定义有 backing-field 的其他属性。不能有状态
-
被包装的类型不是是泛型
-
不能继续且被继承
-
内联类不能被定义为其他类的内部类
-
定义的方法在编译的时候会被替换为静态方法
数据类的 Json 化、
依赖
kotlin("kapt") version ("1.3.50")
implementation("com.squareup.moshi:moshi:1.8.0")
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
fun main() {
//Gson
val gson = Gson()
println(gson.toJson(Person("Gson", 20)))
println(gson.fromJson("""{"name":"Gson"}""", Person::class.java))
println("----------------------")
//MoShi
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(Person::class.java)
println(jsonAdapter.toJson(Person("MoShi", 20)))
println(jsonAdapter.fromJson("""{"name":"Gson"}"""))
}
@JsonClass(generateAdapter = true)
data class Person(val name: String, val age: Int = 10)
{"name":"Gson","age":20}
Person(name=Gson, age=0)
----------------------
{"name":"MoShi","age":20}
Person(name=Gson, age=10)
Bean 类中 age 的默认值是 10
在 gson 解析的时候没有指定 age 则为 0
而 moshi 解析的时候会按照构造方法中的默认值来进行解析。注意 moshi 用到了注解处理器 @JsonClass
递归整形案例
fun main() {
val l1 = inListOf(0, 1, 3, 4)
println(l1)
println(l1.joinToString('-'))
println(l1.sun())
val (x, y, z) = l1
println(x)
println(y)
println(z)
}
fun inListOf(vararg ints: Int): IntList {
return when (ints.size) {
0 -> IntList.Nil
else -> {
//递归调用,
//slice:切割,从第一个到最后一个(注意不是第 0 个)
//递归时,ints[0]永远是上一次的下一个
IntList.Cons(ints[0], inListOf(*(ints.slice(1 until ints.size).toIntArray())))
}
}
}
sealed class IntList {
object Nil : IntList() {
override fun toString(): String {
return "Nil"
}
}
class Cons(val head: Int, val tail: IntList) : IntList() {
override fun toString(): String {
return "$head,$tail"
}
}
fun joinToString(sep: Char = ','): String {
return when (this) {
Nil -> "Nil"
is Cons -> {
"${head}$sep${tail.joinToString(sep)}"
}
}
}
operator fun component1(): Int? {
return when (this) {
Nil -> null
is Cons -> head
}
}
operator fun component2(): Int? {
return when (this) {
Nil -> null
//下一个 Cons
is Cons -> tail
.component1()
}
}
operator fun component3(): Int? {
return when (this) {
Nil -> null
is Cons -> tail
.component2()
}
}
}
fun IntList.sun(): Int {
return when (this) {
IntList.Nil -> 0
is IntList.Cons -> {
head + tail.sun()
}
}
}
- 类型进阶
- 类的构造器
- 主构造器:唯一的路径
- 副构造器:提供其他的构造路径
- init 快:可写多个,最后会合成一个执行
- 可见性
- public
- internal:模块内可见,类似一个 jar,aar
- protected:没有包内可见,只有子类看见
- private
- 类属性的延时初始化
- 可空类型
- lateinit
- lazy
- 代理 Delegate:
- 接口代理
- 属性代理:lazy,observable,vetoable 等
- 使用属性代理读写 Properties
- 单例 object
- 伴生对象
- 定义 java field 的 @JvmField
- 定义静态成员的 @javaStatic
- 内部类
- 默认静态
- 非静态用 inner 修饰
- 匿名内部类可以实现多个接口
- 本地类和本地函数
- 数据类
- component:主构造器中定义的属性
- 编译器生成的方法:equals,hashCode,copy,componentN
- 解构:定义了 componentN方法,即可通过解构获取到对应的值
- 插件:noArg(增加默认构造方法),allOpen(去掉 final)
- 其他类
- 枚举类
- 密封类:子类只能定义在父类相同的文件中
- 内联类:类似于装箱拆箱
- 数据类的 json 序列化
- Gson,Moshi,Kotlinx.serialization
- 特性:默认参数,init 块
- 递归的整形列表的简单实现
- 递归用的非常好
- 可进行解构
- 类的构造器
参考自慕课网 Kotlin 从入门到精通