一.类
1.构造函数,Kotlin和Java一样可以有1个或者多个构造函数,
但是Kotlin区分 主构造函数和次构造函数,并且,主构造函数是类头的一部分,次构造函数就是位于类内部的构造函数(我自己理解的)
class Test constructor(name: String) {}
或者是省略constructor关键字
class Test (name: String) {}
主构造函数不能包含任何代码,初始化的代码可以放到init关键字为前缀的代码块中去,并且,初始化代码块,会优先于次构造函数执行,即使没有主构造函数
class Test(var name: String) {//这段代码真心没明白啥意思
var firstProperty = "First Property :$name ".also(::println)
init {
println("first initializer block that prints ${name}")
}
var secondProperty = "Second Property ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
如果一个非抽象类没有声明任何构造函数,则他会生成一个可见性为public的不带参的主构造函数,想要改变可见性,可以使用private关键字
2.创建类的实例-和java一样,没有new就行了
var test = Test("张三")
3.超级父类-Any类似于java重的Object,是任何一个类的隐形父类
4.复写方法(override)
1.父类的方法必须是open的才可以允许子类复写
2.子类复写时,必须由override关键字
3.子类不允许出现和父类的非open方法同名的方法,当然更不允许复写父类非open方法
4.标记为override的方法允许子类再次复写,如果拒绝子类复写,则可以加上final关键字
5.覆盖属性,同样需要override关键字
可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个 val 属性本质 上声明了一个 get 方法,而将其覆盖为 var 只是在子类中额外声明一个 set 方法。
6.子类的初始化顺序:在构造派生类的新实例的过程中,第一步完成其基类的初始化(信息量很大)
这意味着,基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑 中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确
的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员
7.调用超类实现,super关键字
8.抽象类or抽象成员的关键字依然是abstract
9.伴生对象(companion object)-后续更新
10.属性与字段
- var和val都能声明属性,var声明的是可变的,而val声明的是只读的
2.一个只读属性,需要用val声明,并且不能由set方法
11.幕后字段/幕后属性(没搞懂)
12.延迟初始化属性与变量
由于:一般地,属性声明为非空类型必须在构造函数中初始化,但有些时候需要延迟初始化,比如布局的findViewById()得到的控件,这时可以使用lateinit
1.关键字-lateinit,关于latinit使用可能遇到的错误
lateinit modifier is allowed only on mutable properties
lateinit modifier is not allowed on primitive type properties
意思是说:lateinit只能修饰不可空类型,并且不能修饰基础类型包括(四类八种基础类型int, double,boolean等)
二.接口
1.关键字与Java一致-interface
2.实现接口
注意:
1.Kotlin中实现和继承均使用:,而,Java中使用的分别是implements和extend
2.Kotlin中,接口的方法可以有方法提,并且如果有方法体则默认不是抽象的,实现接口的时候可以不用实现该方法,而Java的接口,抽象方法不允许有方法体
interface IInterface {
fun test()
}
class Test(var name: String) : IInterface {
override fun test() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
3.属性-与Java不同,Kotlin重的属性居然也有abstract的概念,所以实现类必须提供该属性对应的实现,可以有2种方式:
接口:
interface IInterface {
val prop:Int
fun test()
}
1.在构造中实现
class Test(var name: String, override val prop: Int) : IInterface {
override fun test() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
2.在成员变量中实现
class Test(var name: String) : IInterface {
override val prop: Int
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override fun test() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
4.接口的继承:
5.注意属性&方法的覆盖
实现多个接口时,可能会出现同一个方法继承多个实现的问题,比如C要实现A,B,但是A,B中均有foo()方法需要实现,实现时,可指明对应的接口方法
interface A {
fun foo() {
println("A")
}
fun bar()
}
interface B {
fun foo() {
println("B")
}
fun bar() {
println("bar")
}
}
class D : A, B {
override fun bar() {
super<B>.bar()
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
三.可见性修饰符
1.4种修饰符:public,protected,private,internal,默认的可见性修饰符为public
四.扩展
1.扩展函数
Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。这通过叫做 扩展 的特殊声明完成。例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。这个新 增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。这种机制称为 扩展函数
。此外,也有扩展属性,允许你为一个已经存在的类添加新的属性 。
2.扩展属性
3.扩展的作用域
4.伴生对象的扩展
5.扩展声明为成员
在一个类的内部,你可以为另一个类声明扩展,在这样扩展的内部,有多个隐式接收者--其中的对象成员可以无需通过限定符访问,扩展声明所在的类的实例称为分发接收者,扩展方法调用所在的接收者类型的实例称为扩展接收者。
6.扩展的可见性
扩展的可见性与相同作用域内声明的其他实体可见性相同
-在文件顶层声明的扩展可以访问同一文件中的其他private顶层声明
-如果扩展是在其接收者类型外部声明的,那么该扩展不能访问接收者的private成员
五.数据类
1.概念:我们经常创建一些只保存数据的类(Java中的常量类),在Kotlin中,这叫做数据类并标记为data
2.数据类:
-“data class must have at least one primary constructor parameter”(必须至少有一主构造参数)
-主构造函数的所有参数需要标记为val或者var
-数据类不能是抽象的,开放的,密封或者内部的
-(1.1之前)数据类只能实现接口
3.在类体中声明的属性
对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性,比如:
data class User constructor(var name: String) {
var age: Int = 0
}
如果没有显示实现自动生成的函数,在 toString() 、equals() 、hashCode() 以及 copy() 的实现中只会用到 name 属性,并且 只有一个 component 函数 component1() 。虽然两个 Person 对象可以有不同的年龄,但它们会视 为相等。
4.复制
1.关键字:copy()
很多情况下,我们需要复制一个对象该变一些他们的属性,但其余部分保持不变,对于上面的User,可以用copy():
var user: User = User(name = "张三")
var anotherUser = user.copy(name = "里斯")
六.泛型
1.与Java类似,Kotlin的类也可以有泛型
class User<T>(t: T) {
var value = t;
}
2.型变
3.声明处型变
4.类型投影
1.使用处型变-类型投影
2.星投影
5.泛型函数
class User<T>(t: T) {
var value = t
fun <T> singletonlist(item: T): List<T> {
return listOf(item)
}
}
6.泛型约束
1.上界,对应Java中的extends,Kotlin中使用:
7.类型擦除
Kotlin 为泛型声明用法执行的类型安全检测仅在编译期进行。运行时泛型类型的实例不保留关于其类 型实参的任何信息。其类型信息称为被擦除。例如,Foo<Bar> 与 Foo<Baz?> 的实例都会被擦除为
Foo<> 。 因此,并没有通用的方法在运行时检测一个泛型类型的实例是否通过指定类型参数所创建 ,并且编译
器禁止这种 is 检测。
类型转换为带有具体类型参数的泛型类型,如 foo as List<String> 无法在运行时检测。当高级
程序逻辑隐含了类型转换的类型安全而无法直接通过编译器推断时,可以使用这种非受检类型转换。编 译器会对非受检类型转换发出警告,并且在运行时只对非泛型部分检测(相当于 foo as List<> )。
泛型函数调用的类型参数也同样只在编译期检测。在函数体内部,类型参数不能用于类型检测,并且类 型转换为类型参数(foo as T)也是非受检的。然而,内联函数的具体化的类型参数会由调用处内联
函数体中的类型实参所代入,因此可以用于类型检测与转换,与上述泛型类型的实例具有相同限制。
七.嵌套类与内部类
1.demo
class User {
var age: Int = 0
class Nested {
fun foo() = 2
}
}
使用:
val demo = User.Nested()
demo.foo()
2.内部类
1.关键字:inner
2.概念:标记为inner的内部类可以访问其外部类的成员,内部类会带有一个外部类的引用
class User {
var age: Int = 0
inner class Nested {//可以访问age,但是没有inner关键字是无法访问的
fun foo() = age
}
}
使用也发生了变化
val demo = User().Nested()//User后面加了()
demo.foo()
3.匿名内部类
testTv.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.e("test", "onClick")
}
})
//lamda表达式
var onClickListener: (View) -> Unit = {
Log.e("test", "onClick")
}
testTv.setOnClickListener(onClickListener)
或者
var onClickListener = View.OnClickListener {
Log.e("test", "onClick")
}
testTv.setOnClickListener(onClickListener)
八.枚举类
1.枚举类最基本的用法是实现类型安全的枚举,每个枚举常量都是一个对象。枚举常量用逗号分隔。
enum class Sex {
MALE, FEMALE
}
2.初始化
enum class Sex constructor(sex: Int) {
MALE(0),
FEMALE(1)
}
3.匿名类
枚举常量还可以声明其带有相应方法以及覆盖了基类方法的匿名类。
enum class ProtocolState { WAITING {
override fun signal() = TALKING },
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
九.对象表达与对象声明
1.如果,我们只需要“一个对象而已”,并不需要特殊的数据类型,那么我们可以简单地写:
class User {
var age: Int = 0
fun foo() {
var abc = object {
var x = 0
var y = -1
}
println("abc的属性:${abc.x} & ${abc.y}")
}
}
请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数 的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果 你没有声明任何超类型,就会是 Any 。在匿名对象中添加的成员将无法访问。
2.对象声明:主要用于单例的创建(待学)
3.伴生对象:同样适用于单例模式的创建
class User {
companion object Factory {
fun createUser(): User = User()
}
}
使用:
var createUser = User.createUser()
伴生对象的名称可以省略:通过Companion使用
class User {
companion object {}
}
使用:
var createUser = User.Companion
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口.
4.对象声明与对象表达式的差异:
-对象表达式是在使用它们的地方立即执行(及初始化)的
-对象声明是在第一次被访问到时延迟初始化的
-伴生对象的初始化是相应的类被加载(解析)时,与Java静态初始化的代码语义相配
十.类型别名
1.关键字:typealias
2.类型别名为现有类型提供替代名称。
3.类型别名不会引入新类型。它们等效于相应的底层类型。
十一.内联类
1.关键字:inline
2.有时候,业务逻辑需要围绕某种类型创建包装器。然而,由于额外的堆内存分配问题,它会引入运行时的 性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因为原生类型通常在运行时就 进行了大量优化,然而他们的包装器却没有得到任何特殊的处理。
内联类必须含有唯一的一个属性在主构造函数中初始化。在运行时,将使用这个唯一属性来表示内联类的实例
3.成员:内联类支持普通类中的一些功能。特别是,内联类可以声明属性与函数
然而,内联类的成员也有一些限制:
— 内联类不能含有init代码块
— 内联类不能含有幕后字段
— 因此,内联类只能含有简单的计算属性(不能含有延迟初始化/委托属性)
4.内联类允许继承
5.内联类与类型别名
初看起来,内联类似乎与类型别名非常相似。实际上,两者似乎都引入了一种新的类型,并且都在运行时 表示为基础类型。
然而,关键的区别在于类型别名与其基础类型(以及具有相同基础类型的其他类型别名)是 赋值兼容 的, 而内联类却不是这样。
换句话说,内联类引入了一个真实的新类型,与类型别名正好相反,类型别名仅仅是为现有的类型取了 个新的替代名称(别名)
6.在 Gradle 中启用内联类
compileKotlin {
kotlinOptions.freeCompilerArgs += ["-Xinline-classes"]
}
十二.委托
1.属性委托
2.由委托实现
3.覆盖有委托实现的接口成员
4.委托属性
5.标准委托
网友评论