Kotlin学习系列(一):基础

1,545 阅读9分钟

概述

现在Kotlin很多开源项目都已经开始用了,再不学习就看不懂开源了,开始学起

变量定义

可变变量定义

var 变量名 : 变量类型 = 变量值

var name :String = "小明"

//相当于java

String name = "小明"

var name = "小明" //编译器自动推到类型为String


//注意这种写法,只能是局部变量
var aa : String //先定义不初始化,必须要有类型
aa = "aa"


不可变变量定义

val 变量名 :变量类型 = 变量值
val name :String = "小红"

相当于java
final String name = "小红"

函数定义

函数定义的关键字为fun,参数格式为 值 :类型,

整体格式为

fun 方法名(参数名 :参数类型 ...) : 返回值类型{

    方法体

}

fun sum(a: Int, b: Int): Int {
   return a+b
}

表达式作为函数体,返回类型自动推断

fun sum1 (a : Int, b : Int) = a+b

无返回值的函数,类似java中的void

fun printMsg (msg : Int):Unit {
    print(msg)
}

//无返回值可省略
fun printMsg1 (msg : Int) {
    print(msg)
}

可变长参数

fun aa (vararg a: Int){
    for (v in a){
        print(v)
    }
}

//调用
fun  main(){
    aa(1,2,3,4,5)
}

lambda(匿名函数)

fun bb() {
    val sunLambda: (a: Int, b: Int) -> Int = { x, y -> x + y }
    print(sunLambda(1, 2))
}


//调用
fun main() {
    bb()
}

这个后续会详细鞋

字符串模板

这里新增一个操作符 $ ,$变量名 表示取这个变量的值 ${方法} 表示取方法的返回值,如下:

var a =1

var ss = "value = $a"

var sss = "value =  ${sum(3,4)} "

输出

value = 1
value = 7

NULL检查机制

kotlin 增加了空安全,再java中如果传递一个参数,我们需要判断是否为空,不然就会报空指针异常NullPointerException,比如:

public void text(String msg){
        if (msg!=null){
            char c = msg.charAt(0);
        }
    }

kotlin中增加了空安全

//这样定义的变量默认就是非空的
var a1 : String = "123"
//给这个变量直接赋值null,会报错
var a1 : String = null
//在类型后加上?操作符,表示这个变量可为空,赋值null不会报错
var a2 :String?  = null

空安全如何再代码中调用

//默认情况下表示非空,可以任意调用变量的方法
var a1 : String = "123"
a1.length
//可为空的情况下,直接调用变量方法会报错
var a2 :String?  = "123"
a2.length//编译报错

可为空的变量有一下几种调用方式

  • 显示的再代码中判读
      val b: String? = "Kotlin"
      if (b != null && b.length > 0) {
          print("String of length ${b.length}")
      } else {
          print("Empty string")
      }
    
  • 使用?.操作符
      //如果 a2 不为空则返回a2 的长度,如果为空 则返回null,不会抛出空指针异常
      var length :Int? = a2?.length
    
  • 使用!!操作符
    //如果不为空则正常返回,如果为空则抛出空指针异常
    var length1 : Int = a2!!.length
    
  • Elvis 操作符
      //如果a2不为空则走?:左边,如果为空 则走?:右边
      var length2: Int = a2?.length ?: -1
    

类型检测及自动类型转换

我们可以用is操作符来判断类型 相当于java中的instanceof

 if (aa is String){
        //此时已经自动转换类型为String
        println(aa.length)
    }
//或者
 if (aa !is String){
        return
    }
    //这里会自动转换String
    println(aa.length)

区间

kotlin可以通过kotlin.ranges包中的rangeTo() 函数及其操作符形式的..创建俩个值的区间,通常rangeTo会辅助以 in和!in

   var i= 1
    if (i in 1..4){
        //相当于 1<=i<=4
        println(i)
    }

    for (j in 1..4 ){
        //输出1,2,3,4
        println(j)
    }

反向迭代数字

    for (k in 4 downTo 1){
        //输出4,3,2,1
        println(k)
    }

自定义迭代步长

//每俩次输出一次
 for (l in 1..5 step 2){
        //输出1,3,5
        println(l)
    }

迭代不包含结束区间

for (g in 1 until 4){
        //输出1,2,3不包含4
        println(g)
    }

基本数据类型

这里只记录和java不同的地方

类型转换

由于不同的表达方式,较小类型并不是较大类型的子类,所以不能隐式转换

var d : Byte = 1

//这种会报错
var e :Int =d

//可以通过这种来进行转换
var f :Int =d.toInt()

