7章、类型进阶
7.1、类的构造器
主构造器
/**
* 1、constructor可以省略
* 2、var修饰的是属性,类内部可见
* 3、name属性,由于没有var或者val修饰,所以在构造器内可见 + init块内可见
* 4、init块里面可以访问构造器的参数
*/
class Person constructor(var age: Int = 0, name: String = "") {
// init块类似于主构造器的方法体
init {
Log.e("cdx",name)
}
}
init块
class Person constructor(var age: Int = 0, name: String = "") {
private var firstName: String;
// init块类似于主构造器的方法体
init {
Log.e("cdx", name)
firstName = name
}
init {
age += 1
}
}
1、init块可以有多个,最终会被合并执行。
2、构造器会将firstName,多个init一块合并,完成初始化。
主构造器 + 副构造器
// 括号内部的是主构造器
class Person(var age: Int, var name: String) {
// 1、类内部的构造器是副构造器
// 2、副构造器必须要调用主构造器
constructor(age: Int) : this(age, "") {
}
}
不定义主构造器,只有副构造器(不推荐)
class Person {
// 可以省略super()
constructor(age: Int) : super() {
}
}
主构造器的默认参数
第一步:Person的构造器中age属性有默认参数。
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
}
class Person constructor(val name: String, val age: Int = 0) {
}
第二步:我们定义Java类,然后去调用Person类,然后构造Person。
出现了编译错误,为了能让Java也能有调用它的重载构造方法,我们可以修改下kotlin的代码,增加@JvmOverloads
class Person @JvmOverloads constructor(val name: String, val age: Int = 0) {
}
构造同名的工厂函数
package com.example.kotlindemo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity2 : AppCompatActivity() {
val persons = HashMap<String, Person>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
/**
* 构造同名的工厂函数
* 和Person类名一样
*/
fun Person(name: String): Person {
return persons[name] ?: Person("", 0).also { persons[name] = it }
}
}
class Person @JvmOverloads constructor(val name: String, val age: Int = 0) {
}
在Android中的例子是:
// 构造函数
var str = String()
// 同名的工厂函数
var str2 = String(charArrayOf('1', '2'))
7.2、类与成员的可见性
可见性对比
记忆:模子
修饰范围
只有protected只能修饰成员,其它的什么都能修饰。
模块概念
模块可以认为是一个jar包、一个aar
internal的作用
一般由SDK 或者 公共组件 开发者用于隐藏模块内部的实现细节。
跨模块访问kotlin中的internal修饰的类
1、首先在mylibrary中定义Test类
package com.example.mylibrary
internal class Test {
fun test() {
}
}
2、然后在app module中的kotlin类中访问Test类
3、然后在app module中的java类中访问Test类
Java类中是可以访问kotlin中的Internal修饰的类的。
package com.example.kotlindemo;
import com.example.mylibrary.Test;
public class JavaTest {
void test() {
Test test = new Test();
test.test();
}
}
如何才能在java中也访问不了kotlin中internal修饰的类,做到kotlin对java的隔离呢?
我们给方法增加一个别名 @JvmName("%abcd")
package com.example.mylibrary
internal class Test {
@JvmName("%abcd")
fun test() {
}
}
构造器的可见性
在构造器上增加了private的修饰,这样外界就没有办法通过构造来生成对象。
class Person private constructor(val name: String, val age: Int = 0) {}
属性的可见性
// 私有化属性name,外界没有办法访问到
class Person constructor(private val name: String, val age: Int = 0) {
}
属性可见性的两个结论
1、getter的可见性必须与属性的可见性保持一致。
2、setter的可见性不得大于属性的可见性。
顶级声明
1、顶级声明指文件内直接定义的属性、函数、类等。
2、顶级声明不支持protected
3、顶级声明被private修饰表示文件内部可见。
7.3、延迟初始化
第一种方式:初始化为null
class MainActivity2 : AppCompatActivity() {
private var tv: TextView? = null;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
tv = findViewById(R.id.tv)
tv?.text = ""
}
}
不推荐,因为后续调用的时候,都会通过?.来调用
第二种方式:使用lateinit
class MainActivity2 : AppCompatActivity() {
private lateinit var tv: TextView;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
tv = findViewById(R.id.tv)
tv?.text = ""
// 是否被初始化了
if (::tv.isInitialized) {
}
}
}
不推荐,因为TextView要设置为var,但是它一旦初始化之后就不会变化了。
lateinit注意事项:
1、lateinit会让编译器忽略变量的初始化,不 支持Int 等基本类型。
2、开发者必须能够在完全确定变量值的生命周期下使用lateinit。
3、不要在复杂的逻辑中使用lateinit,它只会让你的代码更加脆弱。
4、Kotlin 1.2加入的判断lateinit属性是否初始化(isInitialized)的API最好不要用。
第三种方式:lazy方式
class MainActivity2 : AppCompatActivity() {
// 只有在tv首次被访问的时候执行
private val tv: TextView by lazy {
findViewById(R.id.tv)
};
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
}
7.4、代理
代理是什么
接口代理:对象代替当前类A实现接口B的方法 我代替你处理它(我替小孩写作业)。
属性代理:对象代替属性a实现getter/setter方法。
比如lazy:lazy代替属性tv实现getter方法,获得tv的初始化。
接口代理的例子
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
}
interface Api {
fun a()
fun b()
fun c()
}
class ApiImpl : Api {
override fun a() {
}
override fun b() {
}
override fun c() {
}
}
/**
* 包装Api,做一些功能的增强,比如在c里面增加一些日志,但是我们还是得去实现a,b两个方法
*/
class ApiWrapper(val api: Api) : Api {
override fun a() {
api.a()
}
override fun b() {
api.b()
}
override fun c() {
Log.e("cdx", "ccccccc")
api.c()
}
}
// 对象api来代替ApiWrapper实现接口Api
// 这个地方相当于Api by api是一个类,然后我们重写这个类的方法
class ApiWrapper2(val api: Api) : Api by api {
override fun c() {
Log.e("cdx", "ccccccc")
api.c()
}
}
接口代理在代码中的使用场景?
属性代理-lazy
// 只有在tv首次被访问的时候执行
private val tv: TextView by lazy {
findViewById(R.id.tv)
};
lazy代理了tv的getter,然后将findViewById的值赋值给了tv
属性代理-observable
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val stateManager = StateManager()
stateManager.state = 3
stateManager.state = 4
}
}
class StateManager {
// observable代理了state的setter方法,当设置完以后,就可以获取到旧方法,新方法了
var state: Int by Delegates.observable(0) { property, oldValue, newValue ->
Log.e("cdx", "$oldValue-$newValue")
}
}
observable代理了state的setter,当state设置完setter以后,会获取到老数据和新数据。
自定义代理
X()对象代理了x属性的getter和setter属性
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val foo = Foo()
Log.e("cdx", foo.x.toString())
foo.x = 3
Log.e("cdx", foo.x.toString())
}
}
class Foo {
// X()对象代理了属性x的getter和setter
var x: Int by X()
}
class X {
operator fun getValue(foo: Any?, property: kotlin.reflect.KProperty<*>): kotlin.Int {
return 2
}
operator fun setValue(foo: Any?, property: KProperty<*>, i: Int) {
Log.e("cdx","i:$i")
}
}
7.6、单例
如何实现
kotlin中如何访问
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
Log.e("cdx", "" + Singleton.x)
Log.e("cdx", "" + Singleton.y())
}
}
// 饿汉式单例
object Singleton {
var x: Int = 2
fun y() {
}
}
java中如何访问
java中访问需要有INSTANCE关键字
public class JavaTest {
void test() {
int x = Singleton.INSTANCE.getX();
Singleton.INSTANCE.y();
}
}
如何让kotlin的成员有静态的概念
1、这个特性也是用于Java在调用的时候,更像是静态方法的调用,kotlin的调用没有什么影响
2、增加@JvmStatic
首先定义kotlin的单例,然后在kotlin文件中调用它
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
Log.e("cdx", "" + Singleton.x)
Log.e("cdx", "" + Singleton.y())
}
}
// 饿汉式单例
object Singleton {
@JvmStatic var x: Int = 2
@JvmStatic fun y() {
}
}
然后是在java的文件中调用它,我们可以看到它少了INSTANCE关键字
public class JavaTest {
void test() {
int x = Singleton.getX();
Singleton.y();
}
}
不生成getter/setter @JvmField
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
Log.e("cdx", "" + Singleton.x)
Log.e("cdx", "" + Singleton.y())
}
}
// 饿汉式单例
object Singleton {
@JvmField var x: Int = 2
@JvmStatic fun y() {
}
}
在java中调用如下:
public class JavaTest {
void test() {
// 由于在定义kotlin单例中类的成员的时候,使用了@JvmField,
// 所以在Java中调用的时候,不再使用getX和setX,而是直接使用了x
int x = Singleton.x;
Singleton.y();
}
}
普通类的静态成员
普通类在用@JvmStatic修饰是不可以的,但是我们可以借助于伴生对象
这两种情况是等价的,伴生对象 + @JvmStatic相当于是一个静态方法
首先定义普通类,内部有非静态Field和静态Field
class Foo {
// 生成非静态Field
@JvmField var x: Int = 2
// 伴生对象,相当于另外的一半
companion object {
@JvmStatic
fun y() {
}
// 生成静态的Field
@JvmField var y:Int = 2
}
}
然后在java文件中去调用它
public class JavaTest {
void test() {
// 对象调用的方式
Foo foo = new Foo();
int x = foo.x;
// 静态调用的方式
Foo.y();
int y = Foo.y;
}
}
object的构造器
1、object不能有构造器。
2、object可以有若干个init块。
object类的继承
// 单例类的继承和普通类是一样的
object Singleton:Runnable {
override fun run() {
}
}
7.7、内部类
内部类定义
1、和Java的定义相反,在kotlin中,加上inner表示非静态,什么都不加,表示静态。
内部类实例化
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 非静态内部类的调用
val inner = Outer().Inner()
// 静态内部类的调用
val staticInner = Outer.StaticInner()
}
}
class Outer {
// 非静态内部类
inner class Inner
// 静态内部类
class StaticInner
}
内部object
// 由于object内部成员默认是static的,
// 所以不能再内部的object前面增加Inner
object OuterObject {
object StaticInnerObject
}
匿名内部类
java和kotlin匿名内部类的表示
匿名内部类实现多个接口
java并不支持,但是kotlin确支持
7.8、数据类
数据类component属性
因为是数据类,所以才有这个特性。
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
var data = TestData("张三", 11)
val component1 = data.component1()
val component2 = data.component2();
Log.e("cdx","$component1-$component2")
}
}
data class TestData(val name: String, val age: Int)
data类的特性
1、有component方法。
2、编译器基于component自动生成了equals/hashCode/toString/copy。
数据类解构
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
var data = TestData("张三", 11)
// 解构赋值
var (name, age) = data
Log.e("cdx", "$name-$age")
}
}
java和kotlin的区别
7.9、枚举类
Java和kotlin枚举的区别
枚举的属性
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
Log.e("cdx",State.Idle.name)
Log.e("cdx",State.Busy.name)
}
}
enum class State {
Idle, Busy
}
结果如下:
枚举的构造器
枚举的接口
枚举的接口-各自实现
枚举进行扩展(和Java不同的地方)
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
Log.e("cdx", State.Idle.next().name)
}
// 获取下一个美剧值
fun State.next(): State {
return State.values().let {
val nextOrdinal = (ordinal + 1) % it.size
it[nextOrdinal]
}
}
}
枚举用来比大小
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
var state = State.Idle
if (state < State.Idle){
Log.e("cdx","11111")
} else if (state < State.Busy){
Log.e("cdx","22222")
}
}
}
enum class State {
Idle, Busy
}
枚举的区间
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
var colors = Color.Yello..Color.Green
var color = Color.Black
var result = color in colors
}
}
enum class Color {
Yello, Black, Red, Green, White, Gray
}
7.10、密封类
定义
1、密封类是一种特殊的抽象类。
2、密封类的子类定义在与自身相同的文件中。
3、密封类的子类的个数是有限的。
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val state: PlayerState = Playing()
val result = when (state) {
Idle -> {
1
}
is Playing -> {
2
}
is Error -> {
3
}
else -> {
4
}
}
}
}
//1、首先是一个抽象类
//2、其次是一个密封类
sealed class PlayerState {
constructor()
constructor(int: Int)
}
//单例
object Idle : PlayerState() {}
class Playing(val song: String = "") : PlayerState() {}
class ErrorInfo(val error: Int = 0) : PlayerState() {}
密封类VS枚举类
7.11、内联类
定义
1、内联类是对某一个类型的包装。
2、内联类是类似于Java装箱类型的一种类型。
3、编译器会尽可能使用被包装的类型进行优化。
/**
* 1、对Int类型的包装
* 2、必须是val。
*/
inline class BoxInt(val value:Int){
operator fun inc():BoxInt{
return BoxInt(value + 1)
}
}
内联类的属性
内联类是对其它类型的包装,所以不应该有属性。
内联类的继承结构
1、可以实现接口,但是不能继承父类也不能被父类继承。
内联类的编译优化
内联类的方法都会被编译成静态方法
内联类的使用场景
内联类可以用作枚举
内联类的限制
1、主构造器必须有且仅有一个只读属性。
2、不能定义有backing-field的其他属性。
3、被包装类型必须不能是泛型类型。
4、不能继承父类也不能被继承。
5、内联类不能定义为其他类的内部类。
7.12、kotlin中的JSON序列化
Gson:JSON解析库
kotlinx.serialization:JSON解析库
moshi:JSON解析库