从定义UserBean说kotlin基础

185 阅读10分钟

0.前言

文章讲述属性,空安全设计,构造函数,继承,函数几个方面的基础使用

1.属性

语法总结

java中声明属性的语法是:访问修饰符+类型+属性名。 是不需要关键字的

kotlin中属性需要在初始化时赋值。如果像java一样定义:关键字+属性名+类型,是会报错的,压根就是个错误语法。

声明一个属性的完整语法如下 被“[]”标记的部分是可以省略的

  1. var 关键字
  2. propertyName 属性名
  3. PropertyType 类型。可以从属性值和getter方法中推断出来,可以省略
  4. property_initializer 属性值。
    1. 上面提到过直接省略属性值会报错,结合关键字 lateinit 可以延迟初始化。
  5. ,
    1. 与java中不同的是,kotlin的getter,setter方法是属性的一部分,而不是脱离属性之外单独定义的方法
    2. 当开发人员声音一个变量时,实际上定义了三个东西:私有属性,getter,setter
    3. getter,setter方法默认存在,只是被省略
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

声明属性

属性需要在初始化时赋值。如果像java一样定义:关键字+属性名+类型,是会报错的,压根就是个错误语法

  1. var可变属性
  2. val只读属性

type = "" 无法赋值 会报错。val 关键字 等于 java中对变量使用 final。只能在声明是添加默认值或者是在构造函数中赋值

class KUserBean {
    var name: String = ""
    var age: Int = 0
    val type = "人"
    private fun test() {
        name = "啦啦啦"
        age = 12
        type = ""
    }
}

关键字 lateinit

声明一个全局变量,稍后在某个方法中进行初始化是很常见的操作,声明变量就要赋默认值太麻烦了。

使用关键字 lateinit 后,变量声明的使用习惯就和java一样了。

定义全局变量,在某个方法中进行赋值,不需要在声明的时候添加默认值。

如果在使用属性之前未进行初始化则会抛出异常,通过语句 ::name.*isInitialized* 返回布尔值:true已初始化,false未初始。可以判断属性是否已经初始化

表达式 ::x获取 KProperty<x>类型对象。KProperty 是kotlin反射框架中提供的对象

class KUserBean {
    private lateinit var name:String
    private fun test() {
        if (!::name.isInitialized){
            name = "啦啦啦"
        }
        Log.d("KUserBean","name:${name}")
    }
}

getter,setter

  1. 未省略情况下,一个完整的属性声明。默认情况下getter,setter是被省略的,当需要自定义getter,setter方法时才需要重写。field 指代当前属性值
class KUserBean {
    var name:String = ""
        get() {
            return field
        }
        set(value) {
            field = value
        }
}
  1. val关键字声明的只读变量只有getter,没有setter
class KUserBean {
    val name:String ="啦啦啦"
    get() {
//        field = "" 不能修改 会报错
				//可定义其他逻辑
        val test= field +"嘻嘻嘻"
        return test
    }
}
  1. 如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现
class KUserBean {
    var name:String ="啦啦啦"
        private set
    var age:Int = 0
       @NonNull set
}
  1. getter可以设置注解,不能设置访问修饰符,getter 总是与属性有着相同的可见性,乍一听感觉有点费解。为什么getter如此特殊?java中属性是属性,方法是方法 它们是不同的部分 只是人为的从逻辑上把它们联系在一起。而kotlin中getter属于属性,它们是一体的。所以getter 总是与属性有着相同的可见性(感觉没说清楚,又不知道咋说了)

@JvmField

kotlin 和 java 可以互相调用。 kotlin使用var关键字声明的属性默认生成getter,setter。

如果在java中不想调用getter,setter。想直接调用属性名进行操作,就需要利用注解@JvmField

截图中可以看出,age属性没有被注解标记仍然需要通过getter,setter调用。

//kotlin 
class KUserBean {
    @JvmField
    var name:String ="啦啦啦"
    var age:Int = 0
}

Untitled.png

空安全设计

kotlin把变量 分为可空类型 和 不可空类型。

举例:声明一个String变量。 var name = ""

  1. 如果添加 = null。 那么只有 null 会报错。 提示:Null can not be a value of a non-null type String 不能把null 赋值给 不可空类型的String
  2. 把变量 定义为 可空类型的语法是 “?=” var name:String ?= null
  3. 可空变量的使用有两种方法
    1. “?.” 相当于 在java 使用 if 判断非null

    2. “!!” 断言 表示判断对象肯定不为空 ****相当于java 不加判断的直接调用 如果对象为null 直接抛出异常

    3. 如下截图name是可空类型变量,type是不可能空变量。分别调用length,通过AndroidStudio的提示也可以看出,使用**“?.”访问name的length属性,变量size1的类型是“Int?”。通过“!!”访问length 和 type访问length一致是”Int”**

Untitled 1.png

  1. java中也有类似的操作 通过注解将变量区分为 @Nullable可空 @NonNull非空。 但是在使用时 AndroidStudio 只有警告提示,能够正常编译运行。如果代码有问题,编译期是不知道的,运行时才会抛出异常

  2. 虽说java可以通过注解定义 @Nullable可空 @NonNull非空变量,但几乎没人用过 或者 干脆就不知道有这个操作

  3. kotlin为了彻底解决空指针异常 在声明变量的阶段就进行限制 如果开发人员 不注意空类型 直接报错

  4. 构造函数

kotlin中的构造函数也比java来的复杂。kotlin中存在主次构造函数的概念,刚刚上手也是容易懵逼的。

  1. 类结构:类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成
    1. class 声明类关键字
    2. KUserBean 类名
    3. constructor() 构造函数 括号中定义参数
    4. 默认情况与java相同是无参构造,关键字constructor 和() 都被省略 所以我们日常看见的类就是这样的class KUserBean{}
    5. 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的 不能省略
