Kotlin基础

文章目录

Kotlin简介

Kotlin作为JVM系的语言,起源于Java又不同于Java。通过在语言层面比较两者的区别,可以使得开发者能够快速学习,融会贯通。

kotlin中所有类都继承自Any类,它是所有类的超类。实例化类没有new 关键字

类的声明(class|object)

一般声明一个类,我们用class

//使用class 关键字声明一个类(Example类从Any隐士继承)
class Example{   
}
//实例化Example类
var example = Example()

Any类默认提供三个函数

  • equals()
  • hashCode()
  • toString()

注意:Any不是java.lang.Object

同时我们可以使用object来声明一个类,此时该类是一个单例类,同时也声明了它的一个对象。

//使用object声明一个类,该类是单例类(此时也声明了它的一个对象)
object Example2{
  
  var name:String="zcmain"
  var age:Int=20
  
  fun getInfo():String{
    return "name->$name,age->$age"
  }
}

//单例类使用,无需实例化对象
print(Example2.name)
print(Example2.age)
println(Example2.getInfo());
//zcmain 20 
//name->zcmain,age->20

类的修饰符(classModifier|accessModifier)

类的修饰符包括:

  • classModifier:类属性修饰符
  • accessModifier:类访问权限修饰符

类属性修饰符

修饰符说明
abstract抽象类
final类不可被继承,默认属性
open类可以被继承
enum枚举类
annotation注解类

类访问权限修饰符

修饰符说明
private仅在同一个文件中可见
protected同一个文件或者子类可见
public所有调用地方可见
internal同一个模块(包名下)可见
静态内部类(class )

静态内部类直接使用class声明即可

//外部类
class School{
    //外部类成员变量
    var name = "zcmain";
    companion object {
        //外部类静态成员变量
        var age = 20;
    }
    
    //静态内部类 class修饰
    class StaticInnerClass {
        fun test() {
            println("我是class修饰的静态内部类:$age")
        }
    }
    
    //静态内部类 object修饰
    object StaticInnerClass2 {
        fun test() {
            println("我是object修饰的静态内部类:$age")
        }
    }
}
非静态内部类(inner)

非静态内部类需要在class前面加上 inner关键字

注意:

非静态内部类访问外部类成员变量使用:this@外部类名.成员变量名

//外部类
class School{
    //外部类成员变量
    var name = "zcmain";
    companion object {
        //外部类静态成员变量
        var age = 20;
    }
    
    //非静态内部类
    inner class MyTeacher{
        fun test() {
            //非静态内部类访问外部类的成员变量
            var innerName = this@School.name
            println("我是非静态内部类:$innerName , $age")
        }   
    }
}

外部调用

object Test{
    @JvmStatic
    fun main(args:Array<String>){
         //访问MyClass的内部类
        MyClass().Innerclass().test();

        //访问MyClass的静态内部类
        MyClass.StaticInnerClass().test()

        //访问MyClass的静态内部类
        MyClass.StaticInnerClass2.test()
    }  
}
抽象类(abstract)

抽象类使用 "abstract"修饰,同样成员/方法也可以使用"abstract"修饰,抽象成员在类中不存在具体的实现。

注意:

1、无需对抽象类或抽象成员标注open注解

//定义抽象类
abstract class Human {
    abstract fun eat()
}

//Man类继承Human抽象类,实现eat方法
class Man : Human() {
    override fun eat() {
        System.out.println("$name")
    }
}
匿名内部类
//定义接口
interface IInterface{
    fun test()
}

class Test{
    fun setInterface(test:IInterface){
        test.test()
    } 
}

fun main(args:Array<String>){
    var testClass = Test()
    testClass.setInterface(object:IInterface{
        override fun test() {
               print("匿名内部类")
            }
    }) 
}
类继承(:xx())

类继承符号:kotlin中所有类都继承Any类,它是所有类的超类

注意:

1:基类需要使用 “open” 修饰,同样基类中需要被子类重写的方法也需要使用 “open” 修饰否则无法继承
2:子类重写父类的方法时候需要使用 “override” 修饰方法

//基类
open class BaseClass{
    open fun test(){
        println("baseclass test")
    }
}

//子类继承基类重写test方法
class ChildClass:BaseClass(){
   //TODO 继承的B类后面需要带()来构建对象
    override fun test(){
        println("我是子类重写了基类的test方法")
    } 
}

选择性调用父类方法

如果基类或者接口有多个相同方法,子类使用super范型选择性调用父类实现

//基类A
pen class A {
    open fun a() {
        print("A-a")
    }

    fun testA() {
        print("A-test")
    }
}

//接口B(可以实现)
interface B {
    fun a() {
        print("B-a")
    }

    fun testB() {
        print("B-test")
    }
}

//子类继承接口并且实现接口
class C : A(), B {
    override fun a() {
        super<A>.a()   //调用基类A的a方法
        super<B>.a()   //调用接口B的a方法
    }
}
接口(:xx)

Kotlin 接口与 Java 8 类似,使用interface 关键字定义接口,允许接口中方法有默认实现:

接口成员变量默认是open

interface MyInterface{
    fun bar()  //未实现
    fun foo(){ //已实现
       print("我是接口实现的方法")
    } 
}

被实现的接口不需要添加() 一个类或者对象可以实现一个或多个接口

//例如A继承B实现C、D接口
class A:B(),C,D{
   //TODO 实现的C、D接口后面不需要带()来构建对象
}

重写接口属性

接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性只,实现接口时,必须重写属性

interface MyInterface{
    var name:String.  //name属性,抽象的
}

class MyImpl:MyInterface{
    override var name:String = "zcmain"  //重写接口属性
}
单例类(object)

单例使用object修饰(替换class则标示这个类是单例模式)

注意:

1、单例类作为代理时候不需要()来构建对象

class A{
    //TODO A是非单例
}

object A{
 //TODO object修饰的类即为单例
}

委托/代理类(by)

代理使用by 关键字

注意

1:委托类和代理类都需要实现同一个接口
2:代理类如果是非单例后面要加上()来构建对象,如果是单例无需加()来构建对象

3:Kotlin委托与Java代理

类委托

类委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

//1、定义接口
interface IStudy {
    fun daydayStudy()
}

//2 实现次接口的被委托类
class ChildStudy:IStudy {
    override fun daydayStudy() {
        System.out.println("我是孩子类我每天都要学习")
    }
}

//3 通过关键字 by 建立委托类
class ParentStudy:IStudy by ChildStudy() {
    //TODO 父类继承学习接口 委托孩子进行学习(调用父类的daydayStudy方法,最终会由孩子执行此方法)
}

//------------------------被委托类是单例模式-------------------------------------//
//实现接口的被委托类(单例模式)
class ChildStudySingleton:IStudy {
   override fun daydayStudy() {
        System.out.println("我是孩子我每天都要学习")
    }
}

