Kotlin基础语法< 一 >

2020-01-20  本文已影响0人  安仔夏天勤奋

使用 Kotlin 可以编写出简洁高效的代码, 同时又可以完全兼容既有的 Java 技术栈(Java-based technology stacks)。初学Kotlin,为学习而总结所学知识,也是为日后温习提供便利。

extends 和 implements

Java的写法 \color{red}{VS} Kotlin的写法

//java
class A  extends  B  implements C{}
//kotlin
class MainActivity : AppCompatActivity(),Runnable{}

\color{red}{注意:} 继承类时要带上括号,接口就不用括号。kotlin自定义的类默认是final的不可继承。如果需要继承,必须添加open或abstract关键字修饰。

open class BaseActivity : AppCompatActivity(){}

属性、方法的继承也需要用open或abstract关键字修饰。子类重写父类的属性、方法时需要用override修饰。代码示例:

abstract class BaseActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(getLayoutId())
        initView()
        initData()
    }
    abstract fun getLayoutId():Int
    abstract fun initView()
    abstract fun initData()
}
class HomeActivity : BaseActivity(){
    override fun getLayoutId(): Int {
        return R.layout.activity_main
    }
    override fun initView() {
    }
    override fun initData() {
    }
}

定义变量

val | var 变量名:类型 = 初始值
val修饰的变量为只读变量 ,要有初始值。如果变量值不需要改变尽量使用val。
var修改的变量为可读可写。

fun main(){
    val a:Int = 0 //只可读(只提供get没提供set)  相当于java用final修饰的变量
    var b:Int = 1 //可读可写
    //a、b重新赋值
    a=10;//使用val初始化的值a再次赋值时编辑器(IDEA或者Android Studio)编译报错
    b=111;

    val c=10 //自动推断类型,静态语言
    var d =11
    d =1.4f //报错(不能再次推断出类型)

    val a1: Int = 1 // 立即赋值
    val b1= 2 // 变量类型自动推断为 `Int` 类型
    val c1:Int // 没有初始化语句时, 必须明确指定类型
    c1 = 3 // 延迟赋值
    println("a1 = $a1, b1 = $b1, c1 = $c1")
}

  在上述代码中,通过变量a、b对比可以看出,使用 val 初始化的值不可修改,使用 var 初始化的值可以修改,也就是说 val 是只读,而 var 可读可修改。可以把val看作常量,var看作变量,这样就很好理解了。

  通过c变量,可以了解到c变量没有为其定义修饰的类型也不会报错,因为在kotlin语言中可自动推断出类型;通过d变量,可以了解到,当变量d自动推断出类型后,再次改变其类型时,编译不通过,因为kotlin是一种静态语言写法,而静态语言,编译期推断出类型,只推断出一次类型。动态语言,运行时推断出类型,可再次推断出类型。

\color{red}{注意:}只读的局部变量使用关键字 val 来定义, 它们只能赋值一次。c1变量没有初始化语句时, 必须明确指定类型,其次在使用之前要赋值。如果c1赋值,println语句中的$c1就编译报错。

类的主构造器和次构造器

Kotlin 中的类可以有一个主构造器 (primary constructor),以及一个或多个次构造器 (secondary constructor)。主构造器是类头部的一部分, 位于类名称(以及可选的类型参数)之后。

class  ClassName constructor (形参){}

如果主构造器没有任何注解(annotation), 也没有任何可见度修饰符,那么 constructor 关键字可以省略。

class  ClassName (形参){}

一个类的次构造器写法

class  ClassName{
    constructor(形参){}
}

按照习惯会这样写:

class User{
    val id:Int
    val name:String
}

上述这样写是报错的,提示必须初始化。继续写,那就给一个初始化:

class User{
    val id:Int
    val name:String
    constructor(id:Int,name:String){
        this.id = id
        this.name = name
    }
}

写完了构造函数,也初始化了。constructor是次构造函数,那么存在次构造函数,就必存在主构造函数,继续代码:

