最近似乎一直在忙,没有抽出时间来写点什么,我也不知道自己在忙什么,但是感觉时间过得好快。这次的文章主要是总结一下最近接触Kotlin的一些实践经历,没有什么太过于复杂深奥的东西,毕竟我也是第一次接触。想了解Kotlin的可以看一下,牛人可以绕行了。
准备工作
我当前使用的AS版本是2.3.x,需要安装Kotlin的相关插件:
下载好插件之后重启AS。
还是新建一个Android 工程,然后对于已经建立好的文件可以使用插件提供的自动转化功能:
- 选中Java文件->Code->Convert Java File to KotlinFile
- 转化工程:Tools->Kotlin->Configure Kotlin in Project
转化完成后的工程样式如下:
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
module下的build.gradle:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"//不做检测会出现问题,找不到库
// sdk19, sdk21, sdk23 are also available
// In case you need support-v4 bindings
// For appcompat-v7 bindings
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'org.jetbrains.anko:anko-sdk15:0.8.3'
compile 'org.jetbrains.anko:anko-support-v4:0.8.3'
compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3'
testCompile 'junit:junit:4.12'
}
project下的build.gradle:
buildscript {
ext.kotlin_version = '1.1.2-4'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
我下了一个AS3.0版,是可以直接建立Kotlin工程的,2.3版本冒死不行。如果你是使用已有的工程直接转成Kotlin,我的意见是慎重,因为,如果工程过于庞大,很可能出现转化不规范,或者转化错误的地方,到时候很不好检查,可以部分文件部分文件的转化。同时,个人认为,通过java文件转kotlin对比学习,是一个学习Kotlin的好办法
语法
学习一门新的语言的时候,总是要先学习它的语法,对比它的语法与自己已会语言的语法有什么不同。
变量声明
//空安全变量
var str: String = "hello"//非空类型
var str1 = "word"
//可为空字符串变量
var str2: String? = null//可空类型 不允许我们直接使用一个可选型的变量去调用方法或者属性。
对于非空类型,如果你将变量设置为空,就会提示错误。
对于可空类型,你是不许调用的它的方法火属性,如val l = str2.length
也会报错,但是提供了另外一种方式调用val l = str2?.length ?: -1
或者你能确保他是非空的val l1 = str2!!.length
两个叹号用来确认变量的非空。
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
还有一种方法更加巧妙:
str2?.let { println("str2"+str2) }
str2后面一定要加?标识可空,let的方法内的执行,只有在str2非空的时候才会去执行。
还有一种写法:
str2?:let {}
把点换成冒号,标识当str2为空的时候才去执行。
以上可以看出Kotlin的用心良苦,这些都完美避免了空指针问题。
顺带说一句吧,拼接字符串跟以前一样,用个加号即可println(str+str1)
switch
when (x) {
7 -> println("7")//当x为7的时候,输出7
if (x > 0) 1 else -1 -> println("大于0并等于1,或小于0并等于-1")//如果x>0是1的时候或小于0是-1的时候
in 1..10 -> println("范围匹配1-10")//是否在1-10之间
is String -> println("String类型")//是String类型
}
ifelse还是可以正常使用的,所以这里不多介绍,主要介绍一下switch,这个跟以前不太一样了,改用when来代替。
when比switch更加强大,when子式可以是常量、变量、返回数值的表达式、返回Boolean值的表达式,甚至ifelse
ArrayList
fun testArrayList(){
val list:ArrayList<String> = ArrayList<String>()
list.add("111")
list.add("222")
list.add("333")
list.add("444")
list.add("555")
list.add("666")
//indices相当于序号集合
for (i in list.indices) {
print(list[i]+" ")
}
print("\n")
for (i in 2..list.size-1) {
print(list[i]+" ")
}
print("\n")
for (item in list) {
print(item+" ")
}
print("\n")
//加强版
for ((i,item) in list.withIndex()){
print(list[i]+" ")
print(item+" |")
}
print("\n")
//变种版
list.forEach {
print(it)
}
print("\n")
}
打印效果如下:
06-02 14:06:07.122 20087-20087/com.umeng.soexample I/System.out: 111 222 333 444 555 666
06-02 14:06:07.122 20087-20087/com.umeng.soexample I/System.out: 333 444 555 666
06-02 14:06:07.122 20087-20087/com.umeng.soexample I/System.out: 111 222 333 444 555 666
06-02 14:06:07.123 20087-20087/com.umeng.soexample I/System.out: 111 111 |222 222 |333 333 |444 444 |555 555 |666 666 |
06-02 14:06:07.123 20087-20087/com.umeng.soexample I/System.out: 111222333444555666
类的相关特性
构造函数
class Person (private val name: String,private val age: String) {
fun sayHello() {
println("$name is $age years old")
}
}
Kotlin允许将构造参数直接跟在类名后面,这种写法声明的构造函数,我们称之为主构造函数。
这时可能有人会有疑问,如果希望在构造函数中添加一些代码逻辑怎么实现?
class Person (private val name: String,private val age: String) {
init {
println("$name init")
println("$age init")
}
fun sayHello() {
println("$name is $age years old")
}
}
刚才提到了这是主构造函数,有主就有次,那么一定有次级构造函数。次级构造函数就需要写在类中了:
class Person (private val name: String,private val age: String) {
private var sex: String? = null
init {
println("$name init")
println("$age init")
}
constructor(name: String, age: String,sex: String) : this(name,age) {
this.sex = sex
}
internal fun sayHello() {
println("$name is $age years old")
}
}
我们让次级构造函数调用了主构造函数。由于次级构造函数不能直接将参数转换为字段,所以需要手动声明一个 sex 字段,并为 sex 字段赋值。
在字符串内部调用$符号,标识后面的是变量,将会把变量的内容替换到该位置。
这里要说一下internal是含义,在同一个module中,它相当于public,在module之外,相对于private
类的扩展
Kotlin可以在已有类中添加新的方法, 比继承更加简洁和优雅。
我们可以在Activity中写一个这样的方法:
fun Person.toast() {
var userInfo = "name:$name, age:$age"
println(userInfo)
}
为什么这么写呢,这个方法不是Activity类内的方法,实际它是刚才Person类的方法,我们调用一下:
val person = Person("deep","30","man")
person.toast();
结果如下:
06-02 17:25:54.095 3989-3989/com.umeng.soexample I/System.out: deep init
06-02 17:25:54.095 3989-3989/com.umeng.soexample I/System.out: 30 init
06-02 17:25:54.095 3989-3989/com.umeng.soexample I/System.out: name:deep, age:30
转换
之前也提到过,可以用 is 来判断一个对象是否是某个类的实例,但是如果需要强制转化呢,我们可以使用as
(person as Person).sayHello()
伴生对象
Kotlin之所以这么做,是由于Kotlin 没有静态方法。在大多数情况下,官方建议是简单地使用伴生对象。
public final class StringUtils public constructor() {
public companion object {
public final fun getLength(str: kotlin.String): Int {
return str.length;
}
}
}
我们可以这样调用:
StringUtils.getLength("1111111");
我们可以看出这种用法相当于静态方法。
所以,我们也可以用这种方式来处理单例模式的类:
class SingleObject private constructor() {
companion object {
fun get():SingleObject{
return Holder.instance
}
}
private object Holder {
val instance = SingleObject()
}
}
然后用SingleObject.get();
进行调用
要注意private constructor()
这种写法,把构造函数私有化。
布局
如果以之前的思路使用xml,布局文件,那么将Activity中的代码,转成Kotlin,应该是如下的样子
class LayoutActivity : Activity() {
internal var btn1: Button? = null;
internal var btn2: Button? = null;
internal var btn3: Button? = null;
internal var btn4: Button? = null;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_layout)
btn1 = findViewById(R.id.btn1) as Button
btn2 = findViewById(R.id.btn2) as Button
btn3 = findViewById(R.id.btn3) as Button
btn4 = findViewById(R.id.btn4) as Button
btn1!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "1111", Toast.LENGTH_LONG).show() }
btn3!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "333333", Toast.LENGTH_LONG).show() }
btn2!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "222222", Toast.LENGTH_LONG).show() }
btn4!!.setOnClickListener { Toast.makeText(this@LayoutActivity, "444444", Toast.LENGTH_LONG).show() }
}
}
在这里推荐另外一种方式布局,就是不使用布局文件。
这种方式需要额外依赖一些库:
compile 'org.jetbrains.anko:anko-sdk15:0.8.3'
compile 'org.jetbrains.anko:anko-support-v4:0.8.3'
compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.3'
我们来看一下写法:
class AnkoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
relativeLayout {
val tv = textView("aaaaa") {
textSize = 25f
}.lparams {
width = matchParent
alignParentTop()
}
button("Sample button").lparams {
width = matchParent
alignParentBottom()
}
textView("bbbbb").lparams {
below(tv)
alignParentRight()
topMargin = 400
}
}
}
}
这种设置方式与布局文件类似,所有能在布局文件中设置的,在这里也能设置,也是使用的嵌套的方式。
这种方式不做过多解释,觉得喜欢的朋友可以查看Anko的相关用法。
总结
Kotlin的学习成本并不高,而且可以通过这个AS的插件,将现有java文件逐个转化进行学习。如果能够熟练掌握Kotlin,确实从代码量上能节省很多。
有疑问的朋友欢迎给我留言指正,或者关注我的公众号留言:
网友评论