一些常用的转换方法

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

有些情况也是可以自动转化的

var l = 1L+4 //Long + Int  =》Long

位操作符

对于位运算没有使用特殊字符来表示,而只可以使用中缀方式调用具名函数,例如

val x = (1 shl 2) and 0x000FF000

这是完整的位运算列表(只适用于 Int Long)

shl(bits) – 有符号左移
shr(bits) – 有符号右移
ushr(bits) – 无符号右移
and(bits) – 位与
or(bits) – 位或
xor(bits) – 位异或
inv() – 位非

数组

在Kotlin中 数组用Array来表示,它定义了get个set函数(这个会转变成[]),以及size属性

    //定义数组[1,2,3]
    var array  = arrayOf(1,2.3)


    //定义数组 [0,2,4]
    var array1 = Array(3,{i->i*2})

    //创建一个容量为6的Int 空数组
    var arrayOfNulls = arrayOfNulls<Int>(6)

    array.forEach {
        it->
        println(it)
    }

    array1.forEach {
        println(it)
    }

    arrayOfNulls.forEach {
        println(it)
    }

kotlin中的数组是不型变的,也就是说我们不可以把Array<String> 赋值给 Array<Any>以防止可能的运行时失败(但是你可以使用 Array, 参见类型投影,泛型)。

原生类型数组

Ktlion也有无装箱开销的专门来表示基本数据类型的数组,ByteArray、 ShortArray、IntArra,这些跟Array类并没有继承关系,但他们有相同的方法集,其用法和Array一样

字符串