class User(id:Int){//User()主构造函数
    //要区分 字段还是属性
    var id:Int = 0
    get() = 0
    set(value) {
        field = value //field是一个幕后字段
    }
    var name:String
    //有主构造函数 必须要init块里初始化
    init{
        this.id = id //id可以直接用主构造函数的参数id
    }
    //可以多个init代码块  但按从上到下的顺序执行
    init{
        name = ""
    }
    //次构造函数  如果有主构造函数,次构造函数必须this调用主构造函数
    constructor(id:Int,name:String):this(id){
        this.id = id
        this.name = name
    }
}

  如果一个类有主构造函数,那么参数初始化必须init块里初始化(init块可以有多个,但执行顺序是从上到下的顺序执行)。主构造器中不能包含任何代码。初始化代码可以放在 初始化代码段 (initializer block) 中,初始化代码段使用 init 关键字作为前缀。如果一个类中存在主次构造函数,那么次构造函数必须使用this调用主构造函数。
\color{red}{注意:}有主构造函数,必须要在init块里初始化变量。如果类中存在主次构造函数,那么次构造函数必须this调用主构造函数。
\color{red}{注意:}多个init块时,都是按从上到下的顺序执行init块进行初始化。

子类的主构造器和次构造器

一个子类的主构造器写法:class SubClass:BaseClass(形参){}
一个子类的次构造器:
1、子构造器使用:this(参数)显示调用本类的重载构造器,最终还是调用父构造器。
2、子构造器使用:super(参数)
3、子构造器既没有使用super(),也没有使用:this(),那么系统将会隐式调用父类无参构造器
super限定:
1、在子类中重写了父类的方法或属性,则即可使用super限定调用父类的。
2、如果子类从多个或直接超类型(类或接口),继承了同名的成员,那么要求子类重写该成员super<T>。

区分字段还是属性

两种幕后字段的用法

class Animal(age:Int,name: String){
    //要区分 字段还是属性
    var age:Int = 0 //是一个属性 相当于java的字段+get方法+set方法
    get() = field
    set(value) {
       field = value //field是一个幕后字段
     }
    var name:String=""
}

class BackingProperty(name:String){
    private var _name : String = name //幕后字段 私有的字段不对外开放  状态保存在幕后字段里
    var name //是一个属性  提供外面调用  不保存状态
    get() = _name
    set(value) {
        _name = value
    }
}

fun main{
    var animal = Animal(1,"dog")
    println(animal.age)
    animal.age = 11
    println(animal.age)
    println(animal.name)
    
    var p = BackingProperty("ok")
    println(p.name)
    p.name = "hao"
    println(p.name)
}

  要区分字段还是属性:如Animal类中的age他是一个属性,set函数体中的field才是一个幕后字段。如果换成BackingProperty类中,因为_name是一个private修饰的变量,是一个幕后字段,并不对外开放的。
\color{red}{注意:}属性是提供外面调用的,并不保存状态;幕后字段不对外开放,并保存状态。

lateinit延迟属性

class Person{
    lateinit var name :String //name 交给我自己管理
    var age:Int = 0  
    lateinit var sex:String
}

fun main(){
    var person = Person()
    person.name ="la"
    person.age = 18
    person.sex ="man"
}

注意:8种基本类型(java 8种基本类型)不能用lateinit

lateinit 修饰的变量,到底是不是初始化了?

class Person{
    lateinit var name :String //name 交给我自己管理
    var age:Int = 0  
}

fun main(){
    val person = Person()
    println(person.name)
}

上述代码运行时,报未初始化异常,所以lateinit修饰的变量是未初始化的。代码再改动一下

class Person{
    lateinit var name :String //name 交给我自己管理
    var age:Int = 0  
}

fun main(){
    val person = Person()
   if(person.name !=null){//报错
     println(person.name)       
   }
    
}

上述代码if(person.name !=null)都无法用来作判断,这里的name是不是初始化了这个状态,这个状态并不是name自己的状态或属性,kotlin官方提供了一个查看lateinit修饰的变量是否初始化了,代码如下:

