Kotlin基础语法< 一 >
使用 Kotlin 可以编写出简洁高效的代码, 同时又可以完全兼容既有的 Java 技术栈(Java-based technology stacks)。初学Kotlin,为学习而总结所学知识,也是为日后温习提供便利。
extends 和 implements
Java的写法 Kotlin的写法
//java
class A extends B implements C{}
//kotlin
class MainActivity : AppCompatActivity(),Runnable{}
继承类时要带上括号,接口就不用括号。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是一种静态语言
写法,而静态语言,编译期推断出类型,只推断出一次类型。动态语言
,运行时推断出类型,可再次推断出类型。
只读的局部变量使用关键字 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调用主构造函数。
有主构造函数,必须要在init块里初始化变量。如果类中存在主次构造函数,那么次构造函数必须this调用主构造函数。
多个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修饰的变量,是一个幕后字段,并不对外开放的。
属性是提供外面调用的,并不保存状态;幕后字段不对外开放,并保存状态。
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类型的结果。
fun3函数:和函数fun2函数的作用是一样的,返回值为 Unit 类型时, 可以省略。
sum函数:使用表达式语句作为函数体, 返回类型由自动推断决定。
null安全(空安全)
- 非空类型和可空类型
- 先判断后使用
- 安全调用
- Elvis运算
空安全的几种简单用法
定义一下参数是可空类型的打印函数
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)
}
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)
}
字符串换行顺序是按原样行,一行行执行。