kotlin支持多行字符串,"""表示

//这样输出会有空格
var text1 = """
        多行字符串
        多行字符串
        多行字符串
        """

//可以用trimMargin去除空格,|作为边界前缀
var text1 = """
       | 多行字符串
       |多行字符串
       | 多行字符串
        """.trimMargin()

输出

        多行字符串
        多行字符串
        多行字符串
        
 多行字符串
 多行字符串
 多行字符串

字符串模板

字符串字面值可以包含模板表达式,就是一些小代码,会求值并合并到字符串中,模板表达式可以用$开头

val i = 10
println("i = $i") // 输出“i = 10”
val s = "abc"
println("$s.length is ${s.length}") // 输出“abc.length is 3”

原始字符串和转移字符串都支持模板,如果你需要再模板字符串中使用$符号,可以用一下语法

val price = """
${'$'}9.99
"""

条件控制

If表达式

  //普通使用
    if (a > b) max = a
    
    //带有else使用
    if (a > b) {
        max = a
    } else {
        max = b
    }
    
    //if表达式返回值返回值
    var min  = if (a>b) b else a

基本用法和java差不多,但是需要注意的是,kotlin的If表达式是有返回值的的,可以看第三个例子,也就是说可以替换java中的三元表达式

使用区间

if (a in 0..3){
        println(a)
    }

可以判断a变量是否再0..3区间内

when

这个是正常的使用,相当于java中的switch,else相当于defult

 when (a) {
        1 -> println("输入1")
        2 -> println("输入2")
        else -> println("不是12")
    }

也可以多个条件放在一个分支

 when(a){
       1,2-> println("输入12")
       else-> println("不是12") 
    }

也可以判断是否在区间或者集合中

  var items = setOf<Int>(1, 2, 11)
    when(a){
        in 0..10 -> println("在区间0-10内")
        in items-> println("在集合内")
        else-> println("不在区间内")
    }

可以判断类型,有返回值,可以作为方法的返回值

fun text(a : Any)= when(a){
    is String -> true
    else -> false
}

也可以替换简单的if表达式

when {
    x.isOdd() -> print("x is odd")
    y.isEven() -> print("y is even")
    else -> print("x+y is odd.")
}

For循环

for循环可以对任何提供迭代器的(iterator)对象进行遍历,语法如下

for (item in collection) print(item)

for 可以循环遍历任何提供了迭代器的对象。即:

  • 有一个成员函数或者扩展函数 iterator(),它的返回类型
  • 有一个成员函数或者扩展函数 next(),并且
  • 有一个成员函数或者扩展函数 hasNext() 返回 Boolean。

普通的遍历

 for (a in array){
        println(a)
    }

通过索引遍历

for (a in array.indices){
        println(array[a])
    }

使用withIndex遍历

for ((index,value) in array.withIndex()){
        println("the element at $index is $value")
    }

遍历区间

  for (a in 1..4){
        println(a)
    }

    for (a in 6 downTo 0 step 2){
        println(a)
    }

while 循环和java一样

返回和跳转

标签

标签的作用就是,可以指定跳转的位置,可以结合break continue,return来自定义跳转位置

定义标签

  • 声明标签:loop@
  • 使用标签:@loop

结合 continue使用

fun text1() {
    for (i in 1..3) {
        for (j in 1..3) {
            if (i == 2 && j == 2) {
                continue
            }
            println("$i -> $j")
        }
    }
}

输出

1 -> 1
1 -> 2
1 -> 3
2 -> 1
2 -> 3
3 -> 1
3 -> 2
3 -> 3

加上标签改造

fun text2() {
    look@ for (i in 1..3) {
        for (j in 1..3) {
            if (i == 2 && j == 2) {
                continue@look
            }
            println("$i -> $j")
        }
    }
}

输出

1 -> 1
1 -> 2
1 -> 3
2 -> 1
3 -> 1
3 -> 2
3 -> 3

不加标签跳出了本次内部循环,自行标签后跳出了本次的最外部循环

结合break使用

改造前

fun text3 (){
    for (i in 1..3) {
        for (j in 1..3) {
            if (i == 2 && j == 2) {
                break
            }
            println("$i -> $j")
        }
    }
}

输出

1 -> 1
1 -> 2
1 -> 3
2 -> 1
3 -> 1
3 -> 2
3 -> 3

改造后

1 -> 1
1 -> 2
1 -> 3
2 -> 1

改造前跳出内部循环,改造后跳出外部煦暖

结合return使用

没有改造之前

fun text5() {
    var arr = arrayOf(1, 2, 3, 0, 4, 5, 6)

    arr.forEach {
        if (it == 0) return
        println(it)
    }
}

输出

1
2
3

改造之后

fun text6() {
    var arr = arrayOf(1, 2, 3, 0, 4, 5, 6)

    arr.forEach hhh@{
        if (it == 0) return@hhh
        println(it)
    }
}

//隐士式法
fun text7() {
    var arr = arrayOf(1, 2, 3, 0, 4, 5, 6)

    arr.forEach {
        if (it == 0) return@forEach
        println(it)
    }
}

// Lambda 法 -> 相当于递归
fun text8(){
    var arr = arrayOf(1, 2, 3, 0, 4, 5, 6)

    arr.forEach(fun(value: Int) {
        if (value == 0) return
        println(value)
    })
}

输出

1
2
3
4
5
6

没改造之前直接退出方法,改造之后,退出然后继续循环遍历

类和对象

这里只说和java不太一样的地方

构造函数

kotlin类具有俩个构造函数,一个主构造函数,一个次构造函数

主构造函数

主构造函数是类头的一部分他跟在类名后,构造函数的关键字为constructor

class Person constructor(firstName: String) { /*……*/ }

如果主构造函数没有任何的注解,或者可见修饰符,可以省略这个constructor关键字

class Person(firstName: String) { /*……*/ }

主构造函数不能包含任何的代码快,初始化代码可以放到init关键字的代码块中进行初始化,主构造函数的参数可以再init代码块中使用,也可以应用于类的成员属性的赋值

//带有关键字constructor
class A constructor(a: String) {
    var a: String = "shsh"
    val b: String = "ccc"
    var c: String
    val d: String

    //初始化代码块,可以初始化主构造函数
    init {
        c = a
        d = a
        println(a)
    }

    fun text(a :String) {
        println(a)
    }
}

//不带有关键字constructor
class B(b : String){
    var lastName :String  = "hahha"
        get() {
            if (field.equals("hahha")){
                field="333"
            }
            return field
        }
        set(value) {
            if (value.equals("111")){
                    field="222"
            }else{
                field=value
            }
        }

    var hah :String  = "222"
    init {
        println(b)
    }

//注释1
     fun text(){
        println(b)//报错,注意这里访问不到主构造函数参数,报错
    }
}

这里重点看下注释1,方法里访问不到主构造函数的参数,如何可以访问呢?,下面这俩种都可以访问

class C(c: String) {
    var c1 = c;

    fun text(){
        println(c1)
    }
}

class  D(var d :String){
    fun text(){
        println(d)
    }
}

如果主构造函数有修饰符或者注解那么关键字不能隐藏

class Customer public @Inject constructor(name: String) { /*……*/ }

次构造函数

类也可以声明前缀有 constructor关键字的次构造函数

class E {

    constructor( a1111: String, b: Int) {
            println(a1111+b)
    }
}

如果类有一个主构造函数,每一个次构造函数都需要委托给主构造函数,可以直接委托,后者通过其他构造函数间接委托,委托到同一个类的另一个构造函数,需要用到this关键字

class F(a: String) {

    constructor(a: String, b: Int) : this(a) {

    }
    
    constructor(a:String,b: Int,c:Int):this(a,b){
        
    }
}

需要注意的是init代码块会在次构造函数之前执行

如果一个非抽象类没有任何的主次构造函数,会自动生成一个没有参数的主构造函数,构造函数是public,如果你不希望有一个公有的构造函数,可以主动创建一个构造函数,自己定义范围

class DontCreateMe private constructor () { /*……*/ }

创建类的实例

和java的区别是,没有new关键字

class F(a: String) {

    constructor(a: String, b: Int) : this(a) {

    }

    constructor(a:String,b: Int,c:Int):this(a,b){

    }
}

fun main() {

    //没有new关键字
   var a  = F("11",1)
}

属性的Getters 与 Setters

声明一个属性的完整语法是

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

其实就是对应于java的get和set方法

public class EE {
    
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

这个是java中的正常写法,kotlin中会自动生成get,set

class FF {
    var name: String = "name"

    var age: String = "age"
        get//默认实现
        set//默认实现

}

当然kotlin中也可以自定义get,set方法,其中filed字段表示本属性

class FF {
    var name: String = "name"
        get() = "不管你设置什么都返回这个"



    var age: String = "age"
        get() {
            if (field.equals("18")){
                field="18岁可真好"
            }
            return field
        }
}

fun  main(){
    var f = FF()


    println(f.age)
    f.age="18"
    println(f.age)

    println(f.name)
    f.name="xiaomign"
    println(f.name)

}

这个就是重写了get方法,当你使用属性值得时候就相当于调用了get方法,会返回你自定义内容

age
18岁可真好
不管你设置什么都返回这个
不管你设置什么都返回这个
class FF {
    var name: String = "name"
        //这个就是返回原值
        get() = field
        set(value) {
            if (value.equals("小明")){
                field="原来是小明啊"
            }
        }
}

fun  main(){
    var f = FF()

    

    println(f.name)
    f.name="小明"
    println(f.name)

}

这就是重写了set方法,当你给属性赋值就会自动调用set方法

输出

name
原来是小明啊

val属性相当于java中的final,不可改变,所以val属性默认没有set方法,因为他不可改变,我们也可以给get set加上访问修饰符privite,public

class C(c: String) {
    var c1 = c
        private set

    fun text(){
        println(c1)
    }
}

c1 属性的set是privite的,所以外部不能重新给c1赋值

编译期常量

//要位于顶层
const val aaa :String  = "aa"
//相当于java
public static final  String aaa = "aa"

延时初始化属性变量

普通变量都必须再构造函数中初始化,这种就很不方便,我们可以选择延时初始化,使用关键字lateinit

class C(c: String) {

    lateinit var abc: String

}

这样就可以延迟初始化

嵌套类

相当于java中的 static 内部类,他不依赖外部类的对象

class outer { //外部类
    var a: Int = 1

    class Nested { //嵌套类
        fun text() = 2
    }
}

fun main() {
    var a = outer.Nested().text() //调用方式

    println(a)
}

内部类

内部类用inner关键字来表示,内部类会带,带有外部类的引用,相当于java中的普通内部类

class outer { //外部类
    private var a111: Int = 1


    class Nested { //嵌套类
        fun text() = 2
    }

    inner class inner { //内部类
        fun text() = a111 //访问外部类成员变量

        fun text1() {
            var out = this@outer//拿到外部类对象
            println(out.a111)//访问外部类成员
        }
    }
}


fun main() {
    var a = outer.Nested().text() //嵌套类调用方式

    println(a)

    var b = outer().inner().text() //内部类调用方式
    var c = outer().inner().text1()
}

匿名内部类

interface Face {
    fun tetx() :String
}

class outer {
    fun text3 (face: Face){
        var a = face.tetx()
        println(a)
    }
}

fun main() {
    //使用
    outer().text3(object : Face{
        override fun tetx(): String {
            return "匿名内部类"
        }
    })
}

其实根java差不多

访问修饰符

类属性修饰符,为了标识类的本身特性

  • abstract //抽象类
  • final //类不可继承
  • enum //枚举类
  • open //类可继承。(类默认是final,不可继承)
  • annotation //注解类

访问权限修饰符,标识可以访问范围

  • public //所有地方都可访问

  • private //只有本类可以访问

  • protected //本类和子类可以访问(这个不能修饰类,可修饰成员变量,方法等)

  • internal //只有本模块可以访问