与Java的差异
常见差异
比较内容 | Kotlin | Java |
---|---|---|
this对象 | this@MainActivity | MainActivity.this |
类 | MainActivity::class.java | MainActivity.class |
继承 | class MainActivity : AppCompatActivity() 在 Kotlin 中被继承类必须被open关键字修饰(默认是final的) | public class MainActivity extends AppCompatActivity |
变量 | 不可变变量 val intent = Intent() 可变变量 var intent = Intent() | Intent intent = new Intent(); |
静态常量 | const val TEXT = “text” | static final String TEXT = “text”; |
方法 | 无返回值 fun doAction() {} 有返回值 fun getNumber() : Int { return 5} 或者 fun getNumber() = 5 | 无返回值 void doAction() {} 有返回值 int getNumber() {return 5;} |
类型判断 | “text” is String | “text” instanceof String |
字符串占位 | val number = 100 val text = “数量 = ${number}” | int number = 100; String text = String.format(“数量 = %d”, number); |
容器 | val array = arrayOf(1,2,3) val list = listOf(1,2,3) val map = mapOf(Pair(1, “one”), Pair(2, “two”), Pair(3, “three”)) | 略 |
for循环 | val array = arrayOf(1,2,3) for (i in array.indices) { pringln(i) } | 略 |
访问权限
Kotlin | 作用域 | Java | 作用域 |
---|---|---|---|
public | 与java相同 | public | 所修饰的类、变量、方法,在内外包均具有访问权限 |
internal | 在Kotlin有一个模块的概念,定义为internal的对象,同一模块是可以相互访问的 | protected | 这种权限是为继承而设计的,protected所修饰的成员,对所有子类是可访问的,但只对同包的类是可访问的,对外包的非子类是不可以访问 |
protected | 与java相同 | default | 只对同包的类具有访问的权限,外包的所有类都不能访问 |
private | 与java相同 | private | 私有的权限,只对本类的方法可以使用 |
Kotlin语法
判断器
var count = 1
when (count) {
0 -> {
println(count)
}
in 1..2 -> {
println(count)
}
else -> {
println(count)
}
}
构造函数
class MyView : View {
constructor(context : Context) : this(context, null) {
}
constructor(context : Context, attrs : AttributeSet?) : this(context, attrs, 0) {
}
constructor(context : Context, attrs : AttributeSet?, defStyleAttr : Int) : super(context, attrs, defStyleAttr) {
}
}
枚举
enum class Sex (var isMan: Boolean) {
MAN(true), WOMAN(false)
}
匿名内部类
object:Callback {
override fun onSuccess() {
}
override fun onFail() {
}
}
内部类
inner class InnerClass{
}
内部类访问外部变量
var name = "外部变量"
inner class MyTask {
var name = "内部变量"
fun show() {
println("我是内部变量:$name,我是外部变量${this@MainActivity.name})
}
}
抽象类
abstract class BaseActivity : AppCompatActivity(), Runnable {
abstract fun init()
}
静态变量和静态方法
在Kotlin中被称为伴生对象
companion object ToastUtils {
var sToast : Toast? = null
fun show() {
sToast!!.show()
}
}
可变长参数
fun add(vararg array: Int) : Int {
var count = 0
array.forEach {
count += it
}
return count
}
构造代码块
class MainActivity : AppCompatActivity() {
var number = 0
init {
number = 1
}
}
静态代码块
class MainActivity : AppCompatActivity() {
companion object {
var number = 0
init {
number = 1
}
}
}
Kotlin语法糖
无需 findViewById
xml中
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
代码中
tvContent.text = "我不用findViewById"
Lambda表达式
和Java8中相同使用即可。
tv_content.setOnClickListener { v -> v.visibility = View.GONE }
如果Lambda表达式中只有一个变量,可以直接用it来代替他。
tv_content.setOnClickListener { it.visibility = View.GONE }
函数赋值变量
在 Kotlin 语法中函数是可以作为变量进行传递的。
var addResult = fun(number1 : Int, number2 : Int) = number1 + number2
println(addResult(1, 2))
空安全检查
Kotlin 对空对象进行了限定,必须在编译时处理对象是否为空的情况,不然会编译不通过。
当对象可为空时,必须先判断对象是否为空。
var text : String? = null
println(text?.length)
返回null
如果确定一个可能为空的对象不为空时,可以使用2个 ! 跳过判断。
var text : String? = "text"
pringln(text!!.length)
如果跳过判断,当对象为空时,还是会抛出NPE。
函数支持默认参数
在Java中,我们定义一个可能包含多个不同参数的方法时,我们需要进行多次重载定义。
public void toast(Context context) {
toast(context, "默认toast");
}
public void toast(Context context, String text) {
toast(context, text, Toast.LENGTH_SHORT);
}
public void toast(Context context, String text, int duration) {
Toast.makeText(context, text, duration).show();
}
在Kotlin中我们只需要给参数设定默认值,只需要定义一个方法即可。
fun toast(context : Context, text : String = "默认toast", time : Int = Toast.LENGTH_SHORT) {
Toast.makeText(context, text, time).show()
}
当调用方法且参数缺省时,就会使用默认参数。
toast(this)
toast(this, "弹个吐司")
toast(this, "弹个吐司", Toast.LENGTH_LONG)
类扩展方法
可以在不用继承的情况下对扩展原有类的方法。
例如判断String是否为空串
fun String.isEmpty() = return TextUtils.isEmpty(this)
println("text".isEmpty)
扩展函数
扩展函数是 Kotlin 用于简化一些代码的书写产生的,其中有 let、with、run、apply、also 五个函数。
- let函数
在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式。
最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。
普通写法
mVideoPlayer?.setVideoView(course_video_view)
mVideoPlayer?.setControllerView(course_video_controller_view)
mVideoPlayer?.setCurtainView(course_video_curtain_view)
let写法
mVideoPlayer?.let {
it.setVideoView(course_video_view)
it.setControllerView(course_video_controller_view)
it.setCurtainView(course_video_curtain_view)
}
- with函数
with函数是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式。
定义一个类
class Person(var name : String, var age : Int)
普通写法
val person = Person("Jack", "30)
println("我叫${person.name},今年${person.age}岁")
with写法
with(Person("Jack", 30)){
print("我叫${name},今年${age}岁")
}
适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上。
普通写法
override fun onBindViewHolder(holder: ViewHolder, position: Int){
val item = getItem(position)?: return
holder.nameView.text = "姓名:${item.name}"
holder.ageView.text = "年龄:${item.age}"
}
with写法
override fun onBindViewHolder(holder: ViewHolder, position: Int){
val item = getItem(position)?: return
with(item){
holder.nameView.text = "姓名:$name"
holder.ageView.text = "年龄:$age"
}
}
- run函数
实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式。
普通写法
val textView = TextView(context)
textView.text = "我是普通的"
textView.textSize = 18f
textView.setTextColor(Color.RED)
run写法
val textView = TextView(context)
textView.run {
text = "我是run函数的"
textSize = 18f
setTextColor(Color.RED)
}
这里的textView的类型是Unit
- apply函数
从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。
apply写法
val textView = TextView(context).apply {
text = "我是run函数的"
textSize = 18f
setTextColor(Color.RED)
}
这里的textView的类型是TextView
使用apply进行多层级判空
定义一个类
class People(var body: Body? = null, var mind: Mind? = null) {
class Body(var trunk: Trunk? = null, var organ: Organ? = null, var bodyFluids: BodyFluids? = null) {
class Trunk(var head: Head? = null, var hand: Hand? = null, var foot: Foot? = null) {
inner class Head {}
inner class Hand {}
inner class Foot {}
}
class Organ {}
class BodyFluids {}
}
class Mind {}
}
普通写法
val people = People()
if (people.body == null || people.mind == null) {
return
}
if (people.body!!.trunk == null) {
return
}
print("我是手对象${people.body!!.trunk!!.hand}")
apply写法
people.body.apply { }?.trunk.apply { }?.apply {
print("我是手对象$hand")
}
- also函数
also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。而also函数返回的则是传入对象的本身。
适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用。
普通写法
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
also写法
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
五种函数的使用场景
携程
子任务协作运行,优雅的处理异步问题解决方案。
协程实际上就是极大程度的复用线程,通过让线程满载运行,达到最大程度的利用CPU,进而提升应用性能。
引入携程包
kotlin {
experimental {
coroutines 'enable'
}
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20'
}
协程的三种启动方式
runBlocking:T
launch:Job
async/await:Deferred
- runBlocking
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试开始")
runBlocking {
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试延迟开始")
delay(20000) // 因为 Activity 最长响应时间为 15 秒
println("测试延迟结束")
}
println("测试结束")
17:02:08.686 System.out: 测试是否为主线程true
17:02:08.686 System.out: 测试开始
17:02:08.688 System.out: 测试是否为主线程true
17:02:08.688 System.out: 测试延迟开始
17:02:28.692 System.out: 测试延迟结束
17:02:28.693 System.out: 测试结束
runBlocking 运行在主线程,过程中 App 出现过无响应提示,由此可见 runBlocking 和它的名称一样,真的会阻塞当前的线程,只有等 runBlocking 里面的代码执行完了才会执行 runBlocking 外面的代码。
- launch
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试开始")
launch {
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试延迟开始")
delay(20000)
println("测试延迟结束")
}
println("测试结束")
17:19:17.190 System.out: 测试是否为主线程true
17:19:17.190 System.out: 测试开始
17:19:17.202 System.out: 测试结束
17:19:17.203 System.out: 测试是否为主线程false
17:19:17.203 System.out: 测试延迟开始
17:19:37.223 System.out: 测试延迟结束
这种效果类似 new Thread()。
- async
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试开始")
async {
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试延迟开始")
delay(20000)
println("测试延迟结束")
}
println("测试结束")
17:29:00.694 System.out: 测试是否为主线程true
17:29:00.694 System.out: 测试开始
17:29:00.697 System.out: 测试结束
17:29:00.697 System.out: 测试是否为主线程false
17:29:00.697 System.out: 测试延迟开始
17:29:20.707 System.out: 测试延迟结束
执行结果和看上去和launch一样。
再执行以下这个代码
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试开始")
val async = async {
println("测试是否为主线程" + (Thread.currentThread() == Looper.getMainLooper().thread))
println("测试延迟开始")
delay(20000)
println("测试延迟结束")
return@async "666666"
}
println("测试结束")
runBlocking {
println("测试返回值:" + async.await())
}
17:50:57.117 System.out: 测试是否为主线程true
17:50:57.117 System.out: 测试开始
17:50:57.120 System.out: 测试结束
17:50:57.120 System.out: 测试是否为主线程false
17:50:57.120 System.out: 测试延迟开始
17:51:17.131 System.out: 测试延迟结束
17:51:17.133 System.out: 测试返回值:666666
可以看到,最后的async是有返回值的,通过它的 await 方法进行获取,需要注意的是这个方法只能在协程的操作符中才能调用。