0%

kotlin学习日志3-语法学习

类和继承

1
class Invoice {}

Kotlin 中类可以有一个主构造函数以及多个二级构造函数。

主构造函数是类头的一部分:跟在类名后面,如果构造函数有注解或可见性声明,需带constructor 关键字

语法格式class 类名 可见性 注解 constructor(参数名1:类型1,参数名2:类型2.....)

1
class Customer public @inject constructor (name: String) {...}

没有注解或可见性说明,则 constructor 关键字是可以省略。

初始化代码可以放在以 init 做前缀的初始化块中初始化

1
2
3
4
5
class Person(val firstName: String, val lastName: String, var age: Int) {
init {
logger,info("Customer initialized with value ${name}")
}
}

在同一个类中代理另一个构造函数使用 this 关键字

1
2
3
4
5
class Person(val name: String) {
constructor (name: String, paret: Person) : this(name) {
parent.children.add(this)
}
}

与java相同,如果一个非抽象类没有声明构造函数,会产生一个没有参数的构造函数。
如果不想类有公共的构造函数,就得声明一个拥有不可见的空主构造函数。

1
2
class DontCreateMe private constructor () {
}

创建类的实例

Kotlin 没有 new 关键字,直接通过类名(参数1,参数2...)进行初始化

1
2
val invoice = Invoice()
val customer = Customer("Joe Smith")

属性和字段

每个字段对应的get,set写法

Getters 和 Setters

1
2
3
4
5
var stringRepresentation: String
get() = this.toString()
set (value) {
setDataFormString(value) // 格式化字符串,并且将值重新赋值给其他元素
}

备用字段

用field标识符来表示,只允许在属性的访问器函数内使用,起到局部变量的作用。

1
2
3
4
5
var counter = 0 // 初始化值会直接写入备用字段
set(value) {
if (value >= 0)
field = value
}

备用属性

感觉跟备用字段挺像的

1
2
3
4
5
6
7
8
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 参数类型是自动推导
}
return _table ?: throw AssertionError("Set to null by another thread")
}

编译时常量

满足以下条件

  • 在顶级声明的 或者 是一个object的成员

  • 以String或基本类型进行初始化

  • 没有自定义getter

这种属性可以被当做注解使用 @Deprected(SUBSYSTEM_DEPRECATED)

1
2
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprected(SUBSYSTEM_DEPRECATED) fun foo() { ... }

延迟初始化属性

希望在访问这个属性的时候,避免非空检查,用lateinit修饰符。

这个修饰符只能够被用在类的 var 类型的可变属性定义中,不能用在构造方法中,并且属性不能有自定义的 getter 和 setter访问器,这个属性的类型必须是非空的,同样也不能为一个基本类型。

在一个lateinit的属性初始化前访问他,会导致一个特定异常。

1
2
3
4
5
6
7
8
9
10
11
public class MyTest {
lateinit var subject: TestSubject

@SetUp fun setup() {
subject = TestSubject()
}

@Test fun test() {
subject.method()
}
}

嵌套类

普通内部类
类可以标记为 inner这样就可以访问外部类的成员。

1
2
3
4
5
6
7
8
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}

val demo = Outer().Inner().foo() //==1

对象表达式

相当于java中的匿名内部类,不一样的是对象表达式可以访问闭合范围内的变量,但不用final修饰

语法格式调用函数(object:父类名1(参数1,参数2...),父类名2(参数1,参数2...){需要重写的变量,需重写的函数1,需重写的函数2})

1
2
3
4
5
6
7
8
9
10
11
12
fun countClicks(windows: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent){
enterCount++
}
})
}

父类有构造函数,则必须传递相应的构造参数。多个父类可以用逗号隔开,跟在冒号后面:

1
2
3
4
5
6
7
8
9
open class A(x: Int) {
public open val y: Int = x
}

interface B { ... }

val ab = object : A(1), B {
override val y = 14
}

如果没有父类

1
2
3
4
5
6
val adHoc = object {
var x: Int = 0
var y: Int = 0
}

print(adHoc.x + adHoc.y)

对象声明

关键字object之后指定了一个名称, 那么它就不再是“对象表达式”,而是一个对“对象声明”。感觉像是匿名内部类的另一种写法。

