前言
在Kotlin中,一切都是对象。没有像Java中那样的原始基本类型。这个是非常有帮
助的,因为我们可以使用一致的方式来处理所有的可用的类型。
基本类型
当然,像integer,float或者boolean等类型仍然存在,但是它们全部都会作为对象
存在的。基本类型的名字和它们工作方式都是与Java非常相似的,但是有一些不同
之处你可能需要考虑到:
- 数字类型中不会自动转型。举个例子,你不能给 Double 变量分配一
个 Int 。必须要做一个明确的类型转换,可以使用众多的函数之一:
val i:Int=7
val d: Double = i.toDouble()
- 字符(Char)不能直接作为一个数字来处理。在需要时我们需要把他们转换为
一个数字:
val c:Char='c'
val i: Int = c.toInt()
- 位运算也有一点不同。在Android中,我们经常在 flags 中使用“或”,所以我
使用"and"和"or来举例:
// Java
int bitwiseOr = FLAG1 | FLAG2;
int bitwiseAnd = FLAG1 & FLAG2;
// Kotlin
val bitwiseOr = FLAG1 or FLAG2
val bitwiseAnd = FLAG1 and FLAG2
还有很多其他的位操作符,比如 sh1 , shs , ushr , xor 或 inv 。当我
们需要的时候,可以在Kotlin官网查看。
- 字面上可以写明具体的类型。这个不是必须的,但是一个通用的Kotlin实践时
省略变量的类型(我们马上就能看到),所以我们可以让编译器自己去推断出具体的类型。
val i = 12 // An Int
val iHex = 0x0f // 一个十六进制的Int类型
val l = 3L // A Long
val d = 3.5 // A Double
val f = 3.5F // A Float
- 一个String可以像数组那样访问,并且被迭代:
val s = "Example"
val c = s[2] // 这是一个字符'a'
// 迭代String
val s = "Example"
for(c in s){
print(c)
}
变量
变量可以很简单地定义成可变( var )和不可变( val )的变量。这个与Java中使
用的 final 很相似。但是不可变在Kotlin(和其它很多现代语言)中是一个很重要
的概念。
一个不可变对象意味着它在实例化之后就不能再去改变它的状态了。如果你需要一
个这个对象修改之后的版本,那就会再创建一个新的对象。这个让编程更加具有健
壮性和预估性。在Java中,大部分的对象是可变的,那就意味着任何可以访问它这
个对象的代码都可以去修改它,从而影响整个程序的其它地方。
不可变对象也可以说是线程安全的,因为它们无法去改变,也不需要去定义访问控
制,因为所有线程访问到的对象都是同一个。
所以在Kotlin中,如果我们想使用不可变性,我们编码时思考的方式需要有一些改
变。一个重要的概念是:尽可能地使用 val 。除了个别情况(特别是在Android
中,有很多类我们是不会去直接调用构造函数的),大多数时候是可以的。
之前提到的另一件事是我们通常不需要去指明类的类型,它会自动从后面分配的语
句中推断出来,这样可以让代码更加清晰和快速修改。我们在前面已经有了一些例
子:
val s = "Example" // A String
val i = 23 // An Int
val actionBar = supportActionBar // An ActionBar in an Activity
如果我们需要使用更多的范型类型,则需要指定:
val a: Any = 23
val c: Context = activity
属性
属性与Java中的字段是相同的,但是更加强大。属性做的事情是字段加上getter加
上setter。我们通过一个例子来比较他们的不同之处。这是Java中字段安全访问和
修改所需要的代码:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
···
Person person = new Person();
person.setName("name");
String name = person.getName();
在Kotlin中,只需要一个属性就可以了:
public class Person {
var name: String = ""
}
···
val person = Person()
person.name = "name"
val name = person.name
如果没有任何指定,属性会默认使用getter和setter。当然它也可以修改为你自定义
的代码,并且不修改存在的代码:
public classs Person {
var name: String = ""
get() = field.toUpperCase()
set(value){
field = "Name: $value"
}
}
如果需要在getter和setter中访问这个属性自身的值,它需要创建一个 backing
field 。可以使用 field 这个预留字段来访问,它会被编译器找到正在使用的并
自动创建。需要注意的是,如果我们直接调用了属性,那我们会使用setter和getter
而不是直接访问这个属性。 backing field 只能在属性访问器内访问。
就如在前面章节提到的,当操作Java代码的时候,Kotlin将允许使用属性的语法去
访问在Java文件中定义的getter/setter方法。编译器会直接链接到它原始的
getter/setter方法。所以当我们直接访问属性的时候不会有性能开销。
Anko是什么?
Anko是JetBrains开发的一个强大的库。它主要的目的是用来替代以前XML的方式
来使用代码生成UI布局。这是一个很有趣的特性,我推荐你可以尝试下,但是我在
这个项目中暂时不使用它。对于我(可能是由于多年的UI绘制经验)来说使用XML
更容易一些,但是你会喜欢那种方式的。
然而,这个不是我们能在这个库中得到的唯一一个功能。Anko包含了很多的非常有
帮助的函数和属性来避免让你写很多的模版代码。我们将会通过本书见到很多例
子,但是你应该快速地认识到这个库帮你解决了什么样的问题。
尽管Anko是非常有帮助的,但是我建议你要理解这个背后到底做了什么。你可以在
任何时候使用 ctrl + 点击 (Windows)或者 cmd + 点击 (Mac)的方式跳转
到Anko的源代码。Anko的实现方式对学习大部分的Kotlin语言都是非常有帮助的。
开始使用Anko
在之前,让我们来使用Anko来简化一些代码。就像你将看到的,任何时候你使用了
Anko库中的某些东西,它们都会以属性名、方法等方式被导入。这是因为Anko使
用了扩展函数在Android框架中增加了一些新的功能。我们将会在以后看到扩展函
数是什么,怎么去编写它。
在 MainActivity:onCreate ,一个Anko扩展函数可以被用来简化获取一个
RecyclerView:
val forecastList: RecyclerView = find(R.id.forecast_list)
我们现在还不能使用库中更多的东西,但是Anko能帮助我们简化代码,比如,实例
化Intent,Activity之间的跳转,Fragment的创建,数据库的访问,Alert的创建……
我们将会在实现这个App的过程中学习到很多有趣的例子。
扩展函数
扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权
限。这是一个在缺少有用函数的类上扩展的方法。在Java中,通常会实现很多带有
static方法的工具类。Kotlin中扩展函数的一个优势是我们不需要在调用方法的时候
把整个对象当作参数传入。扩展函数表现得就像是属于这个类的一样,而且我们可
以使用 this 关键字和调用所有public方法。
举个例子,我们可以创建一个toast函数,这个函数不需要传入任何context,它可以
被任何Context或者它的子类调用,比如Activity或者Service:
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
这个方法可以在Activity内部直接调用:
toast("Hello world!")
toast("Hello world!", Toast.LENGTH_LONG)
当然,Anko已经包括了自己的toast扩展函数,跟上面这个很相似。Anko提供了一
些针对 CharSequence 和 resource 的函数,还有两个不同的toast和longToast方
法:
toast("Hello world!")
longToast(R.id.hello_world)
扩展函数也可以是一个属性。所以我们可以通过相似的方法来扩展属性。下面的例
子展示了使用他自己的getter/setter生成一个属性的方式。Kotlin由于互操作性的特
性已经提供了这个属性,但理解扩展属性背后的思想是一个很不错的练习:
public var TextView.text: CharSequence
get() = getText()
set(v) = setText(v)
扩展函数并不是真正地修改了原来的类,它是以静态导入的方式来实现的。扩展函
数可以被声明在任何文件中,因此有个通用的实践是把一系列有关的函数放在一个
新建的文件里。
这是Anko功能背后的魔法。现在通过以上,你也可以自己创建你的魔法。
执行一个请求
对于感受我们要实现的想法而言,我们目前的文本是很好开始,但是现在是时候去
请求一些显示在RecyclerView上的真正的数据了。我们将会使用OpenWeatherMap
API来获取数据,还有一些普通类来现实这个请求。多亏Kotlin非常强大的互操作
性,你可以使用任何你想使用的库,比如用Retrofit来执行服务器请求。当只是执行
一个简单的API请求,我们可以不使用任何第三方库来简单地实现。
而且,如你所见,Kotlin提供了一些扩展函数来让请求变得更简单。首先,我们要
创建一个新的Request类:
public class Request(val url: String) {
public fun run() {
val forecastJsonStr = URL(url).readText()
Log.d(javaClass.simpleName, forecastJsonStr)
}
}
我们的请求很简单地接收一个url,然后读取结果并在logcat上打印json。实现非常
简单,因为我们使用 readText ,这是Kotlin标准库中的扩展函数。这个方法不推
荐结果很大的响应,但是在我们这个例子中已经足够好了。
如果你用这些代码去比较Java,你会发现我们仅使用标准库就节省了大量的代码。
比如 HttpURLConnection 、 BufferedReader 和需要达到相同效果所必要的迭
代结果,管理连接状态、reader等部分的代码。很明显,这些就是场景背后函数所
作的事情,但是我们却不用关心。
为了可以执行请求,App必须要有Internet权限。所以需要
在 AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET" />
在主线程以外执行请求
如你所知,HTTP请求不被允许在主线程中执行,否则它会抛出异常。这是因为阻
塞住UI线程是一个非常差的体验。Android中通用的做法是使用 AsyncTask ,但是
这些类是非常丑陋的,并且使用它们无任何副作用地实现功能也是非常困难的。如
果你使用不小心, AsyncTasks 会非常危险,因为当运行到 postExecute 时,如
果Activity已经被销毁了,这里就会崩溃。
Anko提供了非常简单的DSL来处理异步任务,它满足大部分的需求。它提供了一个
基本的 async 函数用于在其它线程执行代码,也可以选择通过调用 uiThread 的
方式回到主线程。在子线程中执行请求如下这么简单:
async() {
Request(url).run()
uiThread { longToast("Request performed") }
}
UIThread 有一个很不错的一点就是可以依赖于调用者。如果它是被一
个 Activity 调用的,那么如果 activity.isFinishing() 返回 true ,
则 uiThread 不会执行,这样就不会在Activity销毁的时候遇到崩溃的情况了。
假如你想使用 Future 来工作, async 返回一个Java Future 。而且如果你需
要一个返回结果的 Future ,你可以使用 asyncResult 。
真的很简单,对吧?而且比 AsyncTasks 更加具有可读性。现在,我仅仅给请求
发送了一个url,来测试我们是否可以正确接收内容,这样我们才能在Activity中把它
画出来。我很快会讲到怎么去进行json解析和转换成app中的数据类,但是在我们
继续之前,学习什么是数据类也是很重要的。
检查代码并审查url请求和包结构的代码。你可以运行app并且确保你可以在打印的
json日志和请求完毕之后的toast。