Step into Kotlin - 01 - 入门
摘要
配置环境
入门
简介
Kotlin 是一门静态语言,可以编译成 Java或 Javascript。Kotlin 由 JetBrains 公司开发,所以开发工具是 IDEA。
配置环境
- 安装 IDEA 插件,plugins 中检索 Kotlin 下载并安装,安装完后重启 IDEA
- 建立 Maven 工程,配置 pom.xml
<properties>
<kotlin.version>0.1-SNAPSHOT</kotlin.version>
</properties>
<repositories>
<repository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
</build>
第一个 Kotlin 程序
右键选择新建 Kotlin File,命名为 Hello.kt
fun main(args: Array<String>) {
println(getHelloString())
}
fun getHelloString(): String {
return "Hello World!"
}
运行后可以看到控制台输出 “Hello World!”。
Step into Kotlin - 02 - 数据类型
目录
摘要
显示转换,String templates
Basic Types
概要
Kotlin 中一切皆是对象
Numbers
分类
与 Java 相似,但是 Kotlin 没有自动向上转型( 如 Java 中的 int 可以自动向上转型为 long )。
Type | Bitwidth |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
Kotlin 中字符不是 Number
Literal Constants
- Decimals: 123
- Longs are tagged by a capital L: 123L
- Hexadecimals: 0x0F
- Binaries: 0b00001011
Long 结尾不能为小写的 l
Kotlin 不支持 8 进制字面常量
Representation
数字的装箱操作不会保持相同性,但是会保持相等性
val a: Int = 10000
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(a identityEquals a) // Prints 'true'
println(boxedA identityEquals anotherBoxedA) // !!!Prints 'false'!!!
println(a == a) // Prints 'true'
println(boxedA == anotherBoxedA) // Prints 'true'
Explicit Conversions
语法
所有 Number 类型都支持类似 toByte(),toInt() 这样的显示转换的方法。
例
val a:Int = 1
// val b:Long = a 错误,不支持自动向上转型
val b:Long = a.toInt()
字面量
字面量可以根据上下文推导类型,所以可以不用显示转换
val a:Byte = 1
val b:Long = 1
b = 1.toLong() + 2 //Long + Int => Long
为什么不支持自动向上转型
因为 Kotlin 最终会被编译为 Java 代码,比如说
val a:Int = 2 // java.lang.Integer a = 2
// 如果支持向上转型
val b:Long = a // java.lang.Long b = a
假设支持向上转型,那么 a 就可以赋值给 b,这样 a==b
表达式会返回 false,因为两者类型不同,而这种错误只有在运行时才会发现。
Kotlin 通过强制使用类型转换,使编译时就能发现这种错误,从而提高了程序的安全性。
Operations
Kotlin 支持标准的操作符,并且在对应的类上定义为成员方法。
常用操作
- shl(bits) – 有符号左移 (Java’s <<)
- shr(bits) – 有符号右移 (Java’s >>)
- ushr(bits) – 无符号右移 (Java’s >>>)
- and(bits) – 按位与
- or(bits) – 按位或
- xor(bits) – 按位异或
- inv() – 按位取反
例
val b = 1 shl 2 //4
- 1
Characters
Char 使用单引号 ''
来声明,不能直接当做数字来使用,需要使用 toInt()等方法
val c: Char = 'a'
Booleans
Boolean 值为 true
或者 false
val flag: Boolean = true && false
String
Usage
val s = "Hello World"
for (x in s) {
print(x) // Hello World
}
Templates
String templates 可以使用变量值来替换字符串中的值
val name = "Peter"
val str = "name=$name, $name.length, ${name.length()}"
println(str) // name=Peter, Peter.length, 5
Step into Kotlin - 03 - 变量,常量与表达式
目录
摘要
声明变量,声明常量,表达式
变量,常量与表达式
变量
使用 var
关键字声明,类型写在变量名后
var x = 5
var y: Int = 5
常量
使用 val
关键字声明,又称作 “值”
val a: Int = 1
val b = 2
val c: Int
c = 3
表达式
Kotlin 也是一门面向表达式的语言,所以可以直接将一个表达式绑定在特定的变量上。
val z = if (a > b) a else b
Step into Kotlin - 04 - 函数
目录
摘要
创建函数,调用函数,默认参数,带名参数,可变长参数,Unit,函数作用域
函数
定义函数
格式
fun functionName(argumentName: arguemntType): returnType {function body}
- 1
- 以 fun 关键字进行定义,函数只有一句时可以加上 “=” 省略 “{}”
- 参数名写在参数类型之前
例
// 只有一句时
fun addOne(m: Int): Int = m + 1
fun addTwo(m: Int): Int {
return m + 2
}
调用函数
addOne(2)
- 1
函数参数
默认参数
调用某些方法时可以不指定所有参数,而使用默认值
fun decorate(str: String, left: String = "[", right: String = "]") = left + str + right
println(decorate("abc")) // [abc]
带名参数
可以在调用函数时,指定参数的名称
decorate("abc", right = ">")
- 1
可变长度参数
- 可变长参数使用
vararg
修饰 - 使用变长参数时只能一个个传值,不能直接使用外部的
Array
,除非使用*
表示将Array
中的每个元素当做参数处理
fun capitalizeAll(vararg args: String): List<String> {
return args.map { arg ->
arg.capitalize()
}
}
println(capitalizeAll("a", "b", "c")) // [A, B, C]
val array = arrayOf("d", "e", "f")
println(capitalizeAll("a", "b", "c", *array)) // [A, B, C, D, E, F]
Unit
当返回类型为 Unit
时,表示没有返回值,相当于 Java 中的 void
,此时也可以省略 Unit
的声明。
fun echo(s: String): Unit {
println(s)
}
fun echo2(s: String) {
println(s)
}
函数作用域
Kotlin 中函数可以直接定义在文件中,而不需要依托任何类。其中定义在 class 或 object 中的函数又被称为成员函数 (Member Function)。除此之外,Kotlin 中还有一种特殊的函数:本地函数 (Local Function),本地函数是定义在函数内部的函数。
fun factorialTail(n: Int): Int {
val zero = 0
// 本地函数 loop()
fun loop(acc: Int, n: Int): Int =
if (n == zero) acc else loop(n * acc, n - 1)
return loop(1, n)
}
本地函数可以访问外部函数的私有成员
Step into Kotlin - 05 - 流程控制
目录
摘要
if,for,Range 操作(指定步长,逆序,递减)
流程控制
条件表达式
if/else
结构与 Java 类型,不过由于 Kotlin 面向表达式,所以可以直接将表达式赋给一个变量。
val s = if (x > 0) 1 else -1
- 1
这种方式可以给常量赋值,有点类似 Java 中的三元操作符,此时 s 的类型为 Int
- 如果是下面这种混合型的,s 的类型就为 Any
val s = if (x > 0) "a" else 65
- 1
- 因为 Kotlin 中表达式都必须有值,所以如果没有
else
的话,需要引入 Unit 类
val s3: Any = if (x > 0) 1 else Unit
- 1
- Kotlin 在 if 语句块的最后可以自动返回最后一行表达式的值,而不需要写
return
语句
val max = if (1 < 2) {
println("a")
2
} else {
println("b")
1
}
循环
while 和 do-while
Kotlin 中的 while
和 do-while
类似 Java
for
基本使用
val arr = intArrayOf(1, 2, 3)
for ( a in arr) {
println(a)
}
用于字符串上
for (s in "Hello")
print(s)
Range
概述
Range 使用特殊符号 ..
表示,可以结合 if 和 for 语句使用。
If
在指定范围内
val x = 3
val y = 10
if (x in 1..y - 1)
println("OK")
else
println("OUT")
在指定范围外
val arr = intArrayOf(1, 2, 3, 4, 5)
if ( x !in 0..arr.lastIndex)
println("OUT")
也可结合 String 来使用
val str = "abcdef"
if (str in "abc".."abe") {
println(str) // abcdef
}
For
for (x in 1..n)
println(x)
此表达式表示让变量 i
遍历 in
右边的表达式的所有值 ( “1” 到 “n” )
变量 i
不需要事先定义,循环变量的作用域会一直持续直到循环结束。
除了基本的关键字 in
之外,也可以使用 step
控制步长,使用 downTo
进行递减操作。
for (z in 4 downTo 1)
print(z) // 4321
for (z in 1..4 step 2)
print(z) // 13
for (z in 4 downTo 1 step 2)
print(z) // 42
for (z in (1..4).reversed())
print(z) // 4321
break 和 continue
Kotlin 提供 break
和 continue
来退出循环,用法与 Java 相似。
Step into Kotlin - 06 - 异常
目录
摘要
try…catch,try 表达式
异常
- Kotlin 没有检查异常
try {
throw IOException()
} catch(e: IOException) {
println("io exception")
} catch(e: Exception) {
e.printStackTrace()
} finally {
}
Kotlin 中 try 语句也是种表达式,所以可以将它绑定到一个变量上
val a: Int = try {
val x: Int = 1 / 0
x
} catch(e: Exception) {
-1
}
Step into Kotlin - 08 - 数组
目录
摘要
创建数组(3种方法),空数组,访问元素,遍历数组,检查下标
数组
概述
- Array 类代表着数组
[]
可以用于访问数组的元素,实际上[]
被进行了操作符的重载,调用的是 Array 类的 setter 和 getter 方法
创建数组
直接指定长度
val fixedSizeArray = arrayOfNulls<Int>(5)
- 1
使用装箱操作
val arr = arrayOf(1, 2, 3)
val intArr = intArrayOf(1, 2, 3) //同理还有 booleanArrayOf() 等
- 1
- 2
使用闭包进行初始化
val asc = Array(5, { i -> i * i }) //0,1,4,9,16
- 1
空数组
val empty = emptyArray<Int>()
- 1
长度为 0
的空数组
访问数组元素
val arr = arrayOf(1, 2, 3)
println(asc[1]) // 1
println(asc.get(1)) // 1
// println(asc[10]) ArrayIndexOutOfBoundsException
[]
虽然调用的是 setter 和 getter 方法,但是编译成字节码时会被进行优化,变成直接访问数组的内存地址,所以不会造成性能损失。
修改元素
asc[1] = 10
- 1
遍历数组
for (i in asc) {
println(i)
}
此操作不会创建 iterator 对象,不会影响性能
遍历数组下标
for (j in asc.indices) {
println(j)
}
此操作不会创建 iterator 对象,不会影响性能
检查下标
if (i in asc.indices) { // i>=0 && i<asc.size()
println("indices:" + i)
}
Step into Kotlin - 10 - 映射及多重声明
目录
摘要
创建不可变映射,创建可变映射,访问元素,更新元素,遍历,多重声明
Map 及多重声明
Map
构造映射
不可变映射
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
- 1
可变映射
val mMap = hashMapOf("a" to 1, "b" to 2, "c" to 3)
- 1
访问元素
mMap.get("d")
- 1
如果试图访问不存在的 key
时,会抛出 NullPointerException
异常,所以需要在访问前先进行判断
val x = if (mMap.containsKey("e")) mMap.get("e") else 0
- 1
也可以使用上述的简写方式
mMap.getOrDefault("e", 10)
- 1
更新元素
可变映射
更新或插入新元素
mMap.put("d", 20)
- 1
删除元素
mMap.remove("c")
- 1
不可变映射
不可变映射不可以被修改
遍历
遍历 entry
for ((k, v)in map) {
println("$k -> $v")
}
只遍历 key 或 value
val keys = map.keySet()
val values = map.values()
for (k in keys) {
}
多重声明
只要类实现了 componentN()
方法,该类的对象就可以实现多重构造
class Person(val name: String, val age: Int) {
fun component1(): String {
return name
}
fun component2(): Int {
return age
}
}
val person = Person("Jane", 20)
val (name, age) = person // 多重声明
val pname = person.component1()
println(name.toString() + ", " + age + ", " + pname) // Jane, 20, Jane
以上 Person 实现了 component1
和 component2
方法,所以多重声明时第一个参数赋值给 name,第二个参数赋值给 age。
Step into Kotlin - 11 - 类
目录
摘要
定义类,Empty 类,实例化对象,访问权限,Setter 与 Getter,backing field,主构造器,辅助构造器
类
定义类
class Counter {
private var value = 0
var defaultValue = 1
public var publicValue: Int = 2
fun increment(): Unit {
value += 1
}
fun current() = value
}
- 一个 Kotlin 源文件中可以包含多个类。
- 声明为 public 的成员必须显示指定类型。
Empty 类
Empty 类没有类的实体,可以用于表示概念
class Empty
- 1
实例化对象
val counter = Counter()
counter.increment()
// println(counter.value) can not access
println(counter.publicValue)
println(counter.defaultValue)
- 实例化对象时不使用关键字 “new”
Setter 和 Getter
-
默认定义一个属性时,编译器会自动为该属性生成拥有 public setter 和 getter 方法的 private 属性。且直接调用对象的属性时实际是调用对应的方法
-
如果定义属性为private 的话,该属性的 setter 和 getter 也会变为 private。
class Person {
// 默认 getter 与 setter
var address = ""
public var age: Int = 0
}
val person = Person()
p1.age = 10
println(p1.age) // 10
p1.trueAge = 20
p1.trueAge = 14
println(p1.trueAge) // 20
只生成 Getter
当声明的属性为 val 时,该属性只会生成 getter 方法
val birthday = Date()
- 1
自定义 getter 与 setter
class Person {
// 自定义 getter 与 setter
var trueAge: Int
get() = age
set(pAge) {
if (pAge > age) {
age = pAge
}
}
// 自定义的同时改变访问权限
var setterVisiblity: String = "abc"
private set
}
这里需要特别注意的是由于调用属性实际是调用方法,所以如果上述改为 get() = trueAge
实际会不停调用自身,造成栈溢出。
backing field
- backing field 主要用于在自定义 setter 和 getter 时可以直接访问属性,而不是方法,从而防止栈溢出。
- backing field 以字符
$
开头,在使用前必须先被初始化。
class Person {
var backAge: Int = 0
get() = $backAge
set(pAge) {
if (pAge > $backAge) {
$backAge = pAge
}
}
}
构造器
概述
- 构造器分为主构造器和辅助构造器
- 默认一个类总会有一个无参的主构造器
辅助构造器
- 辅助构造器名为
constructor
- 辅助构造器可以通过
this()
调用其它的辅助构造器,但是任何一个辅助构造器必须调用主构造器
//调用主构造器
constructor(name: String) : this(name, 0) {
}
constructor(age: Int) : this("Default Name", age) {
}
constructor() : this("Default Name") {
}
主构造器
主构造器直接定义在类名后
class Man(val name: String, val age: Int, private var from: String = "USA", description: String = "none") {
init {
println("sentences in primary constructor")
}
}
- 主构造器会执行类中
init
代码块中得所有语句,所以每实例化一个对象,上述例子都会打印一条语句 - 主构造器中可以使用默认参数,如上述的 from
- 主构造器中定义的参数可以是
val
也可以是var
,定义的参数会自动转换为该类的成员属性 - 主构造器中的参数也可以不加
var
或val
,此时如果类中的方法有用到该参数,则此参数转换为成员属性,否则会被忽略 - 主构造器也可以被声明为
private
,这样就只能通过辅助构造器来实例化该对象
私有主构造器
class Woman private constructor(val name: String, val age: Int) {
constructor(name: String) : this(name, 0) {
}
}
Step into Kotlin - 12 - Object 与枚举
目录
摘要
创建单例对象,创建伴生对象,伴生对象与类的关系,使用对象表达式,创建枚举
Object 与 Enumeration
Object
对象声明
单例对象
- 与类不同,单例对象使用
object
进行声明 - Kotlin 没有静态属性和方法,需要使用单例对象来实现类似的功能。
- 对象的构造器不能提供构造器参数,在第一次使用时会被初始化。
object Singleton {
private var num = 0
fun sequence(): Int {
num += 1
return num
}
}
Singleton.sequence()
单例对象可以用于提供常量及共享不可变对象。
伴生对象
- 伴生对象可以用于让一个类即拥有实例化方法又有静态方法。
- 伴生对象必须声明在类中,且使用
companion object
关键字进行声明。 - 伴生对象与类可以互相访问各自的私有成员。
class Companion(private var balance: Int = 0) {
companion object Factory {
private var num = 0
fun create(): Companion = Companion()
private fun sequence(): Int {
num += 1
return num
}
fun getInfo(comp: Companion): String {
return "balance is " + comp.balance
}
}
val id = Companion.sequence()
}
对象表达式
对象表达式用于对类的功能进行修改,但是又不用显示创建类,类型 Java 中的匿名类。
open class A(x: Int) {
public open val y: Int = x
}
interface B {
fun info()
}
首先定义了一个类和一个接口,其中 open
用于表名该类可以被继承。
val ab = object : A(1), B {
override fun info() {
println("info")
}
override val y: Int
get() = 15
}
println(ab.y)
以上值 ab 继承了实现了 A 和 B,并重写了其两个方法,实际上本质是创建了匿名类。
对象表达式也可以没有任何父类或父接口
val adHoc = object {
var x: Int = 1
var y: Int = 2
}
println(adHoc.x)
对象声明与对象表达式的区别
- 对象声明是延迟初始化的,直到第一次访问才会被初始化。
- 对象表达式在使用时则会被立即初始化。
Enumeration
构造枚举
enum class TrafficColor(val info: String) {
RED("stop"), YELLOW("warning"), GREEN("walking"), BLUE("walking")
}
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
使用枚举
//直接获得枚举
var green = TrafficColor.GREEN
TrafficColor.GREEN.name() //返回 name
TrafficColor.GREEN.ordinal() //返回 id
Step into Kotlin - 13 - 包
目录
摘要
创建包,导入包,默认包,导入别名,作用域
包
创建包
类似 Java,使用 package
进行声明,但是包名和文件夹名可以不一致。
package a.b
导入包
类似 Java,使用 import
进行声明
import foo.info
默认包
如果一个 kotlin 源文件中没有任何包声明,则其当中的代码均属于默认包,导入时包名即为函数名
fun hello() {
println("hello, Default Package")
}
import hello
hello()
导入别名
导入类时可以为类起一个别名
import foo.bar as b
b.bar()
作用域
- private 在声明范围及同模块的子作用域内可见
- protected 类似 private,但是对子类也可见
- internal 默认作用域,同模块中都可见
- public 总是可见
注意:在 Java 中不同包名没有任何直接关系。
Step into Kotlin - 14 - 继承
目录
摘要
继承类,重写方法,实现接口,指定实现的父类方法,抽象类
继承
扩展类
- 使用
:
符号 - 默认所有类都是
final
的,如果希望类能够被继承,需要声明为open class
- 子类构造函数需要实现父类的构造函数
- 由于辅助构造器必须调用主构造器或其它辅助构造器,所以辅助构造器永远不可能直接访问父类的构造器
open class Person(name: String) {}
class Employee(name: String) : Person(name) {}
重写方法
- 方法默认为
final
的,如果希望被重写需要声明为open fun
final
修饰的方法无法被重写- 重写方法时必须加上
override
关键字,override
方法默认为open
的 - 调用超类方法时也是使用
super
关键字
// 父类
open fun idf() {}
open fun idf2() {}
// 子类
override fun idf() {
super.idf()
}
// 无法被重写
final override fun idf2() {
super.idf2()
}
接口
- 接口类似 Java8,支持方法,抽象方法和抽象属性
- 接口及其中的方法默认是
open
的
定义接口
interface A {
val prop: Int
fun foo() {
}
fun bar()
}
实现接口
class B : A {
override fun bar() {
throw UnsupportedOperationException()
}
override val prop: Int
get() = throw UnsupportedOperationException()
}
多重实现
如果一个类需要实现的类或接口中包含有同名方法,可以使用 <>
来指明需要调用哪个父类方法
open class TextView : View, OnClickListener {
constructor(size: Int) : super(size)
// onClick must be overridden
override fun onClick() {
super<View>.onClick() // call to View.onClick()
super<OnClickListener>.onClick()
}
}
抽象类
抽象类类似接口,默认也是 open
的,但是必须使用关键字 abstract
显示声明抽象成员。
abstract class Shape {
abstract fun onClick()
}
class Rectangle : Shape(), OnClickListener {
// not required to override onClick
}
以上 Shape 为抽象类,所以需要调用默认构造方法 ()
,而 OnClickListener 为接口,则不需要这样做。
Step into Kotlin - 15 - 注解
目录
摘要
声明注解,使用注解,带参数的注解
注解
声明注解
annotation class 注解名
使用注解
格式
@注解名
- 注解可以用在类,方法,参数,变量上
- 除了表达式和本地声明,其余符号 “@” 都可以省略
例
fancy class Foo {
fancy fun baz(fancy foo: Int): Int {
@fancy fun bar() {
}
return (@fancy 1)
}
}
注解在主构造器上
主构造器必须加上关键字 “constructor”
class Foo3 @fancy constructor() {
// annotate property accessors
var x: Int = 1
@fancy set
}
带参数的注解
annotation class special(val why: String)
special("example") class Foo
注解 Lambda 表达式
var f = @fancy { println("lambda") }
Step into Kotlin - 16 - Data 类与 When 表达式
目录
摘要
Data 类的声明和使用,When 的匹配
Data Class 与 When
Data Class
特点
- Data 类是使用关键字
data
声明的类 - Data 类默认基于构造方法实现了
toString()
,componentN()
,copy()
,equals()
和hashCode()
方法,不在构造方法中定义的属性不会产生在toString()
结果中。 - Data 类可以直接使用
==
进行比较,同样不在构造方法中定义的属性不会用在比较上 - Data 类只表示数据,不能拥有行为
创建 Data 类
data class Customer(var name: String, var email: String)
使用 Data 类
创建对象
val peter = Customer("Peter", "peter@example.com")
复制对象
val peter2 = peter.copy()
val peter3 = peter.copy(name = "")
ComponentN
val (name, email) = peter
println("name=$name,email=$email")
When
特点
- When 类似
switch
但是功能更加强大,且不需要break
语句。 - 模式匹配可以匹配值,类型
When 中的表达式
When 中可以使用的表达式类型
- 类名,用于进行类型匹配
- 范围,用于匹配范围
- 函数,用于匹配参数
- else,匹配其它情况
匹配值和范围
val y = when (x) {
1 -> 2
3 -> 4
3, 10 -> 30
in 10..20 -> 20
!in 20..30 -> 40
else -> 0
}
匹配类型
val x = 10
when (x) {
is Int -> println("long")
else -> println("else")
}
匹配参数
private fun add(x: Int): Int {
return x + 1
}
val x = 10
when (x) {
add(x) -> println("x=" + x)
else -> println("else")
}
Step into Kotlin - 17 - 泛型
目录
摘要
定义泛型,使用泛型,in 与 out,逆变与协变,集合的边界
泛型
定义泛型
class Holder<T>(val t: T) {
fun info() {
println(t)
}
}
使用泛型
var strHolder = Holder("a")
var intHolder = Holder(3)
strHolder.info() // a
intHolder.info() // 3
In 和 Out
Out
参数注解 out
用于表示该泛型只能用于输出,例如作为返回值。同时也提供了协变的特性。
abstract class Source<out T> {
abstract fun nextT(): T
}
协变特性
fun demo(strs: Source<String>) {
val obj: Source<Any> = strs
}
In
参数注解 in
用于表示该泛型只能用于输入,例如作为参数。同时也提供了逆变的特性。
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}
逆变特性
fun demo(x: Comparable<Number>) {
val y: Comparable<Double> = x
}
集合的边界
Java 中集合作为参数时可以指定边界,Kotlin 中也有类似的做法
Array<in String>
以上类似 Java 中的 Array<? super String>
Array<out String>
以上类似 Java 中的 Array<? extend String>
Step into Kotlin - 19 - 正则表达式
目录
摘要
创建正则表达式,匹配所有,替换
正则表达式
构造正则对象
使用 String
的 toRegex
方法
val numPattern = "[0-9]+".toRegex()
为避免转义符的干扰,可以使用"""
表示原样输出
匹配
返回所有匹配结果
for (matchResult in numPattern.matchAll("99 bottles, 98 bottles")) {
println(matchResult.value)
}
返回第一条匹配结果
val first = numPattern.match("99 bottles, 98 bottles")
println(first?.value)
替换
val result = numPattern.replace("99 bottles, 98 bottles", "xxx")
println(result) //xxx bottles, xxx bottles
Step into Kotlin - 20 - 高阶函数
目录
摘要
函数字面值,函数参数,闭包与柯理化,方法扩展表达式,inline
高阶函数
概念
那些以函数作为参数或返回函数的函数被称为高阶函数。
函数字面值
Kotlin 中函数可以赋值给变量
两种写法
val sum = { x: Int, y: Int -> x + y }
val sum2: (Int, Int) -> Int = { x, y -> x + y }
函数作为参数
格式
(函数参数) => 返回值类型
fun add(f: (Int) -> Int) = f(10)
val x = add({ Int -> 2 })
println(x) //2
以上 add 函数接收另一个函数作为参数,并在函数体内调用此函数,所以 add 函数即为高阶函数。
闭包与柯理化
- 闭包指由代码和非局部变量的自由变量组成的代码块
- 柯理化指接收参数产生新的函数的函数,所以柯理化函数也是高阶函数
var sum3 = 0
val ints = intArrayOf(1, 2, 3)
ints filter { it > 0 } forEach {
sum3 += it
}
print(sum3)
方法扩展表达式
val sum4 = fun Int.(other: Int): Int = this + other
1.sum4(2)
1 sum4 2
- inline 注解
每个方法实际都是一个包含 Lambda 表达式的对象,inline 注解可以将每个 Lambda 表达式进行内联以提高性能,noinline 则相反。
inline fun inlineLock<T>(lock: Lock, body: () -> T, @noinline notInlined: () -> T): T {
lock.lock()
try {
return body()
} finally {
lock.unlock()
}
}
Step into Kotlin - 21 - I/O
目录
摘要
读取行,读取字符串
IO 操作
读取操作
逐行读取
var source = File("coffeetime-kotlin/myfile.txt")
val lines = source.readLines("UTF-8")
for (l in lines) {
println(l)
}
读取到字符串中
val contents = source.readText("UTF-8")
Step into Kotlin - 22 - Xml
目录
摘要
解析 xml
Xml
解析 XML
xml
<rss>
<channel>
<title>Yahoo! Weather - Boulder, CO</title>
<item>
<title>Conditions for Boulder, CO at 2:54 pm MST</title>
<forecast day="Thu" date="10 Nov 2011" low="37" high="58" text="Partly Cloudy"
code="29"/>
<forecast day="Fri" date="11 Nov 2011" low="39" high="58"
text="Mostly Cloudy" code="28"/>
<forecast day="Sat" date="12 Nov 2011" low="32" high="49" text="Cloudy"
code="27"/>
</item>
</channel>
</rss>
Kotlin
val f = File("coffeetime-kotlin/src/main/resources/weather.xml")
val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
println(builder.parse(f).toXmlString())
Step into Kotlin - 23 - 与 Java 交互
目录
摘要
Kotlin 调用 Java,Java 调用 Kotlin(类,伴生对象,重载,异常)
与 Java 互相调用
Kotlin 调用 Java
Java
public class JavaBean {
private String name;
public JavaBean(String name) {
this.name = name;
}
public void hello() {
System.out.println("hello,my name is " + name);
}
public boolean is(String name){
return this.name.equals(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Kotlin
val javaBean = JavaBean("Peter")
javaBean.hello()
对于 is
等在 Kotlin 中是关键字而在 Java 中不是关键字的方法,在调用时需要加上 “ 符号。
javaBean.`is`("Peter")
Java 调用 Kotlin
调用类
Kotlin
class KotlinBean(val name: String) {
fun hello() {
println("hello,my name is " + name)
}
}
Java
KotlinBean bean = new KotlinBean("Peter");
bean.hello();
调用伴生对象的方法
Java 调用伴生对象的方法时需要加上注解 platformStatic
Kotlin
class KotlinBean(val name: String) {
fun hello() {
println("hello,my name is " + name)
}
companion object {
platformStatic fun foo() {
println("companion foo")
}
fun bar() {
println("companion bar")
}
}
}
Java
KotlinBean.foo();
以上 KotlinBean 的伴生对象中 foo()
对于Java 来说是静态方法,而 bar()
则不是。
Java 重载
Kotlin 方法可以通过注解 jvmOverloads
实现 Java 调用中的方法重载
Kotlin
jvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
}
Java
obj.f("str");
obj.f("str", 1);
obj.f("str", 1, "foo");
检查型异常
由于 Kotlin 没有 CheckException,所以如果需要在 Java 中抛出此种异常,需要添加注解 @throws
。
Kotlin
@throws(IOException::class) fun e() {
throw IOException()
}
Java
try {
obj.e();
} catch (IOException e) {
e.printStackTrace();
}
Step into Kotlin - 24 - delegate
目录
摘要
类的委托,属性的委托,Lazy Properties,Observable Properties,Not Null,Map Val
Delegate
类的委托
类的委托使用 by 语句
。
创建接口和子类
interface Base {
fun println()
}
class BaseImpl(val x: Int) : Base {
override fun println() {
println(x)
}
}
建立委托类
class Derived(b: Base) : Base by b
以上 Derived 类
创建了所有来自 Base 的方法
,并且委托一个传入的 Base 的对象
执行这些方法。
使用委托类
val b = BaseImpl(19)
b.println() // 19
Derived(b).println() // 19
属性的委托
创建被委托类
class Delegate {
fun get(thisRef: Any?, prop: PropertyMetadata): String {
return "$thisRef, thank you for delegating '${prop.name}}' to me!"
}
fun set(thisRef: Any?, prop: PropertyMetadata, value: String) {
println("$value has been assigned to '${prop.name} in $thisRef.'")
}
}
创建委托类
class Example {
var p: String by Delegate()
}
使用委托
val e = Example()
println(e.p)
e.p = "NEW"
内置属性委托
分类
Kotlin API 中有两种标准的属性委托方法,一种是 lazy properties,一种是 observable properties。
Lazy Properties
- Lazy Properties 在第一次被访问时才会获取值。
- Lazy Properties 是通过 lazy 方法来实现的。
- lazy 方法接收一个Lambda 表达式,在第一次执行时会计算 Lambda 的值然后存储起来,之后访问的话会直接取得保存的值,而不会重新计算。
val lazy: String by lazy {
println("computed!")
"Hello ${System.currentTimeMillis()}"
}
println("lazy is ${lazy}")
println("lazy is ${lazy}")
lazy 是非线程安全的,线程安全的版本见下面的例子
val blockLazy: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("computed2!")
"Hello2 ${System.currentTimeMillis()}"
}
Observable Properties
- Observable Properties 被存储在一个 Map 中,而不是单独的属性。
- Observable Properties 可以设置监听值改变事件的监听器。
- Observable Properties 是通过 observable()方法实现的。
- observable()方法接收两个参数,第一个为初始值,第二个为监听的回调Handler,该 Handler 接收三个参数:被设置的属性,旧值,新值。
class User {
var name: String by Delegates.observable("<no name>") {
d, old, new ->
println("$old -> $new")
}
}
val user = User()
user.name = "first"
user.name = "second"
也可以使用 vetoable()
代替 observable()
,该方法功能类似,但是返回布尔值,用以表示这次设置是否有效。
class User {
var age: Int by Delegates.vetoable(0) {
d, old, new ->
println("$old -> $new")
if (new < 20) true else false
}
}
val user = User()
user.age = 10
println(user.age)
user.age = 20
println(user.age)
Not Null
由于 Kotlin 不允许定义任何非空的非抽象属性,但是实际使用时可能也需要定义这样的属性,这时就需要 not null 方法来实现。
class NotNullFoo {
var bar: String by Delegates.notNull()
}
val foo = NotNullFoo()
foo.bar = "bar"
Map Val
Map Val 可以用于将所有属性保存在一个 Map 中,在使用 JSON 等数据格式或者实现某些动态特性时这种功能很有用。
class Account(val map: Map<String, Any?>) {
val name: String by Delegates.mapVal(map) {
o, p ->
println("$o -> $p")
"default name"
}
val age: Int by Delegates.mapVal(map) {
o, p ->
println("$o -> $p")
0
}
}
val account = Account(mapOf(
"name" to "John",
"age" to 25
))
println(account.name)
println(account.age)
mapVar()为以上的可变版本。