Kotlin语言基础笔记
Kotlin流程控制语句笔记
Kotlin操作符重载与中缀表示法笔记
Kotlin扩展函数和扩展属性笔记
Kotlin空指针安全(null-safety)笔记
Kotlin类型系统笔记
Kotlin面向对象编程笔记
Kotlin委托(Delegation)笔记
Kotlin泛型型笔记
Kotlin函数式编程笔记
Kotlin与Java互操作笔记
Kotlin协程笔记
Kotlin是基于Java的语言,所以Kotlin也是一种面向对象的语言,如果你熟悉Java,你应该很快可以上手,这篇文章假设你已经熟悉Java语言了。
1. 类与构造函数
Kotlin中的类与接口和Java中的有些区别。
- Kotlin中接口可以包含属性申明。
- Kotlin的类申明,默认是final和public的。
- Kotlin的嵌套类并不是默认在内部的,他们不包含外部类的隐私引用。
- Kotlin的构造函数,分为主构造函数和次构造函数。
- Kotlin中可以使用
data
关键字来申明一个数据类。 - Kotlin中可以使用
object
关键字来表示单例对象、伴生对象等。
Kotlin的类由下面几部分组成:
- 构造函数和初始化块。
- 属性
- 函数
- 嵌套类和内部类
- 对象申明
1.1 类的声明
使用class
关键字声明,这个跟Java是一样的,如果你申明的类不包含任何东西的话,可以自己class后面直接写上类名。
class Person
1.2 构造函数
在Kotlin中可以有一个主构造函数,一个或者多个次构造函数。
主构造函数
主构造函数直接跟在类名后面,如下:
open class Person constructor(var name: String, var age: Int) : Any() {
...
}
主构造函数中申明的属性可以是可变的(var)也可以是不变的(val)。如果主构造函数没有任何注解或者可见性修饰符,可以省略constructor关键字,而且Koltin中的类默认就是继承Any的,也可以省略。所以可以简化成如下:
open class Person(var name: String, var age: Int) {
...
}
如果这个类是有注解或者可见性修饰符,那么constructor关键字不可少,如下:
class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
...
}
主构造函数不能包括任何代码。初始化代码可以放到以init
关键字作为前缀的初始化块中:
class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
init {
println("Student(name = $name, age = $age) created")
}
}
主构造函数的参数可以在初始化块中使用,也可以在类体内申明的属性初始化器中使用。
次构造函数
我们也可以在类体中使用constructor
申明次构造函数,次构造函数的参数不能使用val或者var申明。
annotation class MyAutoware
class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
var grade: Int = 1
init {
println("Student(name = $name, age = $age) created")
}
constructor(name: String, age: Int, grade: Int) : this(name, age){
this.grade = grade
}
}
如果一个类有主构造函数,那么每个次构造函数都需要委托给主构造函数,委托到同一个类的另一个构造函数可以使用this关键字,如上面这个例子this(name, age)
如果一个非抽象类没有申明任何构造函数(包括主或者次),它会生成一个不带参数的主构造函数。构造函数的可见性是public。
私有的主构造函数
如果我们不希望这个类被实例化,我们可以申明如下:
class CantCreateMe private constructor()
当然我们可以通过次构造函数来创建。
class CantCreateMe private constructor(){
var name: String = ""
constructor(name: String) : this() {
this.name = name
}
}
1.3 属性
class Point{
var x:Int = 0
var y:Int = 0
}
fun main(args: Array) {
val p = Point()
p.x = 8
p.y = 10
println("Point(x = ${p.x}, y = ${p.y})")
}
可以看出Kotlin中的类的字段自动带有getter和setter方法,比Java会简洁很多。
1.3 方法(函数)
class Point{
var x:Int = 0
var y:Int = 0
operator fun plus(p: Point) {
x += p.x
y += p.y
}
}
fun main(args: Array) {
val p = Point()
p.x = 8
p.y = 10
val p1 = Point()
p1.x = 2
p1.y = 3
val p2 = p + p1
println("Point(x = ${p.x}, y = ${p.y})")
}
2. 抽象类
下面就是一个抽象类,类需要用abstract修饰,其中也有抽象方法,跟Java有区别的是Kotlin的抽象类可以包含抽象属性。
abstract class Person(var name: String, var age: Int){
abstract var addr: String
abstract val weight: Float
abstract fun doEat()
abstract fun doWalk()
fun doSwim() {
println("I am Swimming ... ")
}
open fun doSleep() {
println("I am Sleeping ... ")
}
}
2.1 抽象函数(方法)
在上面这个抽象类中,有doEat和doWalk抽象函数,同时还有具体的实现函数doSwim,在Kotlin中如果类和方法没有修饰符的化,默认就是final的。这个跟Java是不一样的。所以doSwim其实是final的,也就是说Person的子类不能覆盖这个方法。如果一个类或者类的方法想要设计成被覆盖(override)的,那么就需要加上open修饰符。下面是一个Person的子类:
class Teacher(name: String, age: Int) : Person(name, age) {
override var addr:String = "Guangzhou"
override val weight: Float = 100.0F
override fun doEat() {
println("Teacher is Eating ... ")
}
override fun doWalk() {
println("Teacher is Walking ... ")
}
override fun doSleep() {
super.doSleep()
println("Teacher is Sleeping ... ")
}
//编译错误
// override fun doSwim() {
// println("Teacher is Swimming ... ")
// }
}
如果子类覆盖了父类的方法或者属性,需要使用override关键字修饰。如果子类没有实现父类的抽象方法,则必须把子类也定义成
抽象函数的特征有以下几点:
- 抽象函数、抽象属性必须使用abstract关键字修饰。
- 抽象函数或者抽象类不用手动添加open关键字,默认就是open类型。
- 抽象函数没有具体的实现,抽象属性不用赋值。
- 含有抽象函数或者抽象属性的类,必须要使用abstract关键字修饰。抽象类可以有具体实现的函数,这样的函数默认是final的(不能被覆盖)。如果想要被覆盖,需要手工加上open关键字。
3. 接口
Kotlin中的接口跟Java8的接口类似。他们都可以包含抽象方法以及实现的方法。
interface UserService{
var version: String
fun save(user: User)
fun log(){
println("UserService log...")
}
}
3.1 接口的实现
接口是没有构造函数的,和继承一样,我们也是使用冒号:
来实现一个接口,如果要实现多个接口,使用逗号,
分开,如下:
data class User(var name: String, var password: String)
data class Product(var pid: String, var name: String)
interface UserService{
val version: String
val defaultPassword: String
get() = "123456"
fun save(user: User)
fun log(){
println("UserService log...")
}
}
interface ProductService{
val version: String
fun save(product : Product)
fun log(){
println("ProductService log...")
}
}
class UserProductServiceImpl : UserService, ProductService{
override val version: String = "1.0"
override val defaultPassword: String
get() = "@WSX1qaz"
override fun save(user: User) {
println("save user...")
}
override fun save(product: Product) {
println("save product")
}
override fun log() {
super.log()
super.log()
}
}
3.2 覆盖冲突
在上面的例子中,UserService和ProductService都有log()这个方法的实现,我们在重写UserProductServiceImpl的log()方法时,如果我们调用super.log()
。编译器是不知道我们要调用那个父类的log()方法的,这样就产生了覆盖冲突。
解决的办法也很简单,我们使用下面的这种语法来指定调用那个父类的log()方法。
super.log()
super.log()
3.3 接口中的属性
接口中的属性,可以是抽象的,或者是提供访问器的实现。因为接口没有状态,所以接口的属性是无状态的。请看上面的例子。
4. 继承
在Kotlin中,所有的类都默认继承Any这个类,Any并不是跟Java的java.lang.Object一样,因为它只有equals(),hashCode()和toString()这三个方法。
除了抽象类和接口默认是可被继承外,其他类默认是不可以被继承的(相当于默认都带有final修饰符)。而类中的方法也是默认不可以被继承的。
- 如果你要继承一个类,你需要使用
open
关键字修饰这个类。 - 如果你要继承一个类的某个方法,这个方法也需要使用
open
关键字修饰。
open class Base
class SubClass :Base()
如果Base有构造函数,那么子类的主构造函数必须继承。如下:
open class Base(type: String){
open fun canBeOverride() {
println("I can be override.")
}
fun cannotBeOverride() {
println("I can't be override.")
}
}
class SubClass(type: String) :Base(type){
override fun canBeOverride() {
super.canBeOverride()
println("Override!!!")
}
// override fun cannotBeOverride() { 编译错误。
// super.cannotBeOverride()
// println("Override!!!")
// }
}
override重写的函数也是open的,如果不希望被重写,可以加上final:
open class Base(type: String){
open fun canBeOverride() {
println("I can be override.")
}
fun cannotBeOverride() {
println("I can't be override.")
}
}
open class SubClass(type: String) :Base(type){
//如果某个类继承了SubClass,那么这个类将不可以重写canBeOverride方法。
final override fun canBeOverride() {
super.canBeOverride()
println("Override!!!")
}
}
继承实现接口:
abstract class Animal {
fun doEat() {
println("Animal Eating")
}
}
abstract class Plant {
fun doEat() {
println("Plant Eating")
}
}
interface Runnable {
fun doRun()
}
interface Flyable {
fun doFly()
}
class Dog : Animal(), Runnable {
override fun doRun() {
println("Dog Running")
}
}
class Eagle : Animal(), Flyable {
override fun doFly() {
println("Eagle Flying")
}
}
// 始祖鸟, 能飞也能跑
class Archaeopteryx : Animal(), Runnable, Flyable {
override fun doRun() {
println("Archaeopteryx Running")
}
override fun doFly() {
println("Archaeopteryx Flying")
}
}
fun main(args: Array) {
val d = Dog()
d.doEat()
d.doRun()
val e = Eagle()
e.doEat()
e.doFly()
val a = Archaeopteryx()
a.doEat()
a.doFly()
a.doRun()
}
5. 枚举类
Kotlin的枚举类基本上跟Java的差不多。
enum class Direction{
NORTH, SOUTH, WEST, EAST
}
fun main(args: Array) {
println(Direction.NORTH.name) //打印NORTH
println(Direction.NORTH.ordinal) //打印0
println(Direction.NORTH is Direction) //打印true
println(Direction.valueOf("NORTH")) 打印NORTH
println(Direction.values().joinToString(prefix = "[", postfix = "]")) //打印[NORTH, SOUTH, WEST, EAST]
}
枚举类也可以有构造函数,如下:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
fun main(args: Array) {
println(Color.RED.rgb) //打印16711680
}
枚举常量也可以声明自己的匿名类:
enum class ActivtyLifeState {
onCreate {
override fun signal() = onStart
},
onStart {
override fun signal() = onStop
},
onStop {
override fun signal() = onStart
},
onDestroy {
override fun signal() = onDestroy
};
abstract fun signal(): ActivtyLifeState
}
fun main(args: Array) {
println(ActivtyLifeState.onCreate) //打印onCreate
}
我们也可以使用enumValues ()
函数来列举所有的值:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
fun main(args: Array) {
println(enumValues().joinToString { "${it.rgb} : ${it.name} : ${it.ordinal} " }) //打印16711680 : RED : 0 , 65280 : GREEN : 1 , 255 : BLUE : 2
}
6. 注解类
Kotlin中的注解与Java的的注解完全兼容。下面示例如何声明一个注解:
annotation class 注解名
代码示例:
@Target(AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.EXPRESSION,
AnnotationTarget.FIELD,
AnnotationTarget.LOCAL_VARIABLE,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER,
AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicClass
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicFunction
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicConstructor
在上面的代码中,我们通过向注解类添加元注解(meta-annotation)的方法来指定其他属性:
- @Target :指定这个注解可被用于哪些元素(类, 函数, 属性, 表达式, 等等.);
- @Retention :指定这个注解的信息是否被保存到编译后的 class 文件中, 以及在运行时是否可以通过反 射访问到它;
- @Repeatable:允许在单个元素上多次使用同一个注解;
- @MustBeDocumented : 表示这个注解是公开 API 的一部分, 在自动产生的 API 文档的类或者函数签名中, 应该包含这个注解的信息。
注解可以用在类、函数、参数、变量(成员变量、局部变量)、表达式、类型上等。这个由该注解的元注解@Target定义。
@MagicClass class Foo @MagicConstructor constructor() {
constructor(index: Int) : this() {
this.index = index
}
@MagicClass var index: Int = 0
@MagicFunction fun magic(@MagicClass name: String) {
}
}
7. 单例模式(Singleton)与伴生对象(companion object)
7.1 单例模式
Kotlin中没有静态属性和方法,但是也提供了实现类似单例的功能,使用object
关键字声明一个object对象。
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
fun main(args: Array) {
println("C:${StringUtils.separator}Users${StringUtils.separator}Denny") //打印c:\Users\Denny
println(StringUtils.isDigit("12321231231")) //打印true
}
我们反编译后可以知道StringUtils转成了Java代码:
public final class StringUtils {
@NotNull
private static final String separator = "\\";
public static final StringUtils INSTANCE;
@NotNull
public final String getSeparator() {
return separator;
}
public final boolean isDigit(@NotNull String value) {
Intrinsics.checkParameterIsNotNull(value, "value");
String var4 = value;
int var5 = value.length();
for(int var3 = 0; var3 < var5; ++var3) {
char c = var4.charAt(var3);
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
static {
StringUtils var0 = new StringUtils();
INSTANCE = var0;
separator = "\\";
}
}
7.2 伴生对象
使用companion object
来声明:
class Utils{
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
companion object MiscUtils{
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
class Class{
companion object {
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
fun main(args: Array) {
println(Utils.isEmpty("xx"))
println(Utils.MiscUtils.isEmpty("xx"))
println(Class.isEmpty("xx"))
}
一个类只能有一个伴生对象。
class Utils{
val index:Int = 100
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
companion object MiscUtils{
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
class Class{
companion object {
val index:Int = 100
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
fun main(args: Array) {
println(Utils.MiscUtils.isEmpty("xx"))
println(Utils.isEmpty("xx")) //因为只能有一个伴生对象,所以可以省略到伴生对象的类名
println(Class.Companion.isEmpty("xx")) //可以使用默认的Companion来访问伴生对象。
println(Class.Companion.index)
println(Class.isEmpty("xx")) //当然也可以省略
}
伴生对象的初始化是在相应的类被加载解析时,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实的对象的实例成员。另外:
@JvmField注解,会在生成的Java类中使用静态字段。
class ClassA{
@JvmField val separator: String = """\"""
}
上面这段会被转成下面的Java代码:
class ClassA{
@JvmField val separator: String = """\"""
}
@JvmStatic注解,会让你在单例对象或者伴生对象中生成对应的静态方法。
8. 密封类(sealed)
声明一个密封类,需要在类名前面添加sealed
修饰符,密封类的所有子类必须与密封类在同一个文件中声明:
sealed class Expression
class Unit : Expression()
data class Const(val number: Double) : Expression()
data class Sum(val e1: Expression, val e2: Expression) : Expression()
data class Multiply(val e1: Expression, val e2: Expression) : Expression()
object NaN : Expression()
密封类的主要使用场景其实更像是枚举类的扩展,比如使用when表达式时。
fun eval(expr: Expression): Double = when (expr) {
is Unit -> 1.0
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
is Multiply -> eval(expr.e1) * eval(expr.e2)
NaN -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
9. 数据类(data class)
在Kotlin中从语言层面上支持创建一个领域实体类,也叫做数据类:
data class Book(val name: String)
data class Fook(var name: String)
data class User(val name: String, val gender: String, val age: Int) {
fun validate(): Boolean {
return true
}
}
定义好数据类后,会自动生成以下函数:
- equals()、hashCode()和toString()
- componentN() 函数 : 按声明顺序对应于所有属性component1()、component2() ...
- copy()函数
如果我们自己实现了以上函数,那么Kotlin就不会生成,会使用我们实现的方法。
数据类有以下限制:
- 主构造函数必须至少有一个参数。
- 主构造函数所有参数必须要标记为val或者var。
- 数据类不能是抽象、开放、密封或者内部类,
9.1 数据类的解构
因为数据类自动生成componentN()函数,所以我们可以使用解构功能:
val helen = User("Helen", "Female", 15)
val (name, gender, age) = helen
println("$name, $gender, $age years of age")
输出:
Helen, Female, 15 years of age
10. 嵌套类(Nested Class)
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
class Nested1 {
val three = 3
fun getFour() = 4
}
}
}
}
我们可以这样来访问内部类:
val one = NestedClassesDemo.Outer().one
val two = NestedClassesDemo.Outer.Nested().getTwo()
val three = NestedClassesDemo.Outer.Nested.Nested1().three
val four = NestedClassesDemo.Outer.Nested.Nested1().getFour()
但是普通的嵌套类,并不会持有外部类的引用:
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
fun accessOuter() = {
println(zero) // error, cannot access outer class
println(one) // error, cannot access outer class
}
}
}
}
如果要访问Outer类的变量,那么我们需要把Nested类标记为内部类。
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
inner class Inner {
fun accessOuter() = {
println(zero) // works
println(one) // works
}
}
}