1
2
3
4
5
6
7
8
9
10
object MyInfo: Info("submit"),IClickListener {

override fun doClick() {
println("MyInfo do click, $text") // Log: MyInfo do click, , submit
}
}

fun main(args: Array<String>) {
MyInfo.doClick()
}

跟在 object 关键字后面是对象名,和变量声明一样。
可以继承父类

1
2
3
4
5
6
7
8
9
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}

override fun mouseEntered(e: MouseEvent) {
// ...
}
}

伴随对象

用 companion 关键字标记对象声明,跟java不一样,Kotlin中并不支持静态方法,调用方式有两种,一种是类名.伴随对象.XX,另外一种方式是类名.xx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Books(var name: String, val page: Int) {
companion object ComBooks{
val a : Int = 10
fun doNote() {
println("do note")
}
}
}

fun main(args: Array<String>) {
Books.ComBooks.doNote()
println("Book.a = ${Books.ComBooks.a}")
println("-------------")
Books.doNote()
}

对象表达式,对象声明,伴随对象区别:

  • 对象表达式在使用的地方被立即执行。
  • 对象声明是延迟加载的, 在第一次使用的时候被初始化。
  • 伴生对象所在的类被加载,伴生对象被初始化,与Java静态成员一样。

继承

Kotlin 中所有的类都有共同的父类 Any ,类似java中都继承Object,它是一个没有父类声明的类的默认父类。
声明一个明确的父类,继承类的时候带了构造参数,继承接口的时候不带。

1
2
open class Base(p: Int)
class Derived(p: Int) : Base(p)

默认情形下,kotlin 中所有的类都是 final,如果类有主构造函数,则基类必须在主构造函数中使用参数立即初始化。如果类没有主构造函数,则必须在每一个构造函数中用super关键字初始化基类。

1
2
3
4
5
6
class MyView : View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}

可见性修饰词

  • private,protected,internal,以及 public 。默认的修饰符是 public。
  • 与java不同的是,跟是否可重写没有关系。
  • private 只在该类中可见
  • protected 和 private 一样但在子类中也可见
  • internal 在本模块的所有可以访问到声明区域可见
  • public 任何地方可见
  • 外部类不可以访问内部类的 private 成员。
  • 如果你复写了一个protected成员并且没有指定可见性,那么该复写的成员具有protected可见性。

接口

都可以包含抽象方法,以及方法的实现,java中接口的方法都是抽象的,显得更强大了。和抽象类不同的是,接口不能保存状态。可以有属性但必须是val的,或者提供访问器的实现。接口属性不可以有后备字段。
接口用关键字 interface 来定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface MyInterface {
val property: Int // abstract

val propertyWithImplementation: String
get() = "foo"

fun foo() {
print(property)
}
}

class Child : MyInterface {
override val property: Int = 29
}

一个类或对象可以实现一个或多个接口

1
2
3
4
5
class Child : MyInterface {
fun bar () {
//函数体
}
}

复写

复写方法

kotlin 需要把可以复写的成员都明确注解出来,类名,函数名前面加open或者是abstract,复写时加override,java是不需要单独声明的。

1
2
3
4
5
6
7
8
open class Base {
open fun v() {}
fun nv() {}
}

class Derived() : Base() {
override fun v() {}
}

复写属性

与方法类似,var属性覆盖一个val属性,但反之则不允许

1
2
3
4
5
6
7
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo //可以在构造函数中重写
class Bar2 : Foo {
override var count: Int = 0
}

C 继承 a() 或 b() 的实现没有任何问题,因为它们都只有一个实现。但是 f() 有俩个实现,因此我们在 C 中必须复写 f() 并且提供自己的实现来消除歧义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open class A {
open fun f () { print("A") }
fun a() { print("a") }
}

interface B {
fun f() { print("B") } // 接口的成员变量默认是 open 的
fun b() { print("b") }
}

class C() : A() , B {
// 编译器会要求复写f()
override fun f() {
super<A>.f() // 调用 A.f()
super<B>.f() // 调用 B.f()
}
}

抽象类

