【Kotlin】三、变量和属性

上一篇:【Kotlin】二、编写你的第一个类

前言

在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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容