class Person{
    lateinit var name:String
    var age:Int =0
    
    fun inNameInit():Boolean{
        return ::name.isInitialized //isInitialized 这个是kotlin的一个反射属性  用来判断是否初始化
    }
}

fun main(){
   val person = Person()
   if(personinNameInit()){//正确使用
     println(person.name)       
   }
}

lateinit 我们可以在可以修饰任意参数,可以是 var 、 val 的,我们在声明成员变量时可以不指定具体数值,但是lateinit修饰的参数必须在合适的地方初始化,否则编译不会通过。

函数定义

函数定义的格式如:
fun 函数名(形参列表):返回值类型{
//执行的语句
}
下面通过代码了解函数的定义和使用。

fun fun1(name:String):String{
    return "我的名字是:${name}"
}
/**
 * Unit 函数不返回有意义的结果  Unit可理解为java的void
 * 但也可以用一个变量接收,但这个值为kotlin.Unit
 */
fun fun2(name:String):Unit{
    println("我的名字是:${name}")
}

fun fun3(name:String){ // 返回值为 Unit 类型时, 可以省略。
    println("我的名字是:${name}")
}

//使用表达式语句作为函数体, 返回类型由自动推断决定。(如果函数体只有一行)
fun sum(a:Int,b:Int) = a + b

fun main(){
    val name = fun1("kotlin")
    println(name)
    val name2= fun2("name")
    println(name2)
    println("a+b:${sum(10,20)}")
}

fun1函数:接收一个String类型参数,返回String类型的结果。
fun2函数:接收一个String类型参数,返回Unit类型的结果。\color{red}{(Unit,这是返回一个无意义的结果)}
fun3函数:和函数fun2函数的作用是一样的,返回值为 Unit 类型时, 可以省略。
sum函数:使用表达式语句作为函数体, 返回类型由自动推断决定。\color{red}{(一般函数体只有一行才这样写的)}

null安全(空安全)

空安全的几种简单用法

定义一下参数是可空类型的打印函数

fun strDisplay(str:String?){//str参数是可空类型
    //用法一
    if(str !=null)
    println("str的长度:${str.length}")

    //用法二  如果str为null运行时报错   !!为断言运算符(非空断言)
    println("str的长度:${str!!.length}")

    //使用三 如果str为null运行时不报错 输出str为null
    println("str的长度:${str?.length}")
}

fun main(){
    var pStr:String? //String? 可空的字符串
    pStr = null
    strDisplay(pStr)
    pStr = "66666666"
    strDisplay(pStr)
}

\color{red}{注意} java变量都有默认值吗?答案:否。 全局变量才有默认值;局部变量是没有默认值的。Kotlin所有变量都没有默认值。

var orderNo:String? =null
fun main(){
    if(orderNo !=null){
        println(orderNo.length)//还是报错
    }
}

上述代码即使orderNo作了一层判空处理,orderNo.length还是无法通过空安全,因为orderNo作为全局变量,有可能其他地方异步修改orderNo值,所以不能确定orderNo值一定不为null,可改为println(orderNo?.length)。如果var orderNo:String? =null 替换为val orderNo:String? =null 那么上述代码就是安全的。

类的根的归属

String? 的根是Any? (Any?相当于java的Object)
String 的根是Any,Any是继续于Any?
所以String?和String的类型是不相同的。

Int 不等于 Int?
Int 相当于java基本类型的int , Int?相当于java包装类型Integer

字符串模版

字符串模版用${ } 表示

fun main(){
    val privce  = 23
    var str ="肉价:${privce}"
    println(str)
        
    val name = "kotlin"
    var str1 = "name:$name , price:$price"
    println(str1) 
}

字符串换行 """ """

fun main(){
    val str2 = """
        66666
        居然是换行
        原来是按原样执行
    """.trimIndent()
    println(str2)
}

字符串换行顺序是按原样行,一行行执行。

上一篇 下一篇

猜你喜欢

热点阅读