0.前言
文章讲述属性,空安全设计,构造函数,继承,函数几个方面的基础使用
1.属性
语法总结
java中声明属性的语法是:访问修饰符+类型+属性名。 是不需要关键字的
kotlin中属性需要在初始化时赋值。如果像java一样定义:关键字+属性名+类型,是会报错的,压根就是个错误语法。
声明一个属性的完整语法如下 被“[]”标记的部分是可以省略的
- var 关键字
- propertyName 属性名
- PropertyType 类型。可以从属性值和getter方法中推断出来,可以省略
- property_initializer 属性值。
- 上面提到过直接省略属性值会报错,结合关键字
lateinit可以延迟初始化。
- 上面提到过直接省略属性值会报错,结合关键字
- ,
- 与java中不同的是,kotlin的getter,setter方法是属性的一部分,而不是脱离属性之外单独定义的方法
- 当开发人员声音一个变量时,实际上定义了三个东西:私有属性,getter,setter
- getter,setter方法默认存在,只是被省略
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
声明属性
属性需要在初始化时赋值。如果像java一样定义:关键字+属性名+类型,是会报错的,压根就是个错误语法
- var可变属性
- 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
- 未省略情况下,一个完整的属性声明。默认情况下getter,setter是被省略的,当需要自定义getter,setter方法时才需要重写。
field指代当前属性值
class KUserBean {
var name:String = ""
get() {
return field
}
set(value) {
field = value
}
}
- val关键字声明的只读变量只有getter,没有setter
class KUserBean {
val name:String ="啦啦啦"
get() {
// field = "" 不能修改 会报错
//可定义其他逻辑
val test= field +"嘻嘻嘻"
return test
}
}
- 如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现
class KUserBean {
var name:String ="啦啦啦"
private set
var age:Int = 0
@NonNull set
}
- 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
}
空安全设计
kotlin把变量 分为可空类型 和 不可空类型。
举例:声明一个String变量。 var name = ""
- 如果添加 = null。 那么只有 null 会报错。 提示:Null can not be a value of a non-null type String 不能把null 赋值给 不可空类型的String
- 把变量 定义为 可空类型的语法是 “?=”
var name:String ?= null - 可空变量的使用有两种方法
-
“?.” 相当于 在java 使用 if 判断非null
-
“!!” 断言 表示判断对象肯定不为空 ****相当于java 不加判断的直接调用 如果对象为null 直接抛出异常
-
如下截图name是可空类型变量,type是不可能空变量。分别调用length,通过AndroidStudio的提示也可以看出,使用**“?.”访问name的length属性,变量size1的类型是“Int?”。通过“!!”访问length 和 type访问length一致是”Int”**
-
-
java中也有类似的操作 通过注解将变量区分为
@Nullable可空@NonNull非空。 但是在使用时 AndroidStudio 只有警告提示,能够正常编译运行。如果代码有问题,编译期是不知道的,运行时才会抛出异常 -
虽说java可以通过注解定义
@Nullable可空@NonNull非空变量,但几乎没人用过 或者 干脆就不知道有这个操作 -
kotlin为了彻底解决空指针异常 在声明变量的阶段就进行限制 如果开发人员 不注意空类型 直接报错
-
构造函数
kotlin中的构造函数也比java来的复杂。kotlin中存在主次构造函数的概念,刚刚上手也是容易懵逼的。
- 类结构:类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成
- class 声明类关键字
- KUserBean 类名
- constructor() 构造函数 括号中定义参数
- 默认情况与java相同是无参构造,关键字constructor 和() 都被省略 所以我们日常看见的类就是这样的
class KUserBean{} - 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的 不能省略
class KUserBean private constructor(){
}
- 主构造函数固定跟在类名后面。即便开发人员不想写在类名后面,用java的方式声明构造函数。AndroidStudio也会给予提示如截图所示:转换为主构造函数
- 主构造函数没有代码块,初始化的代码会放到 init关键字 作为前缀的初始化块 如下。当属性在构造函数中赋值,也不需要赋默认值了。
class KUserBean(nameParams: String, ageParams: Int) {
var name: String
var age: Int
init {
Log.d("KUserBean", "主构造")
name = nameParams
age = ageParams
}
}
- 在类体中通过
constructor关键字声明的构造函数叫做次构造函数- 没有声明主构造函数时,次构造函数可以随意声明
- 当类头中已经声明主构造函数,每个次构造函数都需要委托实现主构造函数
- 因为主构造函数已经声明两个参数,想要声明无参的次构造函数就只能写死静态值。所以感觉如非必要不用实现主构造参数
- 调用顺序是:先调用主构造,后调用次构造。如果有继承存在,则是先调用父类构造,后调用子类构造
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.继承
- 在 Kotlin 中所有类都有一个共同的超类
Any等于java的object - 默认情况下kotlin的类不能被继承,相当于java中被final修饰的类。要使一个类可继承,需要使用
open关键字 比如:open class Person{} - 子类继承父类 没有关键字
class KUserBean:Person()- 实现接口也没有关键字 类头冒号后有多个类或接口使用逗号分隔
class KUserBean:Person(),View.OnClickListener
4.自定义View继承与构造函数使用举例
情况1:
class MyTextView创建MyTextView类,无主构造函数,也可以理解为默认无参: AppCompatTextView()继承AppCompatTextView 并调用AppCompatTextView的无参构造,实际上AppCompatTextView并没有无参构造,AndroidStudio会提示报错- 继承时
: AppCompatTextView()基类的括号并不是固定语法,表明调用基类的无参构造
class MyTextView : AppCompatTextView() {
}
情况2:
- MyTextVie声明主构造,AppCompatTextView使用主构造参数初始化
- 用java的思想,想要声明此构造函数实现AppCompatTextView其他的构造函数,但次构造函数调用super的时候报错了。
- 报错的原因在于对kotlin理解的不透彻,用java的老想法写kotlin代码,原因如下:
- 派生类(MyTextView)在存在主构造函数的情况下,基类(AppCompatTextView)必须调用主构造参数立即初始化
- 次构造函数必须调用主构造初始化,如果此构造后面调用基类构造,那么次构造就不是次。它能脱离主构造函数独立的初始化对象。
- kotlin规定,如果存在主构造函数,那么它就是大哥,优先级最高。对象的初始化不能绕过主构造函数。
- 所以如下写法是错误的,也是不可取的。
class MyTextView(context: Context) : AppCompatTextView(context) {
constructor(context: Context,attr: AttributeSet?):super(context,attr){
}
}
情况3:
- 只声明次构造函数,彼此之间是平级,可以随意调用基类构造函数
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
)
}
- 或者调用另一个构造函数
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:
- 使用注解
@JvmOverloads作用:在有默认参数值的方法中使用@JvmOverloads注解,Kotlin就会暴露多个重载方法。 - 所以仅仅声明一个了具有三个参数的主构造,却实现了和情况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)
总结:
- 如果存在主构造函数,优先级最高。对象的初始化不能绕过主构造函数
- 如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化
- 如果派生类没有主构造函数,那么每个次构造函数必须使用 super关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数
5.函数
- 声明一个函数使用关键字
fun。- 返回值跟在参数()后面
- Unit 表示当前参数无返回值和java中void关键字的作用一致。但是Unit不是关键字而是一种类型,只有唯一一个值“Unit”。
- Unit 可以省略不写
fun test():Unit{
//...
}
fun test(){
}
- 参数
- 函数参数名称和类型类型通过冒号隔开
name:type - kotlin的函数参数,可以指定默认值
- 有默认值的情况下,调用函数可以指定给哪一个函数赋值
- 函数参数名称和类型类型通过冒号隔开
- 单表达式函数
- java中if语句如果括号内只有一条语句可以省略括号。
if (kUserBean == null) kUserBean = new KUserBean();
- kotlin中的函数 也存在一条语句可以省略的写法。
- 省略花括号,在等号之后指定代码体
- kotlin存在类型推断,返回值类型也可以省略
//省略写法 fun double(x: Int) = x * 2 //正常写法 fun double(x: Int): Int { return x * 2 } - java中if语句如果括号内只有一条语句可以省略括号。