以写Demo的方式,最快学习Kotlin(1)

71 阅读9分钟

本系列亮点在于以写出demo为目的来学习Kotlin,其中也尽量会讲一些kotlin的原理、性能等,但本人初学习Kotlin,非常稚嫩;如果有遗漏、错误望大家不吝指教,我会尽快修改。

继承和实现

ps:写Demo自然是要先有个页面展示,所以先说一下继承Activity和实现接口

继承和实现的符号( : )

class MainActivity : AppCompatActivity(), View.OnClickListener {

    var textView: TextView = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        textView = findViewById(R.id.text)
    }

    override fun onClick(p0: View?) {
        // 注意:这个todo要删除,否则运行起来执行到todo后会carch
        TODO("Not yet implemented")
    }
}

没错,相比于 java 省去了 extendsimplements 直接使用 : 就可以了,继承和实现的区别就是继承是个类所以后面要带 (),而实现的是接口后面就不需要 ()

方法参数中的接口实现

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        test("test", object : View.OnClickListener{
            override fun onClick(veiw: View?) {
                
            }
        })
    }
    
    fun test(string: String, listener:View.OnClickListener) {
        val text = TextView(this)
        text.text = string
        text.setOnClickListener(listener)
    }
}

提前透露一下Kotlin中新建对象并不是使用 new 的方式,这边的接口传参是不是更像创建了一个obj来实现接口的。

成员变量

valvar 定义成员变量

  • val:固定死的值,不能更改,但是注意这和java里的final并不一样,虽然功能上来说都不能修改
  • var:可以修改的变量
val str1: String = "111"
var str2: String = "222"

// str1 不能更改值
str2 = "333"

声明变量类型

这里说一下 : 的另一个作用,即声明变量类型

var str: String = "123"
var i: Int = 12
var l: Long = 12L
var f: Float = 12.0f

当然 Kotlin 有很智能的判断,可以根据你的赋值来自动判断变量的类型,但是在方法定义参数时,还是要使用的。比如:

var str = "123"
var l = 12L
var f = 12.0f

fun test(str: String) {
   // ...
}

声明变量可以为空

  • 没错,变量如果不设置可以为空的话,设置成null是会爆红的。
  • 声明可为空很简单,在声明的类型后面加上 ? 就行。
  • 但是在使用变量的时候也会让你判断是否为空,不判断也会爆红
  • ? 也有判断是否为空的功能,在变量后加入,如果为空就不会再执行
  • 也可以使用 !! 判断,但是必须要确定该变量不会为空,否则会执行导致carsh
class MainActivity : AppCompatActivity() {
    var str1:String = "ss"
    var str2:String? = "ss"
    var textView:TextView? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        str1 = null // 爆红
        str2 = null
        
        textView.text = "ss" // 爆红
        textView?.text = "ss" // 为null,也不会crash
        textView!!.text = "ss" // 会执行,因为为null,导致crash
    }
}

valvar 稍后赋值

有了页面自然应该到view的实例化了,但是你写变量的时候需要先赋值为null,等setContentView结束了后才能实例化,这样每次在用的时候都要先判断一下是不是空的。所以稍后赋值就诞生了。

  • lateinit var 只能设置为var,这样可以不用先赋值为null,但是在没有赋值直接使用是,会carsh。
  • by lazy {} 只能给val使用,lazy 相当于变量写了个单例方法。
class MainActivity : AppCompatActivity() {
    lateinit var textView:TextView
    
    val textView2: TextView by lazy {
        Log.d(TAG, "textView2 实例化")
        findViewById(R.id.text2)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        textView = findViewById(R.id.text)
        textView.text = "111"
        
        
        Log.d(TAG, "textView2 还没有实例化")
        textView2.text = "222"
        
        Log.d(TAG, "textView2 会再次实例化吗?")
        textView2.text = "333"
    }
}

执行的结果是:

textView2 还没有实例化
textView2 实例化
textView2 会再次实例化吗?

静态变量、静态方法

类中的静态变量、方法

class MainActivity : AppCompatActivity() {

    companion object {
        const val KEY: String = "key_str"
        var str: String = "ss"
        
        fun test() {
            Log.e("tag", "--------test")
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

里面的 KEYstaticValue 就是静态变量了,而 const 才是代表的是 fianl

使用方式和java没有什么区别

class TextActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        MainActivity.KEY
        MainActivity.str
        MainActivity.test()
    }
}

工具类式静态方法

object Util {

    const val KEY:String = "sss"
    var str:String = "aaa"

    fun test1() {
        test2(KEY)
        test2(str)
    }