一个类或一些成员可能被声明成 abstract。抽象方法默认带open,在它的类中没有实现方法。可以用一个抽象成员去复写一个带open的非抽象方法。如果函数没有函数体,那么默认是抽象的。

1
2
3
4
5
6
7
open class Base {
open fun f() {}
}

abstract class Derived : Base() {
override abstract fun f()
}

扩展

Kotlin 提供了一种,可以在不继承父类,也不使用类似装饰器设计模式的情况下对指定类进行扩展。 支持函数扩展和属性扩展。

函数扩展

fun 类名.扩展函数名(入参),为 MutableList<Int>类添加一个 swap 函数:

1
2
3
4
5
6
7
fun MutableList<Int>.swap(x: Int, y: Int) {
val temp = this[x] // this 对应 list
this[x] = this[y]
this[y] = tmp
}
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)// 在 `swap()` 函数中 `this` 持有的值是 `l`

扩展实际上并没有修改它所扩展的类。定义一个扩展,你并没有在类中插入一个新的成员,只是让这个类的实例对象能够通过.调用新的函数。扩展函数是静态分发的,扩展函数的调用决定于声明的参数的类型

1
2
3
4
5
6
7
8
9
open class C 
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
//输出 c

同名同参数的成员函数和扩展函数都存在时,调用的是成员函数

1
2
3
4
5
6
class C {
fun foo() { println("member") }

}
fun C.foo() { println("extension") }
//输出member

即使是一个空对象仍然可以调用该扩展,然后在扩展的内部进行 this == null 的判断。

1
2
3
4
5
fun Any?.toString(): String {
if (this == null) return "null"
// 在空检查之后,`this` 被自动转为非空类型,因此 toString() 可以被解析到任何类的成员函数中
return toString()
}

属性扩展

1
2
3
4
5
6
7
8
9
// 使用扩展属性(extension property)
var View.padLeft: Int
set(value) {
setPadding(value, paddingTop, paddingRight, paddingBottom)
}

get() {
return paddingLeft
}

伴随对象扩展

语法格式类名.Companion.扩展函数名

1
2
3
4
5
6
class MyClass {
companion object {}
}
fun MyClass.Companion.foo(){

}

扩展域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package foo.bar
fun Baz.goo() { ... }

package com.example,usage

import foo.bar.goo // 导入所有名字叫 "goo" 的扩展

// 或者

import foo.bar.* // 导入foo.bar包下得所有数据

fun usage(baz: Baz) {
baz.goo()
}

数据类

自带:

  • equals()/hashCode 函数
  • toString 格式是 “User(name=john, age=42)”
  • 对应按声明顺序出现的所有属性
  • copy() 函数

必须满足:

  • 主构造函数应该至少有一个参数;
  • 主构造函数的所有参数必须标注为 val 或者 var ;
  • 数据类不能是 abstract,open,sealed,或者 inner ;
  • 数据类不能继承其它的类(但可以实现接口)。
  • 在 JVM 中如果构造函数是无参的,则所有的属性必须有默认的值,(参看Constructors);
1
data class User(val name: String = "", val age: Int = 0)

复制

会对一些属性做修改但想要其他部分不变

1
2
val jack = User(name = "jack", age = 1)
val olderJack = jack.copy(age = 2)

数据类和多重声明

1
2
3
val jane = User("jane", 35)
val (name, age) = jane
println("$name, $age years of age") //打印出 "Jane, 35 years of age"

枚举类

每个枚举都是枚举类的一个实例,它们是可以初始化的。

1
2
3
4
5
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}

可以有对应的方法,以及复写基本方法。用分号把枚举常量定义和成员定义分开。

1
2
3
4
5
6
7
8
9
enum class ProtocolState {
WAITING {
override fun signal() = Taking
},
Taking{
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}

通过名字获得枚举常量

1
2
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>

泛型

泛型类

1
2
3
class Box<T>(t: T){
var value = t
}

类型可以推断的

1
val box = Box(1)//1是 Int 型,因此编译器会推导出我们调用的是 Box<Int>

声明处变型

通过注解类型参数 T 的来源,来确保它仅从 Source<T>成员中返回(生产),并从不被消费。使用out 修饰符:

1
2
3
4
5
6
7
8
abstract class Source<out T> {
abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}

接受一个类型参数逆变:只可以被消费而不可以 被生产。使用in。

1
2
3
4
5
6
7
8
9
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK!
}

