最近在学kotlin,这是本人看菜鸟教程后所做的笔记,很多会内容和菜鸟教程重复,仅供参考
基础语法
包声明
//类在包的位置
package com.demo.main
import java.util.*
函数定义
函数定义使用关键字 fun,参数格式为:参数 : 类型
fun printSum(a: Int,b: Int){
println(a+b)
}
无返回值的函数
public fun printSum(a: Int, b: Int) {
print(a + b)
}
可变长参数函数
fun vars(vararg a:String){
for(str in a){
println(str)
}
}
定义常量与变量
可变变量定义:var 关键字
var <标识符> : <类型> = <初始化值>
不可变变量定义:val关键字,只能赋值一次的变量Java中final修饰的变量)
val <标识符> : <类型> = <初始化值>
注释
// 这是一个单行注释
/* 这是一个多行的
块注释。 */
字符串模板
$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
数据类型
类型 | 位宽度 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
比较两个数字
在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小。
类型转换
由于不同的表示方式,较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能把 Byte 型值赋给一个 Int 变量。
val b: Byte = 10 // OK, 字面值是静态检测的
val i: Int = b // 错误
我们可以代用其toInt()方法。
val b: Byte = 1 // OK, 字面值是静态检测的
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
条件控制
IF 表达式
一个 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
When 表达式
when最简单的形势如下:
fun whenTest(a : Int){
when(a){
1->{
println(1)
}
2->{
println(2)
}
3->{
println(3)
}
else->{
println("其他")
}
}
}
when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
循环控制
For 循环
循环体可以是一个代码块:
for (item: Int in ints) {
// ……
}
while 与 do...while 循环
while是最基本的循环,它的结构为:
while( 布尔表达式 ) {
//循环内容
}
do…while 循环 对于 while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次。
do…while 循环和 while 循环相似,不同的是,do…while 循环至少会执行一次。
do {
//代码语句
}while(布尔表达式);
返回和跳转
Kotlin 有三种结构化跳转表达式:
- return。默认从最直接包围它的函数或者匿名函数 返回。
- break。终止最直接包围它的循环。
- continue。继续下一次最直接包围它的循环。
类和对象
类定义
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Runoob { // 类名为 Runoob
// 大括号内是类体构成
}
也可以定义一个空类:
class Empty
属性定义
类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
我们可以像使用普通函数那样使用构造函数创建类实例:
val site = Runoob() // Kotlin 中没有 new 关键字
要使用一个属性,只要用名称引用它即可
site.name // 使用 . 号来引用
site.url
主构造器
主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
次构造函数
类也可以有二级构造函数,需要加前缀 constructor:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
抽象类
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。
abstract class father{
abstract fun f()
}
class childs : father() {
override 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 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
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) // 内部类可以引用外部类的成员,例如:成员属性
}
匿名内部类
使用对象表达式来创建匿名内部类:
class Test {
var v = "成员属性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
类的修饰符
类的修饰符包括 classModifier 和accessModifier:
- classModifier: 类属性修饰符,标示类本身特性。
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类
- accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
继承
Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:
class Example // 从 Any 隐式继承
Any 默认提供了三个函数:
equals()
hashCode()
toString()
注意:Any 不是 java.lang.Object。
如果一个类要被继承,可以使用 open 关键字进行修饰。
open class father(p: Int) // 定义基类
class Derived(p: Int) : father(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}")
println("年龄: ${s.age}")
println("学生号: ${s.no}")
println("成绩: ${s.score}")
}
子类没有主构造函数
如果子类没有主构造函数,则必须在每一个二级构造函数中用 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("-------继承类次级构造函数---------")
println("学生名: ${name}")
println("年龄: ${age}")
println("学生号: ${no}")
println("成绩: ${score}")
}
}
接口
Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现:
interface MyInterface{
fun bar()
fun foo(){
println("foo")
}
}
class MyClass : MyInterface{
override fun bar() {
println("bar")
}
}
fun main(args:Array<String>){
var myClass=MyClass()
myClass.bar()
myClass.foo()
}
接口中的属性
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性:
interface MyInterface{
var name:String
}
class MyClass : MyInterface{
override var name="Jack"
}
fun main(args:Array<String>){
var myClass=MyClass()
println(myClass.name)
}
函数重写
实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如:
interface MyInterface2{
fun foo()
fun bar(){
println("bar2")
}
}
class MyClass : MyInterface,MyInterface2{
override fun bar() {
super<MyInterface2>.bar()
}
override fun foo() {
println("foo2")
}
}
fun main(args:Array<String>){
var myClass=MyClass()
myClass.foo()
myClass.bar()
}
当一个有实现方法,一个没有实现方法时,默认是没有实现方法的, super<MyInterface>.foo()切换实现方法,两个都有时也可以通过这个方式切换实现方法
泛型
泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。
与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
声明一个泛型类:
class Class<T>(t: T) {
var value = t
}
型变
Kotlin 中没有通配符类型,它有两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。
声明处的类型变异使用协变注解修饰符:in、out,消费者 in, 生产者 out。
使用 out 使得一个类型参数协变,协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:
in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:
class demo<out A>(val a: A) {
fun foo(): A {
return a
}
}
class demo2<in A>(a: A) {
fun foo(a: A) {
}
}
泛型约束
我们可以使用泛型约束来设定一个给定参数允许使用的类型。
Kotlin 中使用 : 对泛型的的类型上限进行约束。
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
枚举类
枚举类最基本的用法是实现一个类型安全的枚举。
枚举常量用逗号分隔,每个枚举常量都是一个对象。
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
fun main(args: Array<String>) {
var color:Color=Color.BLUE
println(Color.values())
println(Color.valueOf("RED"))
println(color.name)
println(color.ordinal)
}
对象表达式和对象声明
对象可以继承于某个基类,或者实现其他接口:
open class A(x: Int) {
public open val y: Int = x
}
interface B {
}
fun main(args:Array<String>){
val ab: A = object : A(1), B {
override val y = 15
}
println(ab.y)
}
通过对象表达式可以越过类的定义直接得到一个对象:
val site = object {
var name: String = "Jack"
}
println(site.name)
匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象 中添加的成员将无法访问。
class A {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun Foo2() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
对象声明
Kotlin 使用 object 关键字来声明一个对象。
object Test{
fun foo(){
println("Jack")
}
}
fun main(args:Array<String>){
Test.foo()
}
当然你也可以定义一个变量来获取获取这个对象,当时当你定义两个不同的变量来获取这个对象时,你会发现你并不能得到两个不同的变量。也就是说通过这种方式,我们获得一个单例。
object My {
var name: String = "JACK"
}
fun main(args:Array<String>){
var s1 = My
var s2 = My
s1.name = "HelloJack"
println(s1.name)
println(s2.name)
}
伴生对象
类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
fun test(){
println("Jack")
}
}
fun main(args:Array<String>){
val instance = MyClass.create()
instance.test()
}
我们可以省略掉该对象的对象名,然后使用 Companion 替代需要声明的对象名:
class MyClass {
companion object {
}
}
val x = MyClass.Companion
注意:一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。
类委托
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
// 创建接口
interface Base {
fun print()
}
// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b
fun main(args:Array<String>){
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
val MyLazyValue: String by lazy {
println("computed!") // 第一次调用输出,第二次调用不执行
"Hello"
}
fun main(args:Array<String>){
println(MyLazyValue)
println(MyLazyValue)
}
一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他"动态"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
class Data(val map: Map<String, Any?>) {
val name: String by map
val url: String by map
}
fun main(args:Array<String>){
val site = Data(mapOf(
"name" to "Jack1",
"url" to "Jack2"
))
// 读取映射值
println(site.name)
println(site.url)
}