//通过关键字 by 建立委托类(被委托类是单例,则后面无需添加括号
class ParentStudy:IStudy by ChildStudySingleton {
    //TODO 父类继承学习接口 委托孩子进行学习(调用父类的daydayStudy方法,最终会由孩子执行此方法)
}

例如:A委托B进行业务操作(A是委托类,B是代理类且非单例{class B修饰的})

class A by B(){
  //TODO A委托B进行业务操作(B是非单列所以后面需要加上()来构建对象)
}

例如:A委托B进行业务操作(A是委托类,B是代理类且是单例{object B})

class A by B{
  //TODO A委托B进行业务操作(B是单例所以后面无需加()来构建对象)
}
属性委托

属性委托指的是一个类的某个属性值不是在类中直接定义,而是将其委托给一个代理类,从而实现对该类的属性统一管理。

属性委托的格式

var/val <属性名><类型> by <表达式>
  • var/val :属性类型(可变/只读)
  • 属性名:属性名称
  • 类型:属性的数据类型
  • 表达式:委托代理类

函数(fun)

构造函数
主构造函数

kotlin中类可以有一个主构造器,以及一个或多个次构造器,主构造器是类头部一部分,位于类名称之后,主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用init关键字作为前缀

//定义School类并有一个主构造器携带一个参数
class School constructor(name:String){
    //init初始化代码段
    init{
        println("主构造器参数name:$name")
    }
}

主构造函数的参数,如果无任何修饰符在初始化代码块init{}中可以访问,但在类内部和外部都是无法访问的。如果使用修饰符(var/valpublic var/val可以在类内部和外部都可以访问)

class School constructor(var name: String, age: Int) : IStudy {
    override fun daydayStudy() {
        //可以访问name被var修饰
        print("name is $name")
        //错误,age无修饰符在类内外部都不可以访问
        print("age is $age")  
    }
}

//使用
fun main(args:Array<String>){
    var a = A("zcmain",50)
    //可以访问
    println(a.name)
    //错误,无法访问,age在A主构造函数中无修饰符在类内外部都无法访问
    println(a.age)
   }

忽略主构造函数constructor关键字

如果主构造器无任何注解,也无任何可见度修饰符,那么**constructor可以省略**,如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前。

//主构造函数没有任何修饰符可省略 constructor关键字
class School(name:String){         
}
//主构造函数有private修饰符不可省略 constructor关键字
class School private constructor(name:string){
}

空主构造函数

如果一个非抽象类没有声明构造函数(主或次构造函数),他会自动产生一个Public的没有参数的构造函数,如果不想类默认产生公共的构造函数,可以使用private声明一个空的主构造函数

//声明一个空的主构造函数
class School private constructor(){
}
次构造函数

类也可以有次构造函数,需要加上前缀constructor

class School{
    constructor(name:String){
        println("次构造函数参数name:$name")
    }
}

次构造函数代理主构造函数

如果一个类既有主构造函数,也有次构造函数,那么次构造函数都要直接或间接通过另一个次构造函数代理主构造函数,在同一个类中代理另一个构造函数使用this关键字

class ConstructorClass constructor(name: String) {

    init {
        println("主构造器参数:name:$name")
    }
    
    //次构造函数1代理主构造器函数
    constructor(name1: String, age: Int) : this(name1) {  
        println("我是次构造函数1:name1:$name1,age:$age")
    }
    
    //次构造器函数2代理主构造器函数
    constructor(name2: String, address: String) : this(name2) {
        println("我是次构造函数2:name2:$name2,address:$address")
    }
    
    //次构造器函数3代理次构造器函数
    constructor(name3: String, age: Int, address: String) : this(name3, age) {
        println("我是次构造函数3:name3:$name3,age:$age,address:$address")
    }
}

继承构造函数的初始化

前提:基类有主构造函数或者次构造函数

2.1 子类有主构造函数,必须在子类主构造函数中立即初始化基类

2.2 子类没有主构造函数,必须在子类次构造函数中使用super关键字立即初始化基类

Ps:初始化基类时可以调用基类不同的构造方法

//基类有主构造函数同时有次构造函数
open Person(name:String){
   constructor(name:String,age:Int):this(name){
   }
}

//1.子类有主构造函数,必须在主构造函数中立即初始化基类
class Student(name:String,age:Int,no:String):Person(name,age){
}

//2.子类没有主构造函数,必须在次构造函数使用super关键字初始化基类。
class Student:Person{
    //调用基类的主构造函数初始化基类
    constructor(name:String):super(name){}
    
   //调用基类的次构造函数初始化基类
    constructor(name:String,age:Int):super(name,age){}    
}

普通函数

普通函数定义使用关键字 fun fun 函数名(参数:参数类型):返回类型{}

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

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

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

Public 修饰的函数必须明确写出返回类型(除了无返回类型Unit可省略)

public fun sum(a:Int,b:Int) = a+b

无返回值的函数使用Unit修饰也可以忽略

//返回类型Unit即无返回类型
fun printSum(a:Int,b:Int):Unit{
    println(a+b)
}
//可以省略返回类型Unit关键字
fun printSum(a:Int,b:Int){
    println(a+b)
}
可变长参数的函数(vararg)

vararg关键字修饰可变长参数函数

//可变长参数函数
fun test(vararg v: Int) {
        for (vt in v) {
            println("vt:$vt")
        }
    }
//测试
fun main(args:Array<String>){
      Singleton.test(1,2,3,4,5)
    }
//输出
vt:1
vt:2
vt:3
vt:4
vt:5

成员变量

变量修饰符
(Var)可变变量

用来定义一个可变变量,可以通过重新分配或更改其值的变量。类似于java中的声明的变量

格式:var <标识符> : <类型> = <初始化值>

var a:Int? = null  //定义类型为int可以为空
var b:Any = Any()  //定义类型为Any(即Object)并且不可为空
var c:String?=null //定义类型为String,可以为空
lateinit var d:String //定义类型为String,延迟初始化(lateinit不能修饰基本数据类型)
(Val)不可变变量

val是一个只读变量,这种声明变量的方式相当于java中的final变量,val修饰的变量声明时候必须进行初始化,因为后期不可更改其值。

格式:val <标识符> : <类型> = <初始化值>

val e:String="zcmain"

常量与变量都可以没有初始化值,但是在引用前必须初始化

变量属性
getter|setter

如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省略类型[PropertyType]

Kotlin 属性声明完整写法:

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

getter和setter都是可选的,kotlin默认实现了属性的getter和setter方法。

注意:

1、val不允许设置setter函数,因为他是只读的。

var age :Int?   //错误,需要初始化语句,默认实现了getter和setter方法。
var index = 1   //类型为 Int,默认实现了 getter和setter方法。
val simple:Int? //类型为 Int,默认实现了 getter方法,但必须在构造函数中初始化。
val type = 1    //类型为 Int,默认实现了 getter方法 。