    private fun test2(string: String) {
        Log.e("tag", string)
    }
}

只是将 class 换位 object 里面的变量、方法就都变为了静态的。使用也和之前没有差别:

class TextActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        Util.KEY
        Util.str
        Util.test1()
    }
}

@JvmStatic@JvmName 的使用

静态变量和方法在 kotlin 代码中使用没什么好说的,但是在 java 中使用却有点麻烦了

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String str = Util.KEY;
        str = Util.INSTANCE.getStr();
        Util.INSTANCE.Test1();
        
        str = MainActivity.KEY;
        str = MainActivity.Companion.getStr();
        MainActivity.Companion.test();
        
    }
}

由此看出 const 是代表 final 的原因了,有其修饰可以直接被 java 使用。但是静态变量是var或者是方法是不能使用 const 的。有没有办法让 java 也同样方便使用呢?当然有了:

object Util {

    const val KEY = "ss"

    @JvmField
    var str = "ss"

    @JvmStatic
    fun Test1() {
        // ...
    }

}

class MainActivity : AppCompatActivity() {
    companion object {
        const val KEY ="aaa"
        @JvmField
        var str = "sss"

        @JvmStatic
        fun test() {
            // ...
        }
    }
}

经过 @JvmField 修饰变量、 @JvmStatic 修饰方法后,java 也可以一样的方式使用了

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        String str = Util.str;
        Util.Test1();

        str = MainActivity.KEY;
        str = MainActivity.str;
        MainActivity.test();
    }
}

方法的扩展

kotlin的方法有个有意思的写法,使用起来很方便,缺点是不能和 java 共用

object Util {

    @JvmStatic
    fun Int.dp2px():Int {
        return this * 2
    }
}

// 使用
val px = 12.dp2px()
// java 使用
Util.dp2px(12);

方法名前带上基础类型,在使用这些基础类型值时直接使用方法名;方法中使用this来代表该值;感觉方便了简直不要太多。

数组、Map

这里先介绍我们常用的:[]、List、Map。[]提供有很多容器,比如:IntArrayLongArrayBooleanArrayChatArray等,用法大体相似,就使用 IntArray 来距离说明

创建

// 可以定义数组类型
val array = arrayOf<String>("111", "111")

// 已经指定容器
val array2 = intArrayOf(1,2,3,4)

// 直接实例化,但是要确定长度,下为 size = 3
val array3 = IntArray(3)

// 直接实例化,需要确定长度,并赋值,下为将所有赋值为abc
val array4 = Array<String>(3){"abc"}

// 根据下标进行赋值
val array5 = Array<String>(3){index ->
    if (index == 1) {
        "abc"
    } else {
        "def"
    }
}

// ------------------ list --------------------

// 不可更改的数组
val list = listOf("1", "1")

// 可更改的数组
val list2 = arrayListOf("12", "12")
// 其实点进去看看实现的也是ArrayList
val list3 = mutableListOf("1", "12")

// 直接实例化,和上面实例化Array是一样的,就不多说了,区别就是list的长度是不会固定死的
val list4 = MutableList<String>(3){"abc"}

// 重点,下面有解释
val list5 = ArrayList<String>()

// ------------------ map --------------------

// 不可改变的map
val map = mapOf<String, String>("key" to "value")

// 可更改的map
val map2 = hashMapOf("key" to 1, "key2" to 2)
// 注意:这里面实现的不是hashMap了,而是LinkedHashMap
val map3 = mutableMapOf("key" to 1, "key2" to 2)

// 直接实例化,和上面的重点其实是一样的
val map4 = HashMap<String, String>()
// 直接实例化,和上面的重点其实是一样的
val map5 = LinkedHashMap<String, String>()

从上面的规律可以看出来带有 of 的,其实就是Kotlin给我们的福利,可以更方便的实例化;也可以直接使用类实例化,应该没人放弃使用便利的方式吧。

有看到上面标了 重点 的地方吧,可以点击进去看看里面是什么:TypeAliases.kt

@SinceKotlin("1.1") public actual typealias ArrayList<E> = java.util.ArrayList<E>
@SinceKotlin("1.1") public actual typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
@SinceKotlin("1.1") public actual typealias HashMap<K, V> = java.util.HashMap<K, V>
@SinceKotlin("1.1") public actual typealias LinkedHashSet<E> = java.util.LinkedHashSet<E>
@SinceKotlin("1.1") public actual typealias HashSet<E> = java.util.HashSet<E>

这实际就是告诉我们 kotlin 的这些数组,其实使用的都是 java 的数组。所以不要担心使用 kotlin 数组的时候与 java 数组不匹配。比如 IntentBundle 传值时