多重声明

可以通过给对象插入多个成员函数

1
val (name, age) = person

在for循环中使用

1
2
3
4
for ((a, b) in collection) { ... }
for ((key, value) in map) {

}

一个函数返回俩个值

1
2
3
4
5
6
7
8
data class Result(val result: Int, val status: Status)

fun function(...): Result {
//...
return Result(result, status)
}

val (result, status) = function(...)

代理

类代理

Derived 类可以继承 Base 接口并且指定一个对象代理它全部的公共方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() { printz(x) }
}

class Derived(b: Base) : Base by b

fun main() {
val b = BaseImpl(10)
Derived(b).print()
}

代理属性

1
2
3
class Example {
var p: String by Delegate()
}

语法结构是: val/var 属性名 属性类型 by 类名(参数1,参数2...)
在 by 后面的表达式就是代理,因为get() set() 对应的属性会被 getValue() setValue()方法代理。前面添加operator关键字,必须要提供getValues()函数(如果是 var还需要 setValue())。

1
2
3
4
5
6
7
8
9
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}

调用的时候直接创建对象,通过代理属性直接调用getValue(),调用e.p=”test”是直接调用setValue()方法

1
2
val e = Example()
println(e.p)

标准代理

Kotlin 标准库为几种常用的代理提供了工厂方法

lazy() 是一个接受 lamdba 并返回一个实现延迟属性的代理,第一次调用 执行 lamdba 并传递 lazy() 并存储结果,以后每次调用时只是简单返回之前存储的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
val lazyValue: String by lazy {
println("computed!")
"Hello"
}

fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
//输出
//computed! 真正执行
//Hello 真正执行
//Hello 返回储存的值

可观察熟悉

Delegates.observable() 需要两个参数:一个初始值和一个用于修改的值 。每次我们给属性赋值时都会调用赋值操作进行之前的值。它有三个参数:一个将被赋值的属性,旧值,新值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import kotlin.properties.Delegates

class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}

fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
//输出结果
//<no name> -> first
//first -> second

在map中存储属性

这种操作经常出现在解析 JSON 或者其它动态的操作中。这种情况下你可以使用 map 来代理它的属性。

这是一个只读的map

1
2
3
4
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}

一个可变的map

1
2
3
4
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}

调用的时候

1
2
3
4
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))

通过属性的名字直接取值

1
2
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25

反射

类引用

1
val c = MyClass::class

函数引用

:: 操作符右边不能用重载函数。

1
2
3
fun isOdd(x: Int) =x % 2 !=0
val numbers = listOf(1, 2, 3)
println(numbers.filter( ::isOdd) ) //prints [1, 3]

组合函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//f的入参等于g的返回,整个函数是g的入参,f的返回
fun<A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return {x -> f(g(x))}
// x 为string的入参
//调用g 得到长度
//调用f 得到是否为奇数
}
fun isOdd(x: Int) =x % 2 !=0 //入参int,返回boolean f
fun length(s: String) = s.length//入参string,返回int g

val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")

println(strings.filter(oddLength)) // Prints "[a, abc]"

属性引用

1
2
3
4
5
6
var x = 1
fun main(args: Array<String>) {
println(::x.get())
::x.set(2)
println(x)
}

访问类的属性

1
2
3
4
5
6
class A(val p: Int)

fun main(args: Array<String>) {
val prop = A::p
println(prop.get(A(1))) // prints "1"
}

构造函数引用

1
2
3
4
class Foo
fun function(factory : () -> Foo) {
val x: Foo = factory()
}

直接调用

1
function(:: Foo)

密封类

密封类用于代表严格的类结构,值只能是有限集合中的某中类型,不可以是任何其它类型。这就相当于一个枚举类的扩展。

但每个枚举常量只有一个实例,而密封类的子类可以有包含不同状态的多个实例。

密封类可以有子类但必须全部嵌套在密封类声明内部。

声明密封类需要在 class 前加一个 sealed修饰符。

1
2
3
4
5
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}

与when表达式结合

1
2
3
4
5
6
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}