自己重写getter和setter方法:

class School{
    //重写name变量的getter和setter方法
    var name:String = "zcmain"
        get()=filed
        set(value){
            filed = value+" test"
        }   
}

//访问 School类的 name变量
fun main(args:Array<String>){
    var schoolClass = School()
        schoolClass.name = "reassigned name is Joan"
        println("name:$schoolClass.name")
}

//输出 reassigned name is Joan test
Backing Fields(后端变量)

kotlin中不能有字段,提供了Backing Fidlds机制,备用字段使用field关键字声明,field关键词只能用于属性的访问,如下:

/**
 *错误写法
 *getter 和 setter方法中不能出现字段,如果使用字段如下:set(name = value)其中name=value等同于 
 *set(value),而set(value)里面实现又是 name=value,便会陷入递归调用。
 **/
var name: String? = null
        set(value) {
            name = value
        }
        get() = name


//正确写法使用field关键字进行操作
var name: String? = null
        set(value) {
            field = value
        }
        get() = field

lateinit(延迟初始化)

非空属性必须在定义时候初始化,kotlin提供了一种可以延迟初始化方案,使用 lateinit关键字描述属性:

注意:lateinit只能用于自定义类型对象,不能用于基本数据类型

class School{
    //延迟初始化
    lateinit var name:String
    fun setSchoolName(name:String){
        this.name = name;
    }  
}

数据类型

基本数据类型

Kotlin不同于java的是字符类型char不属于数值类型,是一个独立的数据类型。koltin基本数值类型如下

类型位宽字节
Double648
Long648
Float324
Int324
Short162
Byte81

koltin不支持8进制,2进制以0b开头,16进制以0x开头

类型转换(toxxx())

较小类型不能隐士转换成较大的类型

val b:Byte = 1  //Ok,字面值时静态检测的
val i:Int = b   //错误(较小类型不能隐士转换较大类型)

可以使用toInt()方法

val b:Byte = 1
val i:Int = b.toInt() //ok

每种数据类型都有下面这些方法,可以转换成其他类型

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
其他数据类型
字符(Char)

和java不一样,kotlin中的char不能直接和数字操作,char必须时单引号 包含起来,比如普通的字符’0‘’a‘

fun check(c:Char){
   if(c == 1){    //错误,类型不兼容
       //TODO 
   }
}

我们可以把字符转换为Int数字:

fun decimaDigitValue(c:Char):Int{
    if(c !In '0'..'9')
       	throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt()  //显示转换为数字 
}
字符串(String)

和 Java 一样String 是不可变的。方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历:

for (c in str) {
    println(c)
}

