本系列亮点在于以写出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 省去了 extends
、implements
直接使用 :
就可以了,继承和实现的区别就是继承是个类所以后面要带 ()
,而实现的是接口后面就不需要 ()
。
方法参数中的接口实现
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来实现接口的。
成员变量
val
和 var
定义成员变量
- 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
}
}
val
和 var
稍后赋值
有了页面自然应该到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)
}
}
里面的 KEY
和 staticValue
就是静态变量了,而 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。[]提供有很多容器,比如:IntArray
、LongArray
、BooleanArray
、ChatArray
等,用法大体相似,就使用 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 数组不匹配。比如 Intent
、Bundle
传值时
操作
// 与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);