装箱 同一性
无符号整型 实验性状态
if是一个表达式,它会返回一个值。因此不需要三元操作符
if的分支也可以是代码块,最后的表达式的值作为该快的值
when取代switch操作符,when既可以被当作表达式使用也可以别当作语句使用。如果他被当作表达式,符合条件的分支的值就是整个表达式的值;如果被当作语句使用,则忽略个别分支的值。
for循环
for (item in collection) print(item)
对于区间或者数组的for循环,会被编译为并不创建迭代器的基于索引的循环
返回与跳转
kotlin有三种结构化跳转表达式:
return
break
continue
break continue标签
类与对象
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor关键字。
主构造函数不能包含任何的代码。初始化的代码放到以init关键字作为前缀的初始化块(initializer blocks)中。
在实例初始化期间,初始化块按照他们出现在类体中的顺序执行,与属性初始化器交织在一起:
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("first initializer block that prints ${name}")
}
.....
}
如果一个非抽象类没有声明任何(主次)构造函数,他会有一个生成的不带参数的主构造函数。构造函数的可见性为public。
类成员
类可以包含:
- 构造函数与初始化块
- 函数
- 属性
- 嵌套类和内部类
- 对象声明
覆盖方法 open override
覆盖属性
派生类初始化顺序
在构造派生类的新实例的过程中,第一步完成其基类的初始化,因此发生在派生类的初始化逻辑运行之前。
伴生对象
kotlin没有静态方法,大多数情况下,它建议简单的使用包级函数。
如果你需要写一个无需用一个类的实例来调用、但需要访问类内部的函数,你可以把它写成该类内 对象声明 中的一员。
更具体的讲,如果你在类内声明一个伴生对象,你就可以使用像java中的调用静态方法相同的语法来调用其成员,只是用类名作为限定符
属性与字段
声明属性
声明属性的完整语法
var <propertyName>[: <ProperType>] [= <property_initializer]
[<getter>]
[<setter>]
可以自定义getter,如果定义一个自定义的getter,那么每次访问属性时属性都会调用它
val isEmpty: Boolean
get() = this.size == 0
编译期常量
已知值的属性可以用const修饰符标记为编译期常量.这些属性需要满足以下要求:
- 位于顶层或者是object声明或companion object的一个成员
- 以String或原声类型值初始化
- 没有自定义的getter
延迟初始化属性与变量
一般地,属性声明为非空类型必须在构造函数中初始化。然后,这经常带来不便。例如:属性可以通过依赖注入来初始化,或者在单元测试的setup方法中初始化。这种情况下,你不能在构造函数内提供一个非空初始器。但你仍然想在类体中引用该属性时避免空检查。
为处理这种情况,你可以用lateinit修饰符标记该属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
}
该修饰符只能用于在类体中的属性
接口
你可以在接口中定义属性。再接口中定义的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段
可见性修饰符
- 包
- 类和接口
扩展
kotlin能够扩展一个类的新功能而无需继承该类或使用向装饰者这样的任何类型的设计模式。kotlin支持扩展函数和扩展属性
扩展是静态解析的
what does this mean?
这意味着调用的扩展函数是有函数所在的表达式的类型来决定的,而不是有表达式运行时求值结果决定的。
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
输出what?
扩展属性
伴生对象的扩展
class MyClass {
companion object {} // 将被称为"Companion"
}
fun MyClass.Companion.foo() { ... }
就像伴生对象的其它普通成员,只需要类名作为限定符去调用他们
MyClass.foo()
扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接受者,--其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例为分发接受者,扩展方法调用所在的接收者类型的实例称为扩展接收者。
class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
fun D.foo() {
bar() // 调用 D.bar
baz() //调用 C.baz
}
fun caller(d: D) {
d.foo() //调用扩展函数
}
//对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this 语法
fun D.foo() {
toString()
this@C.toString()
}
}
声明为成员的扩展可以声明为 open 并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
数据类
我们经常创建一些只保存数据的类。在kotlin中,这叫做 数据类 并标记data:
data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性导出以下成员:
- equals()/hashCode()对;
- toString() 格式是"User(name=John, age=42)";
- componentN()函数按声明的顺序对应于所有的属性;
- copy()函数
为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:
- 主构造函数需要至少一个参数;
- 主构造函数的所有参数需要标记为val或var;
- 数据类不能是抽象、开放、密闭或者内部的;
此外成员函数生成遵循关于成员继承的这些规则:
- 如果在数据类体中有显式实现 equals() 、 hashCode() 或者 toString() ,或者这些函数在父类中有 final 实现,那么不会生成这些函数,而会使用现有函数;
- 如果超类型具有 open 的 componentN() 函数并且返回兼容的类型, 那么会为数据类生成相应的函数,并覆盖超类的实现。
- 如果超类型的这些函数由于签名不兼容或者是 nal 而导致无法覆盖,那么会报错;
- 不允许为 componentN() 以及 copy() 函数提供显式实现。
在类体中声明的属性
请注意,对于那些自动生成的函数,编译器只是用在主构造函数内部定义的属性。如需在生成的实现中排出一个属性,请将其生命在类体中:
data class Person(val name: String) {
var age: Int = 0
}
在toString()、equals()、hashCode()以及copy()的实现中只会用到name属性,并且只有一个component函数component1()。虽然两个Person对象可以有不同年龄,但他们会视为相等。
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
复制
在很多情况下,我们需要复制一个对象改变他的一些属性,单其余部分保持不变。copy()函数就是为此而生的。对于上文的User类,其实现会是类似下面这样:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
//so we can write code like this
val jack = User(name="Jack", age = 1)
val olderJack = jack.copy(age = 2)
泛型
型变
声明处型变与类型投射 泛型约束 泛型函数 泛型擦除。。。
嵌套类与内部类
类可以嵌套在其它类中:
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer().Inner().foo() // ==2
内部类
类可以标记为inner以便能够访问外部类的成员。内部类会带有一个对外部类的对象的引用:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
匿名内部类
枚举类
使用枚举常量
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
对象表达式与对象声明
有时候我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显示的声明一个新的子类。Jva用匿名内部类处理这种情况。Kotlin用对象表达式和对象声明对这个概念稍微概括了下。
对象表达式
如果超类型有一个构造函数,则必须传递适当的构造函数参数给他。多个超类型可以由跟在冒号后面的逗号分割的列表指定:
open class A(x: Int) {
public open val y: Int = x
}
interface B { ... }
val ab: A = object : A(1), B {
override val y = 15
}
任何时候,如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单的写:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为共有函数的返回类型或者用作共有属性的类型,那么该函数或者属性的实际类型回事匿名对象声明的超类型,如果没有声明任何超类型,就会是Any。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
就像Java匿名内部类一样,对象表达式中代码可以访问来自包含它的作用域的变量。(与Java不同的是,这不仅限于final或实际相当于final的变量)
对象声明
单例模式在一些场景中很有用,而Kotlin使单例声明变得很容易:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider?
get() = // ...
}
网友评论