Kotlin 支持三个引号 """扩起来的字符串,支持多行字符串,比如:

fun main(args: Array<String>) {
    val text = """
    多行字符串
    多行字符串
    """
    println(text)   // 输出有一些前置空格
}

String 可以通过 trimMargin() 方法来删除多余的空白。

fun main(args: Array<String>) {
    val text = """
    |多行字符串
    |菜鸟教程
    |多行字符串
    |Runoob
    """.trimMargin()
    println(text)    // 前置空格删除了
}
布尔(true|false)

布尔用 Boolean 类型表示,它有两个值:truefalse

数组(Array)

数组创建由两种方式

  • arrayOf()创建
  • 工厂函数创建
fun main(args:Array<String>){
   //[1,2,3]
   val a = arrayOf(1,2,3)
   //[0,2,4]
   val b = Array(3,{i->(i*2)}) 
}

//读取数组内容
println(a[0])    // 输出结果:1
println(b[1])    // 输出结果:2

注意:与java不同的是,Kotlin中的数组是不变类型的。除了Array还有ByteArrayShortArrayIntArray,用来表示各个类型数组,省去了装箱操作效率更高,用法与Array一样。

val x:IntArray= intArrayOf(1,2,3)
x[0] = x[1] + x[2]

集合(List|Set|Map)

Kotlin没有自己的集合库,完全依赖Java标准库中的集合类,并通过扩展函数增加特性来增强集合。Kotlin与Java最大的不同之一就是:Kotlin将集合分为只读集合可变集合

集合类型只读集合可变集合
ListlistOfmutableList、arrayListOf
SetsetOfmutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
MapmapOfmutableMapOf、hashMapOf、linkedMapOf、sortedMapOf
只读集合(listOf|setOf|mapOf)

kotlin.collections.Collection 该接口可以对集合进行一些基本操作,但无任何添加和移除元素的方法。

//创建只读List集合
val mList = listOf("张三","李四","王五")
println(mList);
//打印输出 {张三,李四,王五}

//只读Set同List...

//创建只读Map
val mMap = mapOf(0 to "张三",1 to "李四",2 to "王五")
println(mMap);
//打印输出 {0=张三,1=李四,2=王五}

可变集合(Mutablexxx)

kotlin.collections.MutableCollection(MutableCollection 接口继承自 Collection) 提供添加、移除和清空集合元素的方法

//创建可变MutableList
var mutableList = mutableListOf("android","java","kotlin")
//向mutableList添加元素
mutableList.add("C")
mutableList.add("Python")
println(mMap);
//打印输出 {android,java,kotlin,C,Python}

区间(..)

区间表达式由具有操作符形式.. 的rangeTo 函数辅以in!in形式。

区间时为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现,如下示例

for(i in 1...4){
    print(i)   //输出 1234
}

for(i in 4..1){
     print(i)   //什么都不输出
}

for(i in 4 downTo 1 ){
     print(i)   //输出4321
}

//检测 i 是否在区间[1,10]内
if(i in 1..10) {//等同于 1<=i && i<=10
     print(i)  
}

for(i in 1..4 step 2){
     print(i)   //输出13
}

for(i in 4 downTo 1 step 2){
     print(i)   //输出42
}

for(i in 1 until 10){  //排除结束元素[1,10)
     print(i)   //输出123456789
}

条件控制

IF表达式

一个if语句包含一个boolean表达式和一条或多条语句

//常规用法
var max:Int
if(a>b){
   max = a  
}else{
   max= b
}

//将if表达式结果赋值给一个变量(简化了java中的三元操作符)
//java
int max = a>b?a:b;
//kotlin
val max = if(a>b)a else b
When表达式

when将他的参数和所有分支条件顺讯比较,直到某个分支满足条件。

when既可以被当作表达式使用也可以被当作语句使用,如果被当作表达式使用符合条件的分支值就是整个表达式的值,如果当作语句使用,则忽略个别分支的值。(when类似java语言中的switch操作符)

//精确匹配
when(x){
    1-> print("x=1")
    2-> print("x=2")
    //else同switch中default,如果其他分支条件都不满足将会取else值
    else->{ print("x 不是2,也不是2")}
}

如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔。

//多分支匹配
when(x){
    0,1->print("x=0 or x = 1")
    else->print("otherwise")  
}

也可以检测一个值在(in)或者不在(!in)一个区间或者集合中

//区间匹配
var list = listOf<Int>(1, 2, 3, 4, 5)
when (x) {
     in 1..10 -> {
         println("x in [1,10]")
     }
     !in 11..20 -> {
         println("x !in [11,20]")
     }
     in list -> {
         println("x in list")
     }
     else -> {
         println("otherwise")
     }
}

//when 中使用 in 运算符来判断集合内是否包含某实例:
var setlist = setOf("Java","C++","Python","Go")
var a = "JavaScript"
var b = "c++"
when{
    a in setlist->{print("a in list")}
    b in setlist->{print("b in list")}  
}

还可以检测一个值是is或者不是!is一个特定类型的值

注意:由于智能转换,因此可以访问该类型的方法和属性而无需任何额外的检测。

fun hasPrefix(x:Any) = {
    when(x){
         is String -> x.startsWith("prefix")
         else -> false
    }
}

循环控制

For循环

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

//直接获取value
for (item in collection){
    print(item)
}

如果想通过索引遍历数组或者list可以如下:

//遍历到index下标索引,获取对应的value
for(index in array.indices){
    print(array[index])  
}

还可以使用库函数withIndex

//使用库函数withIndex可直接获取对应下标索引和value
for((index,value) in array.withIndex()){
    print("index:$index,value:$value)
}
whiledo...while循环

while是最基本的循环,它的结构为:

while(布尔表达式){
    //循环内容
}

do…while循环不同于while循环是:do…while循环即使不满足条件循环至少执行一次

do{
    //循环体
}while(布尔表达式)
repaet循环

repeat循环是Kotlin相对于java新加入的特性,取代for(int i=0;i<Max;i++)用于重复简单的工作

//kotlin
fun test(){
  
  //repeat循环
  repeat(5){i->
     print(i)
  }
  
  //for循环
  //for(i in 0..4){
  //  print(i)
  //}
  
}
//01234

//等价于Java
void test(){
  //for循环
  for(int i = 0;i<5;i++){
    system.out.print(i);
  }
}
//01234
返回(return continue)和跳转(break)

kotlin有三种结构化跳转表达式

  • return:默认从最直接包围它的函数或者匿名函数返回。
  • break:终止最直接包围它的循环。
  • continue:继续下一次最直接包围它的循环。

在循环中kotlin支持传统的breakcontinue操作符

fun main(args:Array<String>){
    for(i in 1..10){
        if(i == 3){
            continue   //i 为3时跳过当前循环,继续下一次循环
        }
        print(1)
        if(i>5){
            break      //i 为 6时跳出循环
        }
    } 
}
//输出:12456
标签处返回(xxx@)

kotlin中任何表达式都可以用标签来标记,标签格式:标识符@ 例如:abc@ footBar@都是有效标签,要为一个表达式添加标签,我们只需要在其前添加标签即可

loop1@ for(i in 1..100){
   loop2@ for(j in 1..100){
              if(j == 10){
                /**
                 * 如果break @loop1 则会直接跳出最外从for循环,
                 * 如果break @loop2 则会跳出内部循环继续执行外部循环
                 **/
                 break@loop1      
              }
      }
}

fun foo(){
       list@ lists.forEach {
            if (it == 3) {
                /**
                 *如果 return@list当it==3时候返回当前的lists循环继续执行下面代码打印出“continue”
                 *如果直接renture则是返回到最外层方法不会执行方法内部循环后面代码
                 **/
                return@list;   
            }
            println("it:$it")
       }
    println("continue。。。")
  }
}

数据类与密封类

数据类(data)

kotlin可以创建一个只包数据的类,关键字为data

data class User(val name:String,val age:Int)
数据类特征

编译器会自动从主构造函数中根据所有声明的属性提取一下函数:

  • 所有属性的 getters (对于 var 定义的还有 setters)
  • equals() / hashCode()
  • toString() 格式如:“User(name=John,age=42)”
  • componentN() functions对应于属性,按声明顺序排列
  • copy() 函数
数据类必要条件

如果这些函数在类中已经明确被定义了,或者从超类中继承而来,就不会再生成。为了保证生成代码的一致性以及有意义,数据类需要满足一下条件

  • 主构造函数中至少包含一个参数
  • 所有的主构造函数的参数必须表示为valvar
  • 数据类不可以声明为abstractopensealedinner
  • 数据类不能继承其他类(但是可以实现接口)

copy 函数使用copy函数复制User数据类,并修改age属性

//声明数据类
data class User(val name:String,val age:Int)

fun main(args:Array<String>){
     val jack = User(name = "zcmain",age = 20)
     // 复制数据类
     val John = jack.copy(age = 40)   
     println("jack user:$jack")
     println("John user:$John")
}
数据类以及解构声明

组件函数允许数据类在解构声明中使用,可以使用_占位不需要的属性。

val jane = User("Jane",35)
val (name,age) = jane
//使用下划线占位不需要的参数
val (_,age) = jane   
//使用componentN获取对应的参数
var age = jack.component2()
println("name:$name,age:$age")
无参构造函数数据库类

若要数据类生成一个无餐构造函数,在使用它的时候不用传入参数,需要在定义该数据类时,对参数指定默认值

//定义无参构造数据类(对参数指定默认值)
data class User(var name:String = "",var age:Int = 0 ){
    //若不希望所有的属性都作为数据类中的属性,可以将该属性放到类体中
    lateinit var address:String
}

//使用
fun main(args:Array<String>){
    //初始化数据类User(由于数据类在定义时候指定默认参数值,这里可通过无参构造函数初始化)
    var user = User()
    user.name = "zcmain"
    user.age = 20
    user.address = "上海浦东"
    println("user:$user")
}
密封类(sealed)

密封类用sealed关键字修饰,用来表示受限类的继承结构。当一个值为有限集中的类型、而不能由任何其他类型时,在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有课包含状态的多个实例。

密封类特点
  • 使用sealed关键字修饰,不允许有非private构造函数(默认是private)。
  • 所有子类必须在密封类自身相同的文件中声明。
  • 密封类自身是抽象的,不能直接实例化,内部可以有抽象abstract成员。
  • 扩展密封类的子类(间接继承者)可以放在任何位置,而无需在同一个文件中。
//定义Bus密封类
sealed class Car {

    //密封类属性
    lateinit var busName: String
    lateinit var busColor: String
    var busNo: Int = 0

    //密封类抽象函数
    abstract fun setArgs(name: String, no: Int, color: String)

    //重写toString方法
    override fun toString(): String {
        println("我是$busColor$busName 车牌:$busNo")
        return super.toString()
    }

    /**
     * 火车子类
     */
    class train : Car() {
        //重写父类的抽象方法setArgs
        override fun setArgs(name: String, no: Int, color: String) {
            busName = "[train]-$name"
            busNo = no
            busColor = color
        }
    }

    /**
     * 校车子类
     */
    class schoolBus : Car() {
        //重写父类的抽象方法setArgs
        override fun setArgs(name: String, no: Int, color: String) {
            busName = "[school]-$name"
            busNo = no
            busColor = color
        }
    }
}

扩展密封类子类的类(间接继承密封类)可以放在任何位置,而无需在同一个文件中

//定义Taxi类继承密封类Car的子类SchoolBus
class Taxi:Car.schoolBus() {
    //重写父类的setArgs方法
    override fun setArgs(name: String, no: Int, color: String) {
        busName = "[Taxi]-$name"
        busNo = no
        busColor = color
    }
}

测试

fun main(args:Array<String>){
    
    //构造密封类的SchoolBus子类
     var car1 = Car.schoolBus()
     car1.setArgs(name = "校车", no = 10000, color = "黄色")
     car1.toString()
    
    //构造密封类Train子类
     var car2 = Car.train()
     car2.setArgs(name = "火车", no = 10001, color = "绿色")
     car2.toString()
    
    //构造密封类子类的类
    var car3 = Taxi()
    car3.setArgs(name = "的士", no = 10002, color = "红色")
    car3.toString()
    println("current car is :${busName(car3)}")
}

//when判断当前汽车类型并可以获取汽车属性
fun busName(car: Car): String = when (car) {
     is Car.schoolBus -> car.busName
     is Car.train -> car.busName
     //不需要else语句了,因为涵盖了密封类所有情况
}

//输出
我是黄色 的[school]-校车 车牌:10000
我是绿色 的[train]-火车 车牌:10001
我是红色 的[Taxi]-的士 车牌:10002
current car is :[Taxi]-的士	红色	10002

泛型(<T>)

kotlin与java一样也提供泛型,为类型安全提供保证,消除类型强转的烦恼

//声明一个泛型变量
class <T> A(t:T){
    var value = t
}

//使用泛型变量
fun main(args:Array<String>){
    
    //显示指明类型参数
    var a1 = A<Int>(10)
    var a2 = A<String>("zcmain")
    
    //如果编译器可以自动推断类型,可以省略类型参数
    var b1 = A(10)         //编译器自动推断为Int类型
    var b2 = A("zcmain")   //编译器自动推断为String类型 
}

枚举(enum)

枚举类最基本的用法是实现一个类型安全的枚举

枚举常量用逗号分隔,每个枚举常量都是一个对象

enum class CarEnum(val code: Int, name: String) {
    //初始化枚举值
    TRUCK(1001, "卡车"),
    TAXI(1002, "的士"),
    TRAIN(1003, "火车"),
    SCHOOLBUS(1004, "校车")
}

//测试
fun main(args: Array<String>) {
  //遍历枚举
  var list = CarEnum.values()
  list.forEach {
       //枚举类型中的code,name,ordinal(顺序)
       enum -> println("code:${enum.code},name:${enum.name},ordinal:${enum.ordinal}")
   }

  //根据值获取枚举
  var carEnum = CarEnum.valueOf("TAXI")
  println("code:${carEnum.code},name:${carEnum.name},ordinal:${carEnum.ordinal}")
}

//输出
code:1001,name:TRUCK,ordinal:0
code:1002,name:TAXI,ordinal:1
code:1003,name:TRAIN,ordinal:2
code:1004,name:SCHOOLBUS,ordinal:3
code:1002,name:TAXI,ordinal:1

对象表达式、对象声明、伴生对象

对象表达式

Kotlin 用对象表达式和对象声明来实现创建一个对某个类做了轻微改动的类的对象,且不需要去声明一个新的子类。

interface Car{
    fun color()
}

open class Taxi{
    open fun drive(){
        
    }
}

fun main(args:Array<String>){
    
    //对象表达式(如果父类有构造函数那么必须传递相应的构造函数)
    val ab = object:Taxi(),Car{
        override fun color(){}
        
        override fun drive(){}
        
        fun run(){
            println("running")
        }  
    }
    ab.run()
    
    
    //如果没有父类
    val ab = object{
        fun run(){
            println("running。。。")
        }
    }
}
对象声明(object单例)

object是kotlin中关键字用来:

  • 对象表达式
  • 对象声明
  • 对象表达式

    继承一个匿名对象

    view.setOnClickListener(object : OnClickListener {
            override fun onClick(p0: View?) {
                Toast.makeText(this@TestActivity, "点击事件生效", Toast.LENGTH_LONG)
            }
    })
    

    上面代码其实就是我们经常要给 view 设置的点击事件,OnClickListener 事件是一个匿名类的对象,用object来修饰。

  • 对象声明

    object修饰的类为单例类

    object School{
        private val TAG = School::class.java.simpleName
        fun study(){
            Log.d(TAG,"I Study at School")
        }
    }
    

    外部使用单例

    object Test{
        @JvmStatic
        fun main(args:Array<String>){
           //School为单例直接通过类名调用成员函数
           School.study()
        } 
    }
    
伴生对象(Companion object)

companion object修饰为伴生对象,伴生对象在类中只能存在一个,类似于java中的静态方法

class School{
    
    //声明伴生对象(字段、方法)类似于java中的静态属性、方法外部可通过类名直接访问
    companion object{
        var name = "zcmain"
        fun Study(){
            println("name:$name")  
        } 
    }
}

外部访问(直接类名访问)

object Test {
    @JvmStatic
    fun main(obj: Array<String>) {
        //访问Mycalss中的伴生对象
        School.name       //直接访问name属性
        School.Study()    //直接访问Study方法  
}
对象表达式、对象声明、伴生对象的区别
  • 对象表达式在我们使用的地方立刻初始化并执行
  • 对象声明是懒加载,是在我们第一次访问的时候初始化
  • 伴随对象在对象类加载的时候初始化,和Java的静态初始化对应

Kotlin中扩展

Kotlin可以对一个类的方法和属性进行扩展,扩展是一种静态行为,被扩展的类代码本身不会造成任何影响。

扩展能够向已经存在的类中添加新的函数或属性,也包含第三方库或者SDK中的类。

扩展函数(fun ClassName.functionName(args:Any)

扩展函数可以在已有的类中添加新的方法,不会对原类做修改,扩展函数定义形式:

//扩展函数
fun targeClass.funtionName(parms){
    body
}
  • targeClass: 表示函数的接受者(扩展目标的类),也就是函数扩展的对象。
  • functionName:扩展函数的名称
  • params:扩展函数的参数,可以为NULL

一般扩展函数定义是直接创建一个文件,然后写fun就可以了,注意是文件,不是类。外面不需要再包一层。

PS:扩展函数实质上并没有改变原来的类,它只是创建了一个对象方法

//创建ExtendFile.kt文件(这里不是类)
/**
 * User类的扩展函数 add、remove
 */
fun User.add(name: String, age: Int) {
   println("add user name:$name,age:$age")
}

fun User.remove(name: String) {
    println("remove user :$name,from List")
}

//使用
object Test{
    @JvmStatic
    fun main(args:Array<String>){
        //调用User类的扩展函数
        User().add("zcmain",30)
        User().add("Jon",28)
        User().remove("zcmain")
    }
}
扩展属性(仅支持Val)

扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中,初始化属性为因为属性没有后端字段(backing Field),所以不允许被初始化,只能由显示提供的getter/setter定义。

扩展属性只能被声明为Val

val User.name = "zcmain"  //错误,扩展属性不能由初始化器
/**
 * 扩展User类的属性
 */
//1、属性必须为Val类型
val User.name:String
    //2、必须提供访问器(get方法)
    get() = "zcmain" 

//使用
object Test{
    @JvmStatic
    fun main(args:Array<String>){
        //调用User类的扩展属性
        var name = User().name
        println("user name:$name")
    }
}
伴生对象扩展(companion object)

如果一个类定义有一个伴生对象,可以为伴生对象定义扩展函数和属性,伴生对象通过.类名.形式调用伴生对象,伴生对象声明的扩展函数,通过类型限定符来调用。

//定义User类以及伴生对象
class User{
    companion object{
        fun test(){
            println("print log...")
        }
    }
}

//定义User伴生对象的扩展函数
fun User.Companion.add(name:String){
    println("我是User类伴生对象的扩展函数:$name")
}

val User.Companion.name:String
     get()= "zcmain"

//使用
object Test{
    @JvmStatic
    fun main(args:Array<String>){
        //调用User类伴生对象的扩展函数和方法
        User.Companion.add("zcmain")
        println("name:${User.Companion.name}")
    }
}
扩展声明为成员

在一个类内部可以为另一个类声明扩展,在这个扩展中,扩展方法所在类实例称为分发接受者,而扩展方法的目标类型实例称为扩展接受者。

class A{
    fun testA1(){}
}

class B{
    fun testA1(){}
    fun testB1(){}
    //B类中声明A类的扩展方法,B类成为分发接受者,A类称为扩展接受者。
    fun A.testA2(){
        testA1()           //调用A类中的A1
        this@B.testA1()    //调用B类中的A1【使用this语法引用分发接受者的成员】
    }  
}

字符串模板(### )

  • $ 标示一个变量名或者变量值
  • $varName 表示变量值
  • ${varName.fun()}表示变量的方法返回值
var a = 1
val s1 = "a is $a"   //输出 a is 1

a = 2
val s2 = "${s1.replace("is","was")},but now is $a"  //输出 a was 1,but now is 2

比较两个数字( 比较值== | 比较内存地址=== )

koltin没有基础数据类型,只有封装的数字类型,每定义一个变量,kotlin其实都帮助封装了一个对象,这样可以保证不会出现空指针。数字类型也是一样,所以在比较数字的时候,就有比较数据大小和两个对象是否相同的区别了。

  • 比较对象地址:===
  • 比较值大小:==
fun main(args: Array<String>) {
    val a: Int = 10000
    println(a === a) // true,值相等,对象地址相等

    //经过了装箱,创建了两个不同的对象
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a

    //虽然经过了装箱,但是值是相等的,都是10000
    println(boxedA === anotherBoxedA) //  false,值相等,对象地址不一样
    println(boxedA == anotherBoxedA) // true,值相等
}

注意数值范围在[-128, 127]之间并不会创建新的对象

和Java一样,在Integer.java类,你会找到IntegerCache.java这个内部私有类,它为[-128,127]之间的所有整数对象提供缓存。在此范围内如果已经创建了一个相同的整数,不会使用new关键字,而用已经缓存的对象,因此两个对象都指向同一个地址

fun main(args: Array<String>) {
  
    val a: Int = 127
    val a1: Int? = a
    val a2: Int? = a
    //经过了装箱,本应该创建了两个不同的对象,但是因为数值在[-128,127]范围内不会重复创建因此a1=a2
    println(a1 === a2) //  true,内存地址相等,是同一个对象
  
    val b: Int = 128
    val b1: Int? = b
    val b2: Int? = b
    //经过了装箱,数值不在[-128,127]内创建两个对象
    println(b1 === b2) // false,内存地址不同
    println(b1 == b2)  // true,值相同
}

位操作符(shl、shr、and、or、xor、inv)

对于Int和Long类型,还有一系列的位操作符可以使用,分别是:

shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向

类型检测及转换

类型检测以及自动转换(is|!is)

Kotlin使用 is 运算符检测一个表达式是否某类型的一个实例(类似于java中的instanceof关键字),如果符合则会自动转换成该类型,然后就可以调用该类型的方法了。

fun getStringLength(obj:Any):Int?{
    if(obj is String){
        //做过类型判断后,obj会被系统自动转换成String类型
        return obj.length
    } 
}

fun getStringLength(obj:Any):Int?{
    if(obj !is String){
        return null
    }
    //在这个分支中,‘obj’的类型会被自动转换成‘String’
    return obj.length
}

fun getStringLength(obj:Any):Int?{
    //在‘&&’ 运算符的右侧,‘obj’类型会被自动转换成‘String’
    if(obj is String && obj.length>0){
        return obj.length
    }
    return null 
}
类型强制转换(as|as?)

除了is自动转换之外,我们还可以使用as来进行强制转换类型。注意如果强制转换失败则会抛出异常。

fun main(){
   val a = "88"
   val b = a as String  //将a强制转换为String类型(成功)
   println(b)  //88
  
  
   val c = 88
   val d = c as String  //将c强制转换成String类型(失败)
   println(c)           //Exception in thread "main" 										 													//java.lang.ClassCastException: 
  											//java.lang.Integer cannot be cast
                        //to java.lang.String
}

上面我们通过as来进行强制类型转换,一旦转换失败则会抛出异常,我们可以使用as?来进行安全的类型转换,通过在as后添加一个**?**即转换失败可以返回空,不会抛出异常。

fun main(){
  val a = 88
  val b = a as? String   //将a强制转换为String类型,如果失败返回null
  println(b)             //null
}

NULL检查机制(!! /?)

?- 表示当前对象可以为空,使用在变量类型后面
!! - 表示当前对象不为空,使用在变量类型后面

class School{
    fun setSchoolName(name:String?){
    }
}

kotlin 的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式

  • 字段后加!! 像java一样抛出空指针异常
  • 字段后加 可不做处理返回null或者配合 ?:做空判断处理
//类型后加 ? 表示可以为空
var age:String? = "23"
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//配合 ?: 返回 -1(如果age为空的)
val ages2 = age?.toInt()?:-1

详细使用见 Kotlin 中?!!区别

Kotlin中延迟初始化

kotlin中属性在声明的同时要求被初始化,否则会出错

var name1:String             //错误,未初始化
var name2:String = "zcmain"  //正确
var name3:String? = null     //正确,可以为空   

但是有时候,我们并不想在声明一个类型可为空的对象,而且也不想对象一声明的时候就为他初始化,此时就需要用到Koltin提供的延迟初始化了

lateinit var

lateinit var作用也比较简单,就是让编译器在检查时不要因为属性变量未初始化而报错。

lateinit var特点

  • 不能修饰基本数据类型变量
  • 不能修饰只读(val)类型变量,只能修饰var变量
  • 不能修饰可空类型变量
lateinit var name:String          //正确
lateinit var age:Int              //错误,lateinit不能修饰基本数据类型变量
lateinit val address:String       //错误,lateinit不能修饰只读类型变量
lateinit var email:String? = null //错误,lateinit不能修饰可空类型变量
by lazy

by lazy本身是一种属性委托。属性委托的关键字是byby lazy要求属性声明为val,即不可变变量,在java中相当于被final修饰。

by lazy特点

  • 只能作用于val关键字标注的属性
  • 当属性用到的时候才会初始化lazy{}里面的内容。而且再次调用属性的时候,只会得到结果不会再次执行lazy{}里面的运行过程
//用于属性延迟初始化
val name:Int by lazy{1}

//用于局部变量延迟初始化
fun test(){
    val name by lazy{"zcmain"}
    println(name)
}

扩展

@JvmField@JvmStatic区别

@JvmField仅注解非private变量,编译后生成无getter/setter方法的public变量(非@JvmField注解的变量编译后会生成包含getter/setter方法的private变量);

@JvmStatic仅在object声明的类companion obj伴生对象中常用于方法的注解,编译后生成对应的静态方法(如果注解目标是变量,会生成包含静态getter/setter的方法和静态私有(private)变量)

代码案例

  • 在Object 声明的类中使用

    object 修饰的类为单例类,编译后里面的变量都为静态的。

    kotlin代码:

    object Person {
      
        //无任何注解修饰的变量(编译后为private静态变量,并生成getter/setter方法)
        var address:String="ShangHai"
      
        //使用@JvmField注解变量(编译后为public静态变量,不会生成getter/setter方法)
        @JvmField
        var name: String = "zcmain"
      
        //使用@JvmStatic注解变量(编译后为privte成静态变量,并会生成静态getter/setter方法)
        @JvmStatic
        var age: Int = 20
    
        //使用@JvmStatic注解的函数(转换成静态方法)
        @JvmStatic
        fun getInfo(): String {
            return name + age + address
        }
    }
    

    转换成Java代码

    public final class Person {
      
       //无任何注解修饰的变量,转换成了private静态变量,并且生成了getter/setter方法
       private static String address;
       public final String getAddress() {return address;}
       public final void setAddress(String var1) {address = var1;}
      
       //@JvmField修饰变量,转换成了public静态变量(没有生成getter/setter方法)
       public static String name;
      
       //@JvmStatic修饰的变量,转换成了private静态变量,并且生成静态的getter/setter方法
       private static int age;
       public static final int getAge() {return age;}
       public static final void setAge(int var0) {age = var0;}
      
       //@JvmStatic注解的方法,转换成了静态方法
       public static final String getInfo() {return name + age + address;}
    
       //obj声明类,转换成了单例类
       public static final Person INSTANCE;
      
       private Person() {}
    
       static {
          Person var0 = new Person();
          INSTANCE = var0;
          name = "zcmain";
          age = 20;
          address = "ShangHai";
       }
    }
    
  • 在Class声明的类中使用

    kotlin代码

    class Person {
    
        //无任何注解修饰的变量(编译后生成包含getter/setter方法的private私有变量)
        var address:String="ShangHai"
    
        //使用@JvmField注解变量(编译后生成无getter/setter方法的public公开变量)
        @JvmField
        var name: String = "zcmain"
    
        /**
         * @JvmStatic只能用在object声明的类,和companion obj伴生对象中,在class声明的类中无法直接使用
         * 伴生对象相当于Java中的静态区域,内部声明的变量和方法都会编译为静态变量和方法
         */
        companion object{
            //伴生对象内无任何注解修饰的变量(编译后生成包含getter/setter方法的private私有静态变量)
            var address1:String="ShangHai1"
    
            //伴生对象内使用@JvmField注解变量(编译后生成无getter/setter方法的public公开静态变量)
            @JvmField
            var name1: String = "zcmain"
    
            //使用@JvmStatic注解变量(编译后生成包含静态getter/setter方法和private私有静态变量)
            @JvmStatic
            var age: Int = 20
    
            //使用@JvmStatic注解方法(编译后生成对应的静态方法)
            @JvmStatic
            fun test(){}
        }
    }
    

    转换成Java代码

    public final class Person {
       //无任何注解修饰的变量(编译后生成包含getter/setter方法的private私有变量)
       private String address = "ShangHai";
       public final String getAddress() {return this.address;}
       public final void setAddress(@NotNull String var1) {this.address = var1;}
      
       //使用@JvmField注解变量(编译后生成无getter/setter方法的public公开变量)
       @JvmField
       public String name = "zcmain";
      
       /**
        *伴生对象内无任何注解修饰的变量(编译后生成包含getter/setter方法的private私有静态变量)
        *get/set方法在下方静态类Companion内
        */
       private static String address1 = "ShangHai1";
      
       //伴生对象内使用@JvmField注解变量(编译后生成无getter/setter方法的public公开静态变量)
       @JvmField
       public static String name1 = "zcmain";
       
       //使用@JvmStatic注解变量(编译后生成包含静态getter/setter方法和private私有静态变量)
       private static int age = 20;
       public static final int getAge() {return age;}
       public static final void setAge(int var0) {age = var0;}
    
       //使用@JvmStatic注解方法(编译后生成对应的静态方法)
       @JvmStatic
       public static final void test() {
          Companion.test();
       }
    
       //companion object伴生对象编译称为了静态内部类
       public static final class Companion {
          @NotNull
          public final String getAddress1() {return Person.address1;}
    
          public final void setAddress1(@NotNull String var1) {
             Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
             Person.address1 = var1;
          }
         
          public final int getAge() {return Person.age;}
    
          public final void setAge(int var1) {Person.age = var1;}
    
          @JvmStatic
          public final void test() {}
    
          private Companion() {}
    
          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }
    

@file:JvmName重命名文件

在kotlin文件中,如果该文件包含顶级函数或者属性(即文件顶级是函数或者属性,而不是class声明的类),例如:MyKotlin.kt,那么我们如果在Java代码中使用会被自动编译为以MykotlinKt名称的文件,如果我们不想使用这个默认生成的文件名,可使用@file:JvmName("文件名")来对该文件重新命名

例如,创建一个顶级为函数名为StringUtil.kt文件,在Java中调用会被编译成名为StringUtilKt

//通过@file:JvmName("")来指定一个合适的名字
@file:JvmName("StringUtil")
fun head(str:String){
  //TODO
}

如果想将多个文件中顶级成员(函数或属性)组合到一个类中,可使用@file:JvmMultifileClass("多个文件指定同一个名称")来实现

例如:StringUtil1.kt

@file:JvmMultifileClass     
@file:JvmName("StringUtil")
fun test1(){}

StringUtil2.kt

@file:JvmMultifileClass     
@file:JvmName("StringUtil")
fun test2(){}

以上两个包含顶级函数的文件,通过使用@file:JvmMultifileClass@file:JvmName("StringUtil")注解,会将顶级成员合并生成一个名为StringUtil的类,使用该类可调用test1()、test2()函数

const关键字

  1. const只能修常量val
  2. const只能在object类或者companion object伴生对象中使用
  3. const效果等于@JvmField,两者不能同时使用

const val 与 val区别

  1. const val 修饰的常量会被转换成 public static final,java中可直接访问
  2. val修饰的常量会被转换成private static finalgetXXX()形式,java中需要通过getXXX访问

Kotlin代码

object Test3 {

    val age1:Int = 20

    const val age2:Int = 30

}

转换成Java代码

public final class Test3 {
   //val常量被转换成private static final 和 getXXX()方法,通过getXXX()访问常量
   private static final int age1 = 20;
   public final int getAge1() {return age1;}
  
  //const val常量直接被转换成 public static final 可直接访问
   public static final int age2 = 30;
  
   public static final Test3 INSTANCE;

   private Test3() {
   }

   static {
      Test3 var0 = new Test3();
      INSTANCE = var0;
      age1 = 20;
   }
}

?!!区别

?!!操作符都是Kotlin中提供的变量非空检测

用法

  • ? 与**?.**

    1. 定义变量时候,用在变量后面,表示允许该变量为空;

      //定义name变量,允许为空值
      var name:String?="zcmain"
      name = null //给name赋空值
      
    2. ?.使用变量时候添加在变量后面,表示非空则执行,为空返回null(注意:虽然你该运算符不会抛出异常,但是会把null值传递给下一个语句,因此可使用?:默认值来替代返回的空值),并且继续执行后续代码(永远不会抛出异常,也称为安全调用运算符

      //如果name为空跳过trim()方法
      name?.trim()
      
      //等价于Java中的
      if(name !=null){
        name.trim();
      }
      
  • !!

    1. 在变量使用时候添加在变量后面!!,它会将其左侧的所有内容视为非 null,因此,如果它左侧表达式的结果为 null,则您的应用会抛出 NullPointerException。此运算符简单快捷,但应谨慎使用,因为它会将 NullPointerException 的实例重新引入您的代码。也称不安全的调用运算符)

      var str:String?=null
      //Kotlin
      str!!.trim()
      
      //等同于Java 
      if(str != null){
        str.trim()
      }else{
        throw new KotlinNullPointException();
      }
      

代码案例

object Main{
  
     @JvmStatic
     fun main(args:Array<String>){
     		test(null) //调用test方法,传入null参数
     }
  
    fun test(str1: String?) {

        var temp1 = str1?.trim()
        println("继续执行了str1?.trim()后续逻辑....$temp1")
      
        //为空设置返回指定值
        var temp2 = str1?.trim()?:"default"
        println("继续执行了str1?.trim()后续逻辑,为空返回指定值....$temp2")

        var temp3 = str1!!.trim()
        println("继续执行了str1!!.trim()后续逻辑....$temp3")
      }
}

//执行结果
继续执行了str1?.trim()后续逻辑....null
继续执行了str1?.trim()后续逻辑,为空返回指定值....default
Exception in thread "main" kotlin.KotlinNullPointerException
	at com.test.myapplication.testkottlin.Main.test(Main.kt:29)
	at com.test.myapplication.testkottlin.Main.main(Main.kt:15)

总结:

如果变量为空,?.不会抛出异常不会执行变量的对应方法,而是返回null或者指定值并继续执行后续代码,!!则会直接抛出异常,终止后续代码

Kotlin中let、with、run、apply、also函数

kotlin提供了一些功能强大的内置函数如:let,with,run,apply,also等等。

  • let函数

    • let函数是一个作用域函数,让定义的变量在特定的作用域范围内执行操作,统一的null判断处理;

    • 返回值是最后一行(作用域内的it代表变量本身)。

      // 使用Java
      if( mVar != null ){
          mVar.function1();
          mVar.function2();
          mVar.function3();
      }
      
      // 使用kotlin(无使用let函数)
      mVar?.function1()
      mVar?.function2()
      mVar?.function3()
      
      // 使用kotlin(使用let函数)。方便了统一判空的处理 & 确定了mVar变量的作用域
      mVar?.let {
             it.function1()
             it.function2()
             it.function3()
      }
      
      
      // let函数返回值是最后一行(result= 999)
      var result = mVar.let {
                     it.function1()
                     it.function2()
                     it.function3()
                     999
      }
      
      
  • also函数

    • 定义:also也是作用于函数,与let函数类似,唯一区别是返回值(let函数返回值是最后一行;also函数值是传入对象本身)。

      // also函数返回值是传入对象本身(result = mVar)
      var result = mVar.also {
                     it.function1()
                     it.function2()
                     it.function3()
                     999
      }
      
  • with函数

    • 需要调用同一个对象的多个方法 / 属性时,省略对象名,直接调用方法/属性名即可;

    • 返回值是作用域(函数块)最后一行

      //格式
      with(object){
         //直接使用object内的方法或者属性即可不需要调用object来访问
      }
      
      /**
       *示例
       */
      
      //java需要通过people来访问对象属性和方法
      User people = new People("carson", 25);
      String var1 = "my name is " + people.name + ", I am " + people.age + " years old";
      System.out.println(var1);
      
      //kotlin使用with函数在代码块内无需通过people对象来访问属性和方法,直接访问即可
      val people = People("carson", 25)
      with(people) {
      	  println("my name is $name, I am $age years old")
      }
      
      
  • run函数

    • 结合了letwith函数,即约定了变量的作用域和统一的null判断处理,以及在代码块内访问变量属性/方法不需要指定变量

    • 返回值是代码块内最后一行

      // kotlin
      val people = People("carson", 25)
      // 此处要调用people的name 和 age属性,且要判空
      people?.run{
          println("my name is $name, I am $age years old")
      }
      
  • apply函数

    • 与run函数类似,但区别在于返回值(run返回代码块最后一行,apply返回传入的对象本身)

      // run函数,返回值是最后一行(result = 999)
      val people = People("carson", 25)
      val result = people?.run{
          println("my name is $name, I am $age years old")
          999
      }
      
      // apply,返回值是传入对象本身(result = people)
      val people = People("carson", 25)
      val result = people?.apply{
          println("my name is $name, I am $age years old")
          999
      }
      
      
  • 总结
    在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值