操作

// 与java一样,注意数组越界
array[0] = "234"

// list和array这些不可变的也是有办法添加item的。原理是新建数组,并添加新的item
array.plus("123")
list.plus("def")

// 添加
list2.add("abc")
// 修改,根据下标修改,也可以使用set不流行了嘛
list2[2] = "def"

// 不流行了
map2.put("key3", 3)
// 流行的方式
map2["key3"] = 3
// 删除
map2.remove["key3"]

遍历


// ------------------- in ---------------------

for (i in array) {
    println(i)
}

// 遍历 list
for (i in list) {
    println(i)
}

// 遍历 map
for (en in map.entries) {
    println("key: ${en.key}, value: ${en.value}")
}
//通过key遍历值
for (key in map.keys) {
    println("key: ${key}, value: ${map[key]}")
}
//key value 遍历
for ((key, value) in map) {
    println("key: $key, value: $value")
}
// 那个值不需要,可以使用_省略
for ((_, value) in map) {
    println("value: $value")
}

// ------------------- 通过index遍历 ---------------------

// array 和 list 都可以使用的方式,就不多写了

// 完全可以简化成下面的index遍历,但是这个可以控制从哪里遍历,也可以倒着遍历等等。
for (index in 0 until array.size) {
    println(array[index])
}

for (index in list.indices) {
    println(list[index])
}

// ------------------- for循环遍历 ---------------------

list.forEach {
    println(it)
}

map.forEach { (key, value) ->
    println("key: $key, value: $value")
}

数组转换

//集合转数组
list.toTypedArray()

//数组转集合
array.toList()

for 循环

for (i in 1 .. 10){ // 闭区间 包含首尾数字 相当于[1,10]
   println("i=" +i)
}
// 输出结果:12345678910

for (x in 1 until 10) { // 前闭后开区间 包含首位但不包含最后一位 相当于[1,10)
   println("x=" +x)
}
// 输出结果:123456789

for (y in 1 .. 10 step 2){// 在闭区间内按给定步长(step)输出数据
   println("y=" +y)
}
// 输出结果:13579

for (a in 10 downTo 1){ // 函数倒序输出数据,因为..和until都只能实现正序,即以小到大
   println("a=" +a)
}
// 输出结果:10987654321

数据对象

kotlin中一般创建数据对象一般都是使用 data calss 了。

好处

省略写get、set、toString、equals、hashCode、copay等方法。

data class DataBean(
    val str: String,
    val sex: Int
)

dataBean = DataBean("ss", 1)
// ------java-------
dataBean.getStr()
// ------kotlin------
dataBean.str

这样写data class连构造方法也省略了,但是带来一个问题就是不能构造无参的构造方法。想要实现无参的构造方法的话就要给所有的参数指定默认值才可以。

注意:如果参数没有默认值,构造方法里必须填入没有的值。

data class DataBean(
    val str: String = “”,
    val sex: Int = 0
)

dataBean = DataBean()
dataBean = DataBean(str = "ss") // 参数可以随意填写,构造方法变的更灵活了。

//------

data class DataBean(
    val str: String? = null, // 也可以定义参数默认值为null
    val sex: Int = 0,
    val name: String,
    val color: Int
)

// 这种情况就不能无参构造了
dataBean = DataBean(name = "ss") // 报错,因为没有给color赋值
dataBean = DataBean(name = "ss", color = 0) // 必须指定赋值给name和color,否则就要使用4个参数来构造了

坏处:没有办法继承:data class 可以继承其他的类,但是没办法继承 data class 的类。所以有建立BaseBean的情况就还是要使用普通的class创建。就像java一样自己补充get、set、toString、equals呗;对了想要被继承,需要使用 open 装饰

open class BaseBean {
    open var str:String? = null // 代表可以将该字段重写
    
    open fun test() {
    }
}

兼容java


data class DataBean @JvmOverloads constructor(
    @JvmField // 将字段改为public类型,消除了get、set
    val str: String = "null",
    @get:JvmName("getStudentSex") // 更改java的get、set名字
    @set:JvmName("setStudentSex")
    var sex: Int = 0,
)

//----------java--------

// 因为@JvmOverloads,实现了多个构造函数,自定义过view的人应该很熟悉
dataBean = DataBean("ss") 
dataBean = DataBean("ss", 2) 
dataBean = DataBean(2) // 报错,只能有顺序的增加参数,就行自定义view的构造那样

dataBean.str = "111";
dataBean.getStudentSex();
dataBean.setStudentSex(11);