class KUserBean private constructor(){
}
  1. 主构造函数固定跟在类名后面。即便开发人员不想写在类名后面,用java的方式声明构造函数。AndroidStudio也会给予提示如截图所示:转换为主构造函数

Untitled 2.png

  1. 主构造函数没有代码块,初始化的代码会放到 init关键字 作为前缀的初始化块 如下。当属性在构造函数中赋值,也不需要赋默认值了。
class KUserBean(nameParams: String, ageParams: Int) {
    var name: String
    var age: Int

    init {
        Log.d("KUserBean", "主构造")
        name = nameParams
        age = ageParams
    }
}
  1. 在类体中通过constructor 关键字声明的构造函数叫做次构造函数
    1. 没有声明主构造函数时,次构造函数可以随意声明
    2. 当类头中已经声明主构造函数,每个次构造函数都需要委托实现主构造函数
    3. 因为主构造函数已经声明两个参数,想要声明无参的次构造函数就只能写死静态值。所以感觉如非必要不用实现主构造参数
    4. 调用顺序是:先调用主构造,后调用次构造。如果有继承存在,则是先调用父类构造,后调用子类构造
class KUserBean(nameParams: String, ageParams: Int) {
    var name: String
    var age: Int

    init {
        Log.d("KUserBean", "主构造")
        name = nameParams
        age = ageParams
    }
    
    constructor():this("嘻嘻嘻",12){

    }
    constructor(name:String,age: Int,type:Int):this(name,age){

    }
}

3.继承

  1. 在 Kotlin 中所有类都有一个共同的超类 Any 等于java的object
  2. 默认情况下kotlin的类不能被继承,相当于java中被final修饰的类。要使一个类可继承,需要使用open关键字 比如:open class Person{}
  3. 子类继承父类 没有关键字 class KUserBean:Person()
    1. 实现接口也没有关键字 类头冒号后有多个类或接口使用逗号分隔
    2. class KUserBean:Person(),View.OnClickListener

4.自定义View继承与构造函数使用举例

情况1:

  1. class MyTextView 创建MyTextView类,无主构造函数,也可以理解为默认无参
  2. : AppCompatTextView() 继承AppCompatTextView 并调用AppCompatTextView的无参构造,实际上AppCompatTextView并没有无参构造,AndroidStudio会提示报错
  3. 继承时: AppCompatTextView() 基类的括号并不是固定语法,表明调用基类的无参构造
class MyTextView : AppCompatTextView() {

}

情况2:

  1. MyTextVie声明主构造,AppCompatTextView使用主构造参数初始化
  2. 用java的思想,想要声明此构造函数实现AppCompatTextView其他的构造函数,但次构造函数调用super的时候报错了。
  3. 报错的原因在于对kotlin理解的不透彻,用java的老想法写kotlin代码,原因如下:
    1. 派生类(MyTextView)在存在主构造函数的情况下,基类(AppCompatTextView)必须调用主构造参数立即初始化
    2. 次构造函数必须调用主构造初始化,如果此构造后面调用基类构造,那么次构造就不是次。它能脱离主构造函数独立的初始化对象。
    3. kotlin规定,如果存在主构造函数,那么它就是大哥,优先级最高。对象的初始化不能绕过主构造函数。
  4. 所以如下写法是错误的,也是不可取的。
class MyTextView(context: Context) : AppCompatTextView(context) {
   constructor(context: Context,attr: AttributeSet?):super(context,attr){
    }
}

情况3:

  1. 只声明次构造函数,彼此之间是平级,可以随意调用基类构造函数
class MyTextView : AppCompatTextView {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )
}
  1. 或者调用另一个构造函数
class MyTextView : AppCompatTextView {
    constructor(context: Context) : this(context,null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs,0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )
}

情况4:

  1. 使用注解@JvmOverloads 作用:在有默认参数值的方法中使用@JvmOverloads注解,Kotlin就会暴露多个重载方法。
  2. 所以仅仅声明一个了具有三个参数的主构造,却实现了和情况3声明了三个次构造一样的效果
class MyTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet?=null, defStyleAttr: Int = 0) 
    : AppCompatTextView(context, attrs, defStyleAttr) {

}

//activity中通过代码声明
val textView1 = MyTextView(this)
val textView2 = MyTextView(this,null)
val textView3 = MyTextView(this,null,0)

总结:

  1. 如果存在主构造函数,优先级最高。对象的初始化不能绕过主构造函数
  2. 如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化
  3. 如果派生类没有主构造函数,那么每个次构造函数必须使用 super关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数

5.函数

  1. 声明一个函数使用关键字fun
    1. 返回值跟在参数()后面
    2. Unit 表示当前参数无返回值和java中void关键字的作用一致。但是Unit不是关键字而是一种类型,只有唯一一个值“Unit”。
    3. Unit 可以省略不写
fun test():Unit{
     //...
}

fun test(){

}
  1. 参数
    1. 函数参数名称和类型类型通过冒号隔开 name:type
    2. kotlin的函数参数,可以指定默认值
    3. 有默认值的情况下,调用函数可以指定给哪一个函数赋值

Untitled 3.png

  1. 单表达式函数
    1. java中if语句如果括号内只有一条语句可以省略括号。
      1. if (kUserBean == null) kUserBean = new KUserBean();
    2. kotlin中的函数 也存在一条语句可以省略的写法。
      1. 省略花括号,在等号之后指定代码体
      2. kotlin存在类型推断,返回值类型也可以省略
    //省略写法
    fun double(x: Int) = x * 2
    //正常写法
    fun double(x: Int): Int {
       return x * 2
    }