Kotlin
一、Kotlin特点
package hello // 可选的包头
class Greeter(val name: String) {
fun greet() {
// 分号可以省略
println("Hello, $name")
}
}
// 包级可见的函数,接受一个字符串数组作为参数
fun main(args: Array<String>) {
Greeter("World!").greet() // 创建一个对象不用 new 关键字
}
二、基本数据
-
基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于 Java 的是,字符不属于数值类型,是一个独立的数据类型
-
字面常量:
- 十进制:123
- 长整型以大写的 L 结尾:123L
- 16 进制以 0x 开头:0x0F
- 2 进制以 0b 开头:0b00001011
- 注意:8进制不支持
-
支持传统符号表示的浮点数值:
- Doubles 默认写法:
123.5
,123.5e10
- Floats 使用 f 或者 F 后缀:
123.5f
- Doubles 默认写法:
-
可以使用下划线使数字常量更易读
val oneMillion = 1_000_000 val creditCardNumber = 1234_5678_9012_3456L val socialSecurityNumber = 999_99_9999L val hexBytes = 0xFF_EC_DE_5E val bytes = 0b11010010_01101001_10010100_10010010
-
比较两个数大小:三个等号 === 表示比较对象地址([-128, 127] 之间并不会创建新的对象),两个 == 表示比较两个值大小
-
类型转换
-
由于不同的表示方式,较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型
val b: Byte = 1 // OK, 字面值是静态检测的 val i: Int = b // 错误 val i: Int = b.toInt() // OK
-
每种数据类型都有下面的这些方法,可以转化为其它的类型:
toByte(): Byte toShort(): Short toInt(): Int toLong(): Long toFloat(): Float toDouble(): Double toChar(): Char
-
有些情况下也是可以使用自动类型转化的,前提是可以根据上下文环境推断出正确的数据类型而且数学操作符会做相应的重载
val l = 1L + 3 // Long + Int => Long
-
-
位操作符:对于Int和Long类型,还有一系列的位操作符可以使用
shl(bits) – 左移位 (Java’s <<) shr(bits) – 右移位 (Java’s >>) ushr(bits) – 无符号右移位 (Java’s >>>) and(bits) – 与 or(bits) – 或 xor(bits) – 异或 inv() – 反向
-
字符:和 Java 不一样,Kotlin 中的 Char 不能直接和数字操作,Char 必需是单引号 ' 包含起来的。比如普通字符 '0','a'。特殊字符可以用反斜杠转义:\t、 \b、\n、\r、'、"、\ 和 $。 编码其他字符要用 Unicode 转义序列语法:'\uFF00'。
-
布尔:布尔用 Boolean 类型表示,它有两个值:true 和 false。若需要可空引用布尔会被装箱
-
数组:数组用类 Array 实现,并且还有一个 size 属性及 get 和 set 方法,由于使用 [] 重载了 get 和 set 方法,所以我们可以通过下标很方便的获取或者设置数组对应位置的值。与 Java 不同的是,Kotlin 中数组是不协变的(invariant)。
数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。如下所示,我们分别是两种方式创建了两个数组:
fun main(args: Array<String>) { //[1,2,3] val a = arrayOf(1, 2, 3) //[0,2,4] val b = Array(3, { i -> (i * 2) }) //除了类Array,还有ByteArray, ShortArray, IntArray,用来表示各个类型的数组,省去了装箱操作,效率更高 val x: IntArray = intArrayOf(1, 2, 3) //读取数组内容 println(a[0]) // 输出结果:1 println(b[1]) // 输出结果:2 }
-
字符串
-
Java 一样,String 是不可变的。方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历
-
Kotlin 支持三个引号 """ 扩起来的字符串,支持多行字符串,比如:
fun main(args: Array<String>) { val text = """ 多行字符串1 多行字符串2 """ println(text) // 输出有一些前置空格 }
-
String 可以通过 trimMargin() 方法来删除多余的空白,默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")
fun main(args: Array<String>) { val text = """ |多行字符串1 |多行字符串2 |多行字符串3 """.trimMargin() println(text) // 前置空格删除了 }
-
字符串模板
-
字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:
fun main(args: Array<String>) { val i = 10 val s = "i = $i" // 求值结果为 "i = 10" println(s) }
-
或者用花括号扩起来的任意表达式:
fun main(args: Array<String>) { val s = "runoob" val str = "$s.length is ${s.length}" // 求值结果为 "runoob.length is 6" println(str) }
-
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:
fun main(args: Array<String>) { val price = """ ${'$'}9.99 """ println(price) // 求值结果为 $9.99 }
-
-
三、条件控制
-
if表达式
// 传统用法 var max = a if (a < b) max = b // 使用 else var max: Int if (a > b) { max = a } else { max = b } // 作为表达式 val max = if (a > b) a else b
-
使用区间:使用 in 运算符来检测某个数字是否在指定区间内
fun main(args: Array<String>) { val x = 5 if (x in 1..8) { println("x 在区间内") } } //如果你需要按反序遍历整数可以使用标准库中的 downTo() 函数: for (i in 4 downTo 1) print(i) // 打印结果为: "4321" //支持指定步长: for (i in 1..4 step 2) print(i) // 打印结果为: "13" for (i in 4 downTo 1 step 2) print(i) // 打印结果为: "42" //如果循环中不要最后一个范围区间的值可以使用 until 函数: for (i in 1 until 10) { // i in [1, 10), 不包含 10 println(i) }
-
when表达式
-
when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件
-
when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
-
when 类似其他语言的 switch 操作符
when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> { // 注意这个块 print("x 不是 1 ,也不是 2") } }
-
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔
when (x) { 0, 1 -> print("x == 0 or x == 1") else -> print("otherwise") }
-
也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:
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") }
-
另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需 任何额外的检测。
fun hasPrefix(x: Any) = when(x) { is String -> x.startsWith("prefix") else -> false }
-
-
返回和跳转
-
Kotlin 有三种结构化跳转表达式:
- return:默认从最直接包围它的函数或者匿名函数返回
- break:终止最直接包围它的循环
- continue:继续下一次最直接包围它的循环
-
Break和Continue 标签
-
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。
loop@ for (i in 1..100) { for (j in 1..100) { if (……) break@loop } }
-
-
三、类和对象
-
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
使用关键字class声明类,后面紧跟类名:
-
类的属性:关键字var声明为可变的,否则使用只读关键字val声明为不可变。要使用一个属性,只要用名称引用它即可
class Runoob { // 类名为 Runoob fun foo() { print("Foo") } // 成员函数 val site = Runoob() // Kotlin 中没有 new 关键字 site.name // 使用 . 号来引用 }
-
Kotlin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后。如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略
class Person constructor(firstName: String) {} class Person(firstName: String) {}
-
getter 和 setter,如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的
class Person { var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法 var initialized = 1 // 类型为 Int, 默认实现了 getter 和 setter val simple: Int? // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化 val inferredType = 1 // 类型为 Int 类型,默认实现 getter //非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用lateinit关键字描述属性 lateinit var subject: TestSubject //lastName 修改了 getter 方法,no 修改了 setter 方法 var lastName: String = "zhang" get() = field.toUpperCase() // 将变量赋值后转换为大写 set var no: Int = 100 //Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器 get() = field // 后端变量 set(value) { if (value < 10) { // 如果传入的值小于 10 返回该值 field = value } else { field = -1 // 如果传入的值大于等于 10 返回 -1 } } }
-
主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数,在同一个类中代理另一个构造函数使用 this 关键字
class Person constructor(firstName: String) { init { println("FirstName is $firstName") } // 次构造函数 constructor (name: String, alexa: Int) : this(name) { println("Alexa 排名 $alexa") } }
-
抽象类:抽象成员在类中不存在具体的实现,无需对抽象类或抽象成员标注open注解
open class Base { open fun f() {} } abstract class Derived : Base() { override abstract fun f() }
-
嵌套类
class Outer { // 外部类 private val bar: Int = 1 class Nested { // 嵌套类 fun foo() = 2 } } fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性 println(demo) // == 2 }
-
内部类:内部类使用 inner 关键字来表示。内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。为了消除歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label 是一个 代指 this 来源的标签
class Outer { private val bar: Int = 1 var v = "成员属性" /**嵌套内部类**/ inner class Inner { fun foo() = bar // 访问外部类成员 fun innerTest() { var o = this@Outer //获取外部类的成员变量 println("内部类可以引用外部类的成员,例如:" + o.v) } } } fun main(args: Array<String>) { val demo = Outer().Inner().foo() println(demo) // 1 val demo2 = Outer().Inner().innerTest() println(demo2) // 内部类可以引用外部类的成员,例如:成员属性 }
- 嵌套类和内部类在使用时的区别
- 创建对象的区别:嵌套类Outter后边没有括号,也就是说,要想构造内部类的对象,必须先构造外部类的对象,而嵌套类则不需要
- 引用外部类的成员变量的方式不同:嵌套类可以引用外部类私有变量,但要先创建外部类的实例,不能直接引用,内部类可以直接通过this@外部类名 的形式引用外部类的成员变量,不需要创建外部类对象
- 嵌套类和内部类在使用时的区别
-
匿名内部类
class Test { var v = "成员属性" fun setInterFace(test: TestInterFace) { test.test() } } //定义接口 interface TestInterFace { fun test() } fun main(args: Array<String>) { var test = Test() //采用对象表达式来创建接口对象,即匿名内部类的实例,bject 是 Kotlin 的关键字,要实现匿名内部类,就必须使用 object 关键字,不能随意替换其它单词 test.setInterFace(object : TestInterFace { override fun test() { println("对象表达式创建匿名内部类的实例") } }) }
-
类的修饰符包括 classModifier 和_accessModifier_:
-
classModifier: 类属性修饰符,标示类本身特性。
abstract // 抽象类 final // 类不可继承,默认属性 enum // 枚举类 open // 类可继承,类默认是final的 annotation // 注解类
-
accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见 protected // 同一个文件中或子类可见 public // 所有调用的地方都可见 internal // 同一个模块中可见
-
四、继承
-
Kotlin 中所有类都继承该 Any ( 不是 java.lang.Object)类,它是所有类的超类,对于没有超类型声明的类是默认超类,Any 默认提供了三个函数equals()、hashCode()、toString()
-
如果一个类要被继承,可以使用 open 关键字进行修饰。子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写
open class Base(p: Int) // 定义基类 class Derived(p: Int) : Base(p)
-
构造函数
-
如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
// 基类 open class Person(var name : String, var age : Int){} class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {} // 测试 fun main(args: Array<String>) { val s = Student("Runoob", 18, "S12346", 89) println("学生名: ${s.name}") }
-
如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。
/**用户基类**/ open class Person(name:String){ /**次级构造函数**/ constructor(name:String,age:Int):this(name){ println("-------基类次级构造函数---------") } } /**子类继承 Person 类**/ class Student:Person{ /**次级构造函数**/ constructor(name:String,age:Int,no:String,score:Int):super(name,age){ println("-------继承类次级构造函数---------") } } fun main(args: Array<String>) { var s = Student("Runoob", 18, "S12345", 89) }
-
-
重写
-
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词:
/**用户基类**/ open class Person{ open fun study(){ // 允许子类重写 println("我毕业了") } } /**子类继承 Person 类**/ class Student : Person() { override fun study(){ // 重写方法 println("我在读大学") } } fun main(args: Array<String>) { val s = Student() s.study(); //我在读大学 }
-
如果有多个相同的方法(继承或者实现自其他类,如A、B类),则使用super范型去选择性地调用父类的实现
open class A { open fun f () { print("A") } fun a() { print("a") } } interface B { fun f() { print("B") } //接口的成员变量默认是 open 的 fun b() { print("b") } } class C() : A() , B{ override fun f() { super<A>.f()//调用 A.f() super<B>.f()//调用 B.f() } } fun main(args: Array<String>) { val c = C() c.f();//AB }
-
-
属性重写
-
属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写
open class Foo { open val x: Int get { …… } } class Bar1 : Foo() { override val x: Int = …… }
-
可以用一个var属性重写一个val属性,但是反过来不行。因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法,可以在主构造函数中使用 override 关键字作为属性声明的一部分
interface Foo { val count: Int } class Bar1(override val count: Int) : Foo class Bar2 : Foo { override var count: Int = 0 }
-
五、接口
-
Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现
-
一个类或者对象可以实现一个或多个接口
-
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性
interface MyInterface { var name:String //name 属性, 抽象的 fun bar() // 未实现 fun foo() { //已实现 // 可选的方法体 println("foo") } } class Child : MyInterface { override var name: String = "runoob" //重写属性 override fun bar() { // 方法体 } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!