学习自码上开学
推荐Kotlin 在Android开发中那些让人舒适的地方
1.添加Kotlin依赖
- 项目根目录下的 build.gradle
buildscript {
ext.kotlin_version = '1.3.50'
...
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
- app目录下的 build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
...
dependencies {
...
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
2.变量
- 变量的声明与赋值
var v: View
属性需要在声明的同时初始化,除非你把它声明成抽象的
解决办法:
1.加 lateinit
关键字,这个变量的初始化就全靠你自己了,编译器不帮你检查了(不适用Int,Long原始类型,原始类型用Delegates.notNull
)
lateinit var view: View
override fun onCreate(...) {
...
view = findViewById(R.id.tvContent)
}
//或者
class App : Application() {
companion object {
var instance: App by Delegates.notNull()
}
2.在类型右边加一个 ? 号,解除它的非空限制,就像 Java 变量一样没有非空的限制了
var view: View? = null
view?.setBackgroundColor(Color.RED)
确保view不是null的情况下调用,否则它会抛出异常
view!!.setBackgroundColor(Color.RED)
空安全调用 ?.
,在对象非空时会执行后面的调用,对象为空时就会返回 null
val str: String? = "Hello"
var length: Int = str?.length//IDE 报错,Type mismatch. Required:Int. Found:Int? (str?.length返回一个null给length)
是null的情况下的替代值
val name = artist?.name ?: "empty"
- is来判断变量类型,类似Java中instanceOf
fun judgeType(obj: Any) {
if (obj is String) {
println("传入的是String类型")
// 做过类型判断以后,obj会被系统自动转换为String类型
println("字符串长度为${obj.length}")
} else if (obj is Int) {
println("传入的是Int类型")
} else if (obj is Double) {
println("传入的是Double类型")
}
}
- 两个数值的比较 : 判断两个数值是否相等(==),判断两个数值在内存中的地址是否相等(===)。
3.函数
- 函数的声明
fun cook(name: String): Food {
...
}
Kotlin 里是返回 Unit,并且可以省略:
// Unit 返回类型可以省略
fun main() {
}
函数参数也可以有可空的控制,可空变量传给不可空参数,报错
// 可空变量传给不可空参数,报错
var myName : String? = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName)
// 可空变量传给可空参数,正常运行
var myName : String? = "rengwuxian"
fun cook(name: String?) : Food {}
cook(myName)
// 不可空变量传给不可空参数,正常运行
var myName : String = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName)
可以给参数指定一个默认值使得它们变得可选
fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, length).show()
}
//==============================调用=======================================
toast("Hello")
toast("Hello", Toast.LENGTH_LONG)
扩展函数
扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权 限
//它可以被任何Context或者它的子类调用,比如Activity或者Service
fun Context.toast(message: CharSequence, duration: Int = Toast.L
ENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
//这个方法可以在Activity内部直接调用:
toast("Hello world!")
toast("Hello world!", Toast.LENGTH_LONG)
4.类型
-
数组
- 数组的创建
arrayOf()
: 参数是一个可变参数的泛型对象。为元素是基本类型的数组增加了xxArray类(其中xx也就是Byte,Short, Int等基本类型)//类似java中: int[] array = new int[] {1, 2}; var array: IntArray = intArrayOf(1, 2) var array2 = arrayOf("0","2","3",'a',32.3f)
arrayOfNulls()
:用于创建一个指定数据类型且可以为空元素的给定元素个数的数组var arr3 = arrayOfNulls<Int>(3)
工厂函数Array()
:Array() => 第一个参数表示数组元素的个数,第二个参数则为使用其元素下标组成的表达式var arr4 = Array(5,{index -> (index * 2).toString() }) for (v in arr4){ print(v) print("\t") } //输出结果为: //0 2 4 6 8
- 数组的遍历
val items = arrayOf(1, 2, 3) //遍历value for (value in items){ print("${value} ") } println() //遍历index for (index in items.indices){ print("${index} ") } println() //遍历index和item for ((index,value) in items.withIndex()){ println("index: ${index} value: ${value} ") }
- 数组的过滤。
filter:
对每个元素进行过滤操作,如果 lambda 表达式中的条件成立则留下该元素,否则剔除,最终生成新的集合
val intArray = intArrayOf(1, 2, 3) // [1, 2, 3] //注意,这里变成了 List。结果为 {2, 3} val newList: List = intArray.filter { i -> i != 1 // 过滤掉数组中等于 1 的元素 }
map:
遍历每个元素并执行给定表达式,最终形成新的集合
val intArray = intArrayOf(1, 2, 3) // [1, 2, 3] //最后结果为 {2, 3, 4} val newList: List = intArray.map { i -> i + 1 //每个元素加 1 }
flatMap:
遍历每个元素,并为每个元素创建新的集合,最后合并到一个集合中
val intArray = intArrayOf(1, 2, 3) // [1, 2, 3] //最后结果 {"2", "a" , "3", "a", "4", "a"} intArray.flatMap { i -> listOf("${i + 1}", "a") //生成新集合 }
- 数组的创建
-
集合
- list集合的创建
listOf(); 返回不可变的集合(内部用ArrayLit实现,返回的list是只读的) listOfNull(); 返回不可变集合,和前一个函数的唯一的区别是,该函数会自动去掉传入的null,也就是说返回的时候不会返回null,会过滤掉。 mutableListOf(); 该函数返回可变的MutableListOf()集合 arrayListOf();同java的ArrayList //数组转集合 val arr = arrayOf("1",2,3,4) val mutableList = mutableListOf(arr)
- set集合的创建
setOf();该函数返回不可变的Set集合,该集合可以接收0个或过个参数,这些参数将作为集合的元素。 mutableSet0f():该函数返回可变的MutableSet集合, hashSetOf():该函数返回可变的HashSet集合, linkedSetOf():该函数返回可变的LinkedHashSet集合。 sortedSetOf():该函数返回可变的TreeSet集合, 用法同下。
-
map类型的创建
mapOf():不可变的Map类型集合的初始化使用 mutableMapOf():可变的Map类型集合的初始化使用 hashMapOf():同Java中的HashMap
- map的遍历
val map = mapOf("key1" to 2 , "key1" to 3 , "key1" to "value1" , "key2" to "value2") map.forEach{ key,value -> println("$key \t $value") }
- map的遍历
-
总结
-数组:
arrayOf(...)
不可变集合:
listOf(...),
mapOf(...),
setOf(...) ,
...
可变集合:
mutableListOf(...),
mutableMapOf(...),
mutableSetOf(...),
...
集合间相互转换:
toList(),
toSet(),
toHashSet(),
toMutableList(),
toSet(),
toIntArray(),
...
5.类和对象
:
不仅可以表示继承,还可以表示 Java 中的 implement
//继承类AppCompatActivity,同事实现了接口 Impl
class MainActivity : AppCompatActivity(), Impl {}
- Kotlin 里的类默认是 final 的,使用
open
可以解除 final 限制
open class MainActivity : AppCompatActivity() {}
//这样一来,我们就可以继承了。
class NewActivity: MainActivity() {}
- Kotlin也有和 Java 一样的
abstract
关键字,这俩关键字的区别就是 abstract 关键字修饰的类无法直接实例化(除非使用object关键字修饰) - 在Kotlin中,允许有一个主构造函数和多个二级构造函数(辅助构造函数)。Kotlin 中使用 constructor 表示构造函数,其中主构造函数是类头的一部分,
- 主构造函数
class Test constructor(num : Int){ ... }
- 如果在主构造器的参数声明时加上 var 或者 val,就等于直接声明了属性
class User(var name: String) { } // 等价于: class User(name: String) { var name: String = name }
- 辅助(二级)构造函数,以constructor关键字作为前缀。使用this关键字对同一类的另一个构造函数进行委派
class Test constructor(num: Int){ ... constructor(num : Int, num2: Int) : this(num) { println(num + num2) } }
- init代码块中的代码,编译后会按顺序放在构造函数中,并且先于构造函数中原来的代码执行
class Person(name: String) { init { println("init---" + name); } constructor(name: String, age: Int) : this(name) { println("constructor---" + name); } } //别处调用 var person = Person("test") //输出顺序 先给name赋值--> init---test--> constructor---name
- 类的实例化,kotlin没有Java的new关键字
var test = Test() var test1 = Test(1,2)
- 不加可见性修饰符的话, 类,方法默认是public的
- get和set函数。
class User {
var name = "Mike"
fun run() {
name = "Mary"// 实际上是这么调用的setName("Mary")
println(name)//法实际上是这么调用的print(getName())
}
}
所以下面打印出name属性值时,默认调用了他的get()方法,输出了testhaha
class Person() {
var name = "test"
get() {
return field + "haha"
}
}
fun main(args: Array<String>) {
print(Person().name);
}
- object(单例类型)修饰类时,这个类的所有所有变量相当于静态变量,所有方法相当于静态方法。相当于创建了一个
单例类
object Sample { val name = "A name" fun printName()=println(name) } //调用时,直接相当于java中static修饰后调用 Sample.name Sample.printName()
- 在一个普通类里,其内部类用object修饰后,相当于创建了他的静态内部类,让类中的一部分函数和变量是静态的
class A { object B { var c: Int = 0 } } //调用 A.B.c
- 类中嵌套的对象可以用 companion 修饰,此时对象的名字也可以省略掉。此时就可以像 Java 一样通过类直接引用
class A { companion object { var c: Int = 0 } }
- 在一个普通类里,其内部类用object修饰后,相当于创建了他的静态内部类,让类中的一部分函数和变量是静态的
- 顶层声明:就是把属性和函数的声明不写在 class 里面。这样写的属性和函数,不属于任何 class,而是直接属于 package,它和静态变量、静态函数一样是全局的
package com.hencoder.plus
// 属于 package,不在 class/object 内
fun topLevelFuncion() {
}
//使用
import com.hencoder.plus.topLevelFunction //直接 import 函数
topLevelFunction()
命名相同的顶级函数 : 当出现两个同名顶级函数时,IDE 会自动加上包前缀来区分,这也印证了「顶级函数属于包」的特性。
println()
就是kotlin添加的一个顶级函数
- const val:相当于Java中的 static final。不过 声明在对象(包括伴生对象)或者「top-level 顶层」中,只有基本类型和 String 类型可以声明成常量
6.范型
编码的时候用符号来指代类型,在使用的时候,再确定它的类型
6.1 Java中范型
TextView textView = new Button(context);//这是多态,编译器不会报错
List<Button> buttons = new ArrayList<Button>();
//由于类型擦除,多态用在这里会报错 incompatible types: List<Button> cannot be converted to List<TextView>
List<TextView> textViews = buttons;
...
//在 Java 里用数组做类似的事情,是不会报错的,这是因为数组并没有在编译时擦除类型
TextView[] textViews = new TextView[10];
Button[] buttons = new Button[10];
textViews = buttons;
不过Java 提供了「泛型通配符」 ? extends
和 ? super
来解决这个问题
? extends
List<? extends TextView> textViews = new ArrayList<Button>();
? super
List<? super Button> buttons = new ArrayList<TextView>();
Java中?相当于? extends Object
6.2 Kotlin中范型
- Kotlin中
out
相当于Java中? extends
- Kotlin中
in
相当于Java中? super
- Kotlin中
?
相当于Java中?
。即Kotlin中Class<*>
相当于Java中Class<?>
7.高级语法特性
- lambda表达式 : 接口只有一个回调的方法适合使用
- lambda 表达式总是被大括号括着;
- 其参数在
->
之前声明(参数类型可以省略); - 函数体在
->
后面 - 唯一一个参数,可以省略大括号
- 最后一个参数得时候,可以写到括号外
fun positiveButton(
@StringRes res: Int? = null,
text: CharSequence? = null,
click: DialogCallback? = null
): MaterialDialog {
...
}
//调用positiveButton()方法
positiveButton(text = "确认") {
//最后一个参数得时候,可以写到括号外
//点击事件
}
mView.setEventListener({
data: Data ->
//todo
})
//或者可以直接省略Data,借助kotlin的智能类型推导
mView.setEventListener({
data ->
//todo
})
//如果以上代码中的data参数没有使用到的话,可以直接把data去掉
mView.setEventListener({
//todo
})
由于setEventListener函数最后一个参数是一个函数,可以直接把括号的实现提到圆括号外面
mView.setEventListener(){
//todo
}
由于setEventListener这个函数只有一个参数,可以直接省略圆括号
mView.setEventListener{
//todo
}
图片来自Kotlin:巧用内置函数let、also、with、run、apply大大提高开发效率!
let
函数- 定义一个变量在一个特定的作用域范围内
object.let{ it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法 ... }
- 避免写一些判断null的操作
//没有使用let函数的代码是这样的,看起来不够优雅 mVideoPlayer?.setVideoView(activity.course_video_view) mVideoPlayer?.setControllerView(activity.course_video_controller_view) mVideoPlayer?.setCurtainView(activity.course_video_curtain_view) //使用let函数后的代码是这样的 //表示mVideoPlayer不为null的条件下,才会去执行let函数体 mVideoPlayer?.let { it.setVideoView(activity.course_video_view) it.setControllerView(activity.course_video_controller_view) it.setCurtainView(activity.course_video_curtain_view) }
inline
关键字:作用是提升性能,适用于代码量不多但是需要频繁调用的方法。Kotlin的inline内联函数
//创建代码块只提供 Lollipop 或者更高版本来执行:
inline fun supportsLollipop(code: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
code()
}
}
//它只是检查版本,然后如果满足条件则去执行。现在我们可以这么做:
supportsLollipop {
window.setStatusBarColor(Color.BLACK)
}
-
by lazy
:懒加载,只有用到时才会对初始化,适用于只获取,不赋值,并且多次使用的对象。lateinit
只用于变量 var,而lazy
只用于常量 valval lazyValue: String by lazy { println("computed!") "Hello" } fun main(args: Array<String>) { println(lazyValue) println(lazyValue) } 打印结果 computed! Hello Hello ``
-
run
:作用域函数- 单独的作用域,
run{ }
代码块是独立的。即我可以在run()函数中写一些和项目无关的代码,因为它不会影响项目的正常运行
fun test(){ var animal = "cat" run { //在run函数中能够重新定义一个animal变量,和上面的变量不会冲突 val animal = "dog" println(animal) // dog } println(animal) //cat }
run
函数当中它不仅仅只是一个作用域,他还有一个返回值。他会返回在这个作用域当中的最后一个对象
//根据`islogin`的值返回不同的dialog并显示 run { if (islogin) loginDialog else getAwardDialog }.show()
val index = 3 val num = run { //when关键字是一个有返回值的表达式 when(index){ 0 -> "kotlin" 1 -> "java" 2 -> "php" 3 -> "javaScript" else -> "none" } }.length println("num = $num")//3
- 使用当前对象的上下文
val str = "kotlin" str.run { println( "length = ${this.length}" )//length = 6 println( "first = ${first()}")//first = k println( "last = ${last()}" )//last = n }
- 单独的作用域,
-
with函数: with函数接收一个 T 类型的对象和一个被作为扩展函数的函数。这个方法主要是让这个t对象去执行body函数
inline fun <T> with(t: T, body: T.() -> Unit) { t.body() }
-
with函数中的参数是一个对象,我们可以带方法中直接引用对象的公有属性或者公有方法,而不用使用方法名
和Java代码做对比TextView text=(TextView)findViewById(R.id.tv_text) text.setText("hahaha") text.setTextSize(23)
with(tv_text){ text="hahaha" textSize=23 }
-
返回值为函数块的最后一行或指定return表达式
val result = with(user, { println("my name is $name, I am $age years old, my phone number is $phoneNum") 1000 }) //但是由于with函数最后一个参数是一个函数,可以把函数提到圆括号的外部,所以最终with函数的调用形式如下: val result = with(user) { println("my name is $name, I am $age years old, my phone number is $phoneNum") 1000 }
-
apply
函数 : 整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值 -
as操作符
val y = 66 //表示允许 String 可空,这样当 y = null 时,不会抛异常;但是,当类型转换失败时,还是会崩溃。直接用as不安全 val x: String? = y as String?
val y = 66 //安全的 类型转换操作符 as?,当类型转换失败时,它会返回 null,但不会抛出异常崩溃 val x: String? = y as? String println("x = $x") // x = null
8.其他
- 函数作为参数,T.()->Unit 和 ()->Unit 的区别
在代码块里面写this的时候,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例。fun <T : View> T.afterMersure(f: T.() -> Unit){ } fun <T : View> T.afterMersure2(f: () -> Unit){ }
-
return@label
return语句默认为跳出最近的函数
//直接跳出foo函数 fun foo(ints: List<Int>) { ints.forEach { if (it == 0) return println(it) } }
如果需要在lambda表达式返回有两种方法
- 把lambda表达式改为匿名函数
fun foo() { ints.forEach(fun(value: Int) { if (value == 0) return print(value) }) }
- 结合label使用
fun foo() { ints.forEach lit@ { if (it == 0) return@lit print(it) } }
- 把lambda表达式改为匿名函数