Kotlin入门系列:第一章 Kotlin基础

1 基本要素:函数和变量

1.1 Hello world

fun main(args: Array<String>) {
	println("Hello world")
}

以上代码语法:

  • 关键字fun用来声明一个函数

  • 参数的类型写在它的名称后面

  • 数组就是类,Kotlin没有声明数组类型的特殊语法

  • 使用println代替System.out.println打印

  • 代码结尾没有分号

1.2 函数

fun max(a: Int, b: Int): Int {
	return if (a > b) a else b
}
等价于
fun man(a: Int, b: Int) = if (a > b) a else b

fun main(args: Array<String>) {
	println(max(10, 20));
}

在Kotlin中,if是有返回结果的表达式而不是判断语句(返回的值就是判断后的语句结果);第一种写法为代码块函数体,第二种写法为表达式函数体,表达式函数体不需要返回类型,代码在编译时会判断

1.3 变量

val question = "The Ultimate Question of Life, the Universe, and Everything"

val answer = 42
等价于
val answer: Int = 42

val yearsToCompute = 7.5e6

可变变量和不可变变量

  • val(来自value):不可变引用。使用val声明的变量不能在初始化之后再次赋值。它对应java的final变量

  • var(来自variable):可变引用。这种变量的值可以被改变。它对应java的非final变量

默认情况下,应该尽可能使用val关键字来声明所有的kotlin变量,仅在必要的时候换成var。使用不可变引用、不可变对象及无副作用的函数让你的代码更接近函数式编程风格。

fun main() {
    val canPerformOperation = Random.nextBoolean()
    val message = if (canPerformOperation) "Success" else "Failed"
    print(message)
}

fun main() {
    // 尽管val引用自身是不可变的,但是它指向的对象可能是可变的
	val languages = arrayListOf("java")
	languages.add("kotlin")	// 改变引用指向的对象
    println(language)
}

fun main() {
	// var允许变量改变自己的值,但它的类型确实改变不了的
	// 错误
	var answer = 42
	answer = "no answer"
}

一般开发中需要使用到 valvar 的情况:

  • 在函数中声明一个临时变量或调用其他函数返回给临时变量时,如
val answer = 42;
var answer = 42;
val answer = callMethod()
  • 创建model对象构造方法有参数时,如
class Person(val nickname: String, val age: Int)

其他情况和java一样,在函数中的参数直接声明即可,如

fun main(args: Arrays<String>) {}

1.4 编译时常量

在 kotlin 中常量是使用关键字 const 声明的,但常量的声明是有要求的:

  • const 只能修饰 object 的属性(object 声明的类相当于 java 的单例类),或 top-level 顶层变量

  • const 变量的值必须在编译期间确定下来,所以它的类型只能是 String 或基本类型

// 正确,kotlin 没有包的概念,顶层变量就是直接写在文件上的变量
// 顶层变量或函数相当于 java 的静态变量或静态函数
const val a = 0

class A {
	// 错误,const 不能声明在非 object 的类中
	const val s = 0
}

object A {
	// 正确,const 声明在 object 类(即 java 的单例类)中
	const val s = 0
}

class A {
	companion object {
		// 正确,companion object 相当于在 java 会创建一个名为 Companion 的静态类
		const val s = 0
	}
}

1.5 字符串模板

fun main() {
	// $表示字符串模板,如果要在字符串输出$符号,要使用转义字符"\"
	// 在字符串模板中也可以使用表达式,使用"{}"括起来
    val args = arrayListOf("kotlin", "java")
    val name = if (args.size > 0) args[0] else "kotlin"
    println("Hello, $name")
    println("Hello, ${args[1]}")
    println("Hello, ${if (args.size > 0) args[0] else "kotlin"}")
}

2 类和属性

2.1 类和属性

java的对象

public class Person {
	private String name;

	public Person(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}

kotlin的对象

// 在kotlin中,类默认是public,所以可以省略
// 这种类(只有数据没有其他代码)通常被叫做值对象
class Person(val name: String)

2.2 属性

// 属性name和isMarried
// name使用val声明,表示只可读,只会为属性生成一个getter
// isMarried使用var声明,表示可读可写,会为属性生成getter和setter
class Person(val name:String, var isMarried: Boolean)

fun main(args: Array<String>) {
	// 创建对象不需要new关键字
	val person = Person("Bob", true)
	// 可以直接访问类的属性,相当于调用了getter
	// setter可以使用person.isMarried = false代表
	println("person's name is ${person.name}, is married ${person.isMarried}")
}

2.3 自定义访问器

class Rectangle(val height: Int, val width: Int) {
	// 自定义一个内部成员属性,修改它的getter访问
	val isSquare get(): Boolean {
		return height == width
	}
}
等价于
class Rectangle(val height: Int, val width: Int) {
	val isSquare get(): Boolean = height == width
	或
	val isSquare get() = height == width
} 

fun main(args: Array<String>) {
	val rectangle = Rectangle(41, 41)
	println(rectangle.isSquare)
}

类的属性我们也可以自定义修改 getter 和 sette:

class Hello {
	var string: String? = null
		// 修改属性的 getter
		get() {
			return field + " modify getter"
		}
		// 修改属性的 setter
		set(value) {
			field = value + " modify setter"
		}
	
