class MyClass {
companion object {
val myClassField1: Int = 1
var myClassField2 = "this is myClassField2"
fun companionFun1() {
println("this is 1st companion function.")
foo()
}
fun companionFun2() {
println("this is 2st companion function.")
companionFun1()
}
}
fun MyClass.Companion.foo() {
println("伴随对象的扩展函数(内部)")
}
fun test2() {
MyClass.foo()
}
init {
test2()
}
}
val MyClass.Companion.no: Int
get() = 10
fun MyClass.Companion.foo() {
println("foo 伴随对象外部扩展函数")
}
fun main(args: Array<String>) {
println("no:${MyClass.no}")
println("field1:${MyClass.myClassField1}")
println("field2:${MyClass.myClassField2}")
MyClass.foo()
MyClass.companionFun2()
}
运行结果:
no:10
field1:1
field2:this is myClassField2
foo 伴随对象外部扩展函数
this is 2st companion function.
this is 1st companion function.
foo 伴随对象外部扩展函数
2295Kotlin 继承
几点补充:
1、子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写:
open class Person(var name: String, var age: Int) {
open var sex: String = "unknow"
init {
println("基类初始化")
}
}
// 子类的主构造方法的 name 前边也加了 var,这是不允许的,会报'name' hides member of supertype and needs 'override' modifier
class Student(var name: String, age: Int, var no: String, var score: Int) : Person(name, age) {
override var sex: String = "male"
}
如上代码片段中,子类 Student 主构造方法的第一个字段 name 前边加 var 关键字会报错。
2、子类不一定要调用父类和接口中共同拥有的同名的方法
引用文章中的话:“C 继承自 a() 或 b(), C 不仅可以从 A 或则 B 中继承函数,而且 C 可以继承 A()、B() 中共有的函数。此时该函数在中只有一个实现,为了消除歧义,该函数必须调用A()和B()中该函数的实现,并提供自己的实现。”
我试过了,不是必须调用 A() 和 B() 中该函数的实现,代码如下:
open class A {
open fun f() {
println("A")
}
fun a() {
println("a")
}
}
interface B {
fun f() {
println("B")
}
fun b() {
println("b")
}
}
class C : A(), B {
override fun f() {
// super<A>.f()
// super<B>.f()
println("C")
}
}
如上代码片段,注释掉 super<A>.f() 和 super<B>.f() 也不报错。
3、关于子类不能用 val 重写父类中的 var,我的猜测是:子类重写父类属性,也就相当于必须重写该属性的 getter 和 setter 方法,而子类中的 val 不能有 setter 方法,所以无法“覆盖”父类中 var 的 setter 方法,相当于缩小了父类中相应属性的使用范围,是不允许的,就像我们不能把父类中一个 public 方法重写成 private 方法一样。
4、如果一个变量想要在定义的时候被初始化,则该变量必须拥有 backing field 字段,该变量的默认 getter 和 setter 方法中是有定义 field 字段的,但是如果我们重写了这个变量的 getter 方法和 setter 方法,并且在 getter 方法和 setter 方法中都没有出现过 filed 这个关键字,则编译器会报错,提示 Initializer is not allowed here because this property has no backing field,除非显式写出 filed 关键字(哪怕它什么都不干,只要放在那里就可以了,我理解是出现一次就相当于“声明”过了,就可以用了,而在定义变量的时候初始化是要求 field 被“声明”过才可以):
var aaa: Int = 0
get() {
field // 这里必须出现一下field关键字,否则 var aaa: Int = 0 会报错,除非你去掉 = 0这部分,不要给它赋初始化值
return 0
}
set(value) {}
2294Kotlin 类和对象
关于 field 我也分享一下理解。
// 还是 JAVA 代码
int no = 100;
private int _no = 100;
public int getNo() {
return _no;//
}
public void setNo(int value) {
if (value < 10) {
_no = value;// Kotlin中出现“no =”这样的字样,直接被编译器理解成“这里要调用setter方法”
} else {
_no = -1;// 在setter方法中调用setter方法,这是不正确的
}
}
var demo = Outter.Nested()// 嵌套类,Outter后边没有括号
var demo = Outter().Inner();// 内部类,Outter后边有括号
也就是说,要想构造内部类的对象,必须先构造外部类的对象,而嵌套类则不需要;
(2)引用外部类的成员变量的方式不同
先来看嵌套类:
class Outer { // 外部类
private val bar: Int = 1
class Nested { // 嵌套类
var ot: Outer = Outer()
println(ot.bar) // 嵌套类可以引用外部类私有变量,但要先创建外部类的实例,不能直接引用
fun foo() = 2
}
}
再来看一下内部类(引用文章中代码):
class Outer {
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner {
fun foo() = bar // 访问外部类成员
fun innerTest() {
var o = this@Outer //获取外部类的成员变量
println("内部类可以引用外部类的成员,例如:" + o.v)
}
}
}
2296Kotlin 扩展
伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。
对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:
例如以下代码:
运行结果:
2295Kotlin 继承
几点补充:
1、子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写:
如上代码片段中,子类 Student 主构造方法的第一个字段 name 前边加 var 关键字会报错。
2、子类不一定要调用父类和接口中共同拥有的同名的方法
引用文章中的话:“C 继承自 a() 或 b(), C 不仅可以从 A 或则 B 中继承函数,而且 C 可以继承 A()、B() 中共有的函数。此时该函数在中只有一个实现,为了消除歧义,该函数必须调用A()和B()中该函数的实现,并提供自己的实现。”
我试过了,不是必须调用 A() 和 B() 中该函数的实现,代码如下:
如上代码片段,注释掉 super<A>.f() 和 super<B>.f() 也不报错。
3、关于子类不能用 val 重写父类中的 var,我的猜测是:子类重写父类属性,也就相当于必须重写该属性的 getter 和 setter 方法,而子类中的 val 不能有 setter 方法,所以无法“覆盖”父类中 var 的 setter 方法,相当于缩小了父类中相应属性的使用范围,是不允许的,就像我们不能把父类中一个 public 方法重写成 private 方法一样。
4、如果一个变量想要在定义的时候被初始化,则该变量必须拥有 backing field 字段,该变量的默认 getter 和 setter 方法中是有定义 field 字段的,但是如果我们重写了这个变量的 getter 方法和 setter 方法,并且在 getter 方法和 setter 方法中都没有出现过 filed 这个关键字,则编译器会报错,提示 Initializer is not allowed here because this property has no backing field,除非显式写出 filed 关键字(哪怕它什么都不干,只要放在那里就可以了,我理解是出现一次就相当于“声明”过了,就可以用了,而在定义变量的时候初始化是要求 field 被“声明”过才可以):
2294Kotlin 类和对象
关于 field 我也分享一下理解。
上面这样就没有问题, 而 field 就相当于编译器给你提供两一个隐式私有变量。
2293Kotlin 类和对象
补充几点:
1、field 关键字
这个问题对 Java 开发者来说十分难以理解,网上有很多人讨论这个问题,但大多数都是互相抄,说不出个所以然来,要说还是老外对这个问题的理解比较透彻,可以参考这个帖子:https://stackoverflow.com/questions/43220140/whats-kotlin-backing-field-for/43220314
其中最关键的一句:Remember in kotlin whenever you write foo.bar = value it will be translated into a setter call instead of a PUTFIELD.
也就是说,在 Kotlin 中,任何时候当你写出“一个变量后边加等于号”这种形式的时候,比如我们定义 var no: Int 变量,当你写出 no = ... 这种形式的时候,这个等于号都会被编译器翻译成调用 setter 方法;而同样,在任何位置引用变量时,只要出现 no 变量的地方都会被编译器翻译成 getter 方法。那么问题就来了,当你在 setter 方法内部写出 no = ... 时,相当于在 setter 方法中调用 setter 方法,形成递归,进而形成死循环,例如文中的例子:
这段代码按以上这种写法是正确的,因为使用了 field 关键字,但是如果不用 field 关键字会怎么样呢?例如:
注意这里我们使用的 Java 的思维写了 getter 和 setter 方法,那么这时,如果将这段代码翻译成 Java 代码会是怎么样呢?如下:
翻译成 Java 代码之后就很直观了,在 getter 方法和 setter 方法中都形成了递归调用,显然是不正确的,最终程序会出现内存溢出而异常终止。
2、嵌套类和内部类在使用时的区别
(1)创建对象的区别
也就是说,要想构造内部类的对象,必须先构造外部类的对象,而嵌套类则不需要;
(2)引用外部类的成员变量的方式不同
先来看嵌套类:
再来看一下内部类(引用文章中代码):
可以看来内部类可以直接通过 this@ 外部类名 的形式引用外部类的成员变量,不需要创建外部类对象;
3、匿名内部类的实现
引用文章中的代码
特别注意这里的 object : TestInterFace,这个 object 是 Kotlin 的关键字,要实现匿名内部类,就必须使用 object 关键字,不能随意替换其它单词,切记切记。
2292Kotlin 循环控制
正常循环:
如果你需要按反序遍历整数可以使用标准库中的 downTo() 函数:
也支持指定步长:如果循环中不要最后一个范围区间的值可以使用 until 函数: