Java转Kotlin,对语法、声明和一些符号的使用不是很清楚,特此记录
变量定义方式
val 只读变量,只能赋值一次
var 读写变量,可多次赋值
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
理解自动类型推断
函数的定义
//带有两个 Int 参数、返回 Int 的函数:
fun sum(a: Int, b: Int): Int {
return a + b
}
//将表达式作为函数体、返回值类型自动推断的函数:
fun sum(a: Int, b: Int) = a + b
//将表达式作为函数体、返回值类型自动推断的函数:
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
//Unit 返回类型可以省略:
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
模板字符串
在字符串中引用变量,或者在字符串中使用任意表达式
var a = 1
val s1 = "a is $a1"
a = 2
val s2 = "${s1.replace("is", "was")}, but now is $a"
s2的输出结:a was 1, but now is 2
表达式
与Java类似,if(){}
,if(){}else{}
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
fun maxOf(a: Int, b: Int) = if (a > b) a else b
两种写法都一样
空值检测和和使用
当某个变量的值可以为 null 的时候,必须在声明处的类型后添加 ? 来标识该引用可为空
fun parseInt(str: String): Int? {
//表示该方法可以返回一个整型或者null
}
使用类型检测及自动类型转换
is
运算符检测一个表达式是否某类型的一个实例。 如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}
// 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
return null
}
fun getStringLength(obj: Any): Int? {
// `obj` 在 `&&` 右边自动转换成 `String` 类型
if (obj is String && obj.length > 0) {
return obj.length
}
return null
}
emmmm.....这里的is
和Java的instanceof
是一个意思,其中is
作用范围内自动转换数据类型比较Java方便了不少
if条件表达式
// 传统用法
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作为表达式
val max = if (a > b) a else b
//if的分支可以是代码块,最后的表达式作为该块的值
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
与Java的if不同在于,Kotlin的if可以作为语句,也可一作为表达式,当它作为表达式时,每一个分支中最后一个表达式的值为分支的值
When表达式
Kotlin使用when取代了switch
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}
//多分支合并,和switch真的很像
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
//任意表达是作为分支
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
//任意区间判断作为分支
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
//Since Kotlin 1.3, it is possible to capture when subject in a variable using following syntax:
//强大到令人发指??
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
和if一样,when也可以作为表示式使用,但是需要注意:如果 when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了
伴生对象
类内部的对象声明可以用 companion 关键字标记
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用:
val instance = MyClass.create()
其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象 (无论是否命名)的引用
class MyClass1 {
companion object Named { }
}
val x = MyClass1
class MyClass2 {
companion object { }
}
val y = MyClass2
emmm...看起来很像Java的static,但是它不是static,请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
对象表达式和对象声明之间有一个重要的语义差别:
对象表达式是在使用他们的地方**立即**执行(及初始化)的
对象声明是在第一次被访问到时**延迟**初始化的
伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配
创建基本类和实例
不需要关键字new,直接类名称就能创建一个对象
val rectangle = Rectangle(5.0, 2.0) // 不需要“new”关键字
val triangle = Triangle(3.0, 4.0, 5.0)
主构造函数和次构造函数以及初始化
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后
class Person constructor(firstName: String) { ... }
//如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字
class Person(firstName: String) { ... }
//事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:
class Person(val firstName: String, val lastName: String, var age: Int) { …… }
主构造函数不能包含任何的代码
初始化代码应该放在init{}
中
在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
fun main() {
InitOrderDemo("hello")
}
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public
这一点和Java挺像的
类也可以声明前缀有 constructor的次构造函数
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
属性、访问器、幕后字段、幕后属性
这里我看api文档是真的没有看懂。。。。。。感谢依然范特希西大佬
Kotlin 什么是幕后字段?
接口
使用关键字 interface 来定义接口
Kotlin 的接口与 Java 8 类似,既包含抽象方法的声明,也包含实现。
需要注意实现多个接口冲突的覆盖
//接口的定义
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
/**在接口中声明的属性要么是抽象的,要么提*供访问器的实现。
在接口中声明的属性不能有幕后字段(backingfield),
因此接口中声明的访问器不能引用它们*/
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
//接口冲突的覆盖和重写
class Child : MyInterface {
override val prop: Int = 29
}
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
接口里面不仅是声明方法,还可以定义方法体。。。但是和抽象类还是有很大的区别,与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
访问修饰符
- 如果你不指定任何可见性修饰符,默认为
public
,这意味着你的声明将随处可见; - 如果你声明为
private
,它只会在声明它的文件内可见; - 如果你声明为
internal
,它会在相同模块内随处可见; -
protected
不适用于顶层声明。
对模块的理解:可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:
- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
- 一次 <kotlinc> Ant 任务执行所编译的一套文件。
扩展函数和扩展属性
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个swap 函数:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
与函数类似,Kotlin 支持扩展属性:
val <T> List<T>.lastIndex: Int
get() = size - 1
注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。