	// val 声明的属性只能重写 getter
	val string2: String? = null
		get() {
			return field + " modify getter"
		}
}

需要注意的是,声明为 val 并不代表变量是一个常量,而是该变量是 final 声明的不可重新赋值的变量,但是我们还是可以用修改 getter 的方式修改变量的值:

class Person(var birthYear: Int) {
	val age: Int
		get() {
			return Calendar.getInstance().get(Calendar.YEAR) - birthYear
		}
	
	// 模拟一年后
	fun oneYearsLater() {
		birthYear--
	}
}

fun main(args: Array<String>) {
	val person = Person(1990)
	println(person.age)
	person.oneYearsLater() // Person 对象的 birthYear 被修改
	println(person.age) // 因 birthYear 可变变量被修改,age 重新计算获取的 getter 值也被修改
}

输出结果:
28
29

3 表示和处理选择:枚举和"when"

3.1 声明枚举

// 枚举
// java只有一个enum关键字声明枚举,kotlin需要enum class,因为enum在kotlin中是软关键字,只有出现在class前面才有特殊意义
enum class Color {
	RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

enum class Color(val r: Int, val g: Int, val b: Int) {
	RED(255, 0, 0), ORANGE(255, 165, 0); // 下面有方法时,要用分号隔断
	
	fun rgb() = (r * 256 + g) * 256 + b	// 枚举定义方法
}

3.2 使用"when"处理枚举

when 可以实现java的 switch判断 ,switch只能判断枚举常量、字符串或者数字字面值,但when可以传入任何对象,只要匹配到when下面的分支即返回结果,且when下面的分支也可以是表达式

fun getMnemonic(color: Color) =
	when (color) {
		Color.RED -> "Richard" // 匹配到就直接返回"->"后面的值或表达式
		COLOR.ORANGE -> "Of"
		.....
	}

fun getWarmth(color: Color) = 
	when(color) {
		Color.RED, Color.ORANGE -> "warn"	多个匹配到,用","分隔
		.....
	}

fun main(args: Array<String>) {
	println(getMnemonic(Color.BLUE))
}

3.3 在"when"结构中使用任意对象

// setOf(c1, c2)表示根据传入的参数创建一个set集合
// 在when语句块对比两个元素相同的集合
fun mix(c1: Color, c2: Color) =
	when(setOf(c1, c2)) {
		setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
		setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
		setOf(Color.BLUE, Color.VIOLET) -> Color.INDIGO
		else -> throw Exception("Dirty color")	// 匹配不到
	}

3.4 使用不带参数的"when"

// when没有传入参数,在代码块中表达式判断
fun mixOptimized(c1: Color, c2: Color) =
	when {
		(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
		....
	}

3.5 智能转换:合并类型检查和转换

interface Expr // 接口Expr
// 对象Num实现Expr接口
// 相当于java的public class Num implements Expr
class Num(val value: Int): Expr 
// 对象Sum实现Expr接口
// 相当于java的public Sum implements Expr
class Sum(val left: Expr, val right: Expr): Expr

// is相当于java的instanceof
fun eval(e: Expr) : Int {
	if (e is Num) {
		val n = e as Num	// 强制转换
		return n.value
	}
	// kotlin对类型是会自动转换且不需要再使用其他变量强制转换
	// 将e Expr自动转化为Sum,直接使用Sum的属性
	if (e is Sum) {	
		return eval(e.right) + eval(e.left)
	}
	throw IllegalArgumentException("Unknown expression")
}
等价于
fun eval(e: Expr): Int {
	if (e is Num) {
		e.value
	} 

	if (e is Sum) {
		eval(e.right) + eval(e.left)
	} else {
		throw IllegalArgumentException("Unknown expression")
	}
}

注意:智能转换只在 isis 关键字相当于java的 instanceof)检查且之后不再发生变化的情况下,且属性必须为val属性,并且不能有自定义的访问器【即上例中的函数参数e在is判断后转换为Num或Sum类型后,在后续的代码中不会再进行其他类型的转换】。

或许你会觉得奇怪,为什么我们使用 is 判断后就能智能转换直接调用了?其实将kotlin反编译为java可以发现,在使用上和java是一致的,它还是会将类型强制转换后再调用:

public final int eval(@NotNull ExampleUnitTest.Expr e) {
   Intrinsics.checkParameterIsNotNull(e, "e");
   int var10000;
   if (e instanceof Num) {
   	  // 实际还是强制转换了类型
      var10000 = ((Num)e).getValue();
   } else {
      if (!(e instanceof Sum)) {
         throw (Throwable)(new IllegalArgumentException());
      }

	  // 实际还是强制转换了类型
      var10000 = this.eval(((Sum)e).getLeft()) + this.eval(((Sum)e).getRight());
   }

   return var10000;
}

3.6 重构:用"when"代替"if"

fun eval(e: Expr): Int = 
	when(e) {
		is Num -> e.value
		is Sum -> eval(e.right) + eval(e.left)
		else -> throw IllegalArgumentException("Unknown expression")
	}

3.7 代码块作为"if"和"when"的分支

fun evalWithLogging(e: Expr): Int =
	when (e) {
		is Num -> {
			println("num:${e.value}")
			e.value
		}
		is Sum -> {
			val left = evalWithLogging(e.left)
			val right = evalWithLoggin(e.right)
			println("sum: $left + $right")
			left + right
		}
 		else -> throw IllegalArgumentException("Unknown expression")
	}

fun main() {
    evalWithLogging(Num(10))
    evalWithLogging(Sum(Num(10), Num(10)))
}

4 迭代:"while"循环和"for"循环

whiledo while 和java一样

4.1 迭代数字:区间和数列

kotlin为了替代最常见的循环,使用了区间的概念,用 .. 运算符代表

val oneToTen = 1..10	// 代表区间范围1到10

fun fizzBuzz(i: Int) = 
	when {
		i % 15 == 0 -> "FizzBuzz"
		i % 3 == 0 -> "Fizz"
		i % 5 == 0 -> "Buzz"
		else -> "$i"
	}

// 无论是循环list、map,都需要使用关键字in
// 循环从1开始到100,[1,100]都是闭区间包含数值
for (i in 1..100) {
	print(fizzBuzz(i))
}	

// 半开区间[1, 100)不包含100,相当于i in 1..100-1
for (i in 1 until 10) {
	println(fizzBuzz(i))
}

// 倒序输出结果,从10到1
for (i in 10 downTo 1) {
	println(i)
}

// 闭区间[1, 10],每个循环迭代步径为2,即+=2
for (i in 1..10 step 2) {
	println(i)
}

// 遍历对象列表
val users = arrayListOf(...)
for (user in users) {
	println(user)
}

// 闭包 println(it) 重复执行次数,这里指定循环10次
repeat(10) {
	println(it)
}

4.2 迭代map

val binaryReps = TreeMap<Char, String>() // 创建一个TreeMap

// 循环字符区间A到F
for (c in 'A'..'F') {
	// map存储,key为c(即key为字符A到F),value为binary(即value为字符A到F到二进制数)
	binaryReps[c] = Integer.toBinaryString(c.toInt())	
}

// 将键给letter,值给binary
for ((letter, binary) in binaryReps) {
	println("$letter = $binary")
}

// 跟踪下表index
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
	println("$index:$element")
}

4.3 使用"in"检查集合和区间的成员

// c in 'a'..'z'相当于java的c >= a && c <= z
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'

fun recognize(c: Char) = when(c) {
	in '0'..'9' -> "It's a digit!"
	in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
	else -> "I don't know..."
}	

// 相当于"kotlin" >= "Java" && "kotlin" <= "Scala"
// 这里字符串是按照字母表顺序进行比较的,因为String就是这样实现Comparable接口的
println("kotlin" in "Java".."Scala")

5 kotlin中的异常

与java一样的抛异常,只是没有 new 关键字,kotlin创建对象不需要new

throw IllegalArgumentException()

5.1 “try”、“catch”、“finally”

// Int?不必显式地指定抛出的异常类型
fun readNumber(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()
    }
}
try-catch在kotlin也是有返回值的,可以简化为
fun readNumber(reader: BufferedReader): Int? {
    return try {
        val line = reader.readLine()
        Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        null
    } finally {
        reader.close()
    }
}

val reader = BufferedReader(StringReader("23"))
println(readNumber(reader))

5.2 "try"作为表达式

try可以作为表达式赋值返回

fun readNumber(reader: BufferReader) {
	val number = try {
		Integer.parseInt(reader.readLine())
	} catch(e: NumberFormatException) {
		// return	// 如果抛出异常,执行return后不会再往下执行
		null		// 如果抛出异常,返回null
	} 
	println(number)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值