Kotlin使用指南之类篇特殊的类


Kotlin中的一些特殊的类


数据类


```KOTLIN

data class User(val name: String, val age: Int)

```


数据类就是在基本类的基础上,自动从**主构造函数**中声明的所有属性导出


1. equals/hashCode 对;

2. toString 格式是 "User(name=John, age=42)";

3. componentN 函数 按声明顺序对应于所有属性;

4. copy 函数(见下文)

这四个方法


需要注意的地方有


- 主构造参数至少要有一个参数

- 主构造参数中不要有非成员属性的变量,也就说所有的参数都要带var/val

- 类不能是open,abstract,private,inner的

- euqals,hashCode,toString这三个方法如果,自身已经实现,或者父类中使用final修饰了这些方法,编译时系统不会重新为我们实现这些方法。

- copy方法既不允许父类实现,也不允许数据类制剂实现

- componentN方法不允许自身实现,如果父类实现的类型不冲突,且非final,是被允许的。


componentN中的N是指参数的个数,如果主构造函数有两个参数,那么系统会为我们生成两个component1,component2方法,这两个方法是用来对数据类进行解构。使用方法如下。


```KOTLIN

val jane = User("Jane", 35)

val (name, age) = jane

println("$name, $age years of age") // 输出 "Jane, 35 years of age"

```


密封类


密封类的定义是你只能在同一个文件中继承他,也就是说他的所有子类只存在他所在的文件中。


```KOTLIN

sealed class Expr

data class Const(val number: Double) : Expr

data class Sum(val e1: Expr, val e2: Expr) : Expr

object NotANumber : Expr

```


sealed关键字表示密封类,需要注意:


- 一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。

- 密封类不允许有非-private 构造函数(其构造函数默认为 private)。

- 密封类子类的子类可以放在任何位置,而无需在同一个文件中。

- 使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了


嵌套类


```KOTLIN

class Outer {

private val bar: Int = 1

class Nested {

fun foo = 2

}

}


val demo = Outer.Nested.foo // == 2

```


注意,Kotlin中的嵌套类并不是内部类,不能使用外部类的属性,使用时和普通的类没有区别,唯一的不同就是使用时要使用 Outer.Nested这种方式来创建对象而不是直接Nested


内部类


Kotlin的内部类要使用inner关键字来声明


```kOTLIN

class Outer {

private val bar: Int = 1

inner class Inner {

fun foo = bar

}

}


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

```


内部类是可以使用外部类的属性个方法,内部类的this默认指向内部类自己,可以使用带有限定符的this来指向外部类,比如上面的代码可以使用this@Outer来指向外部类


匿名内部类


Kotlin中的匿名内部类用法如下


```KOTLIN

window.addMouseListener(object : MouseAdapter {


override fun mouseClicked(e: MouseEvent) { …… }


override fun mouseEntered(e: MouseEvent) { …… }

})

```


和Java中的用法差不多,唯一区别就是语法有一点区别,不赘述了


枚举类


```KOTLIN

interface ColorFace{

fun signal:ColorFace

}


enum class Color(val rgb: Int) :ColorFace{

RED(0xFF0000){

override fun signal = GREEN

},

GREEN(0x00FF00){

override fun signal = GREEN

},

BLUE(0x0000FF){

override fun signal = GREEN

}

}


```


由上面的代码我们可以知道,kotlin中的枚举与java一样可以有属性也可以有方法,并且可以实现接口,不过写法确实是比方便一些。


Kotlin 中的枚举类也有合成方法允许列出定义的枚举常量以及通过名称获取枚举常量。这些方法的签名如下


```KOTLIN

EnumClass.valueOf(value: String): EnumClass

EnumClass.values: Array<EnumClass>

```


如果指定的名称与类中定义的任何枚举常量均不匹配,valueOf 方法将抛出 IllegalArgumentException 异常


每个枚举常量都具有在枚举类声明中获取其名称与位置的属性:


val name: String

val ordinal: Int


枚举常量还实现了 Comparable 接口, 其中自然顺序是它们在枚举类中定义的顺序。


可以使用 enumValues<T> 与 enumValueOf<T> 函数以泛型的方式访问枚举类中的常量,结合上面的代码


```KOTLIN

print(enumValues<Color>.joinToString { it.name })

```


对象


kotlin中的对象关键字object有两种用处分别是对象表达式和对象声明


对象表达式


上面讲匿名内部时已经介绍过其用法值得注意的是如果匿名类的父类的构造函数是有参的必须要提供对应的参数


```KOTLIN

open class A(x: Int) {

public open val y: Int = x

}


interface B { /*……*/ }


val ab: A = object : A(1), B {

override val y = 15

}

```


当然我们也可以使用一个无父类的对象表达式,他会继承自Any


```KOTLIN

val adHoc = object {

var x: Int = 0

var y: Int = 0

}

```


这里需要注意的一点是,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问。


```KOTLIN

class C {

// 私有函数,所以其返回类型是匿名对象类型

private fun foo = object {

val x: String = "x"

}


// 公有函数,所以其返回类型是 Any

fun publicFoo = object {

val x: String = "x"

}


fun bar {

val x1 = foo.x // 没问题

val x2 = publicFoo.x // 错误:未能解析的引用“x”

}

}

```


这个也很好理解因为匿名的内部类对其他的外部类而言类型是不可见


对象声明


object同样可以用来声明类


```KOTLIN

object C

```


他的作用就剩声明一个单例,我们看一下他生成的java代码


```JAVA

public final class C {

public static final C INSTANCE;


static {

C c = new C;

INSTANCE = c;

}

}

```


可见就是一个饱汉式的单例模式,我们使用时也无需java中的getInstance,直接使用即可


```KOTLIN

DataProviderManager.registerDataProvider(……)

```


注意对象声明不能作用于内部类中


#### 伴生对象


上面我们说到对象声明不能作用于内部类中,那如果我们的刚好有需求想实现一个单例内部类呢?

这时就需要伴生对象


```KOTLIN

class MyClass {

companion object Factory {

fun create: MyClass = MyClass

}

}

```


同样伴生对象的名称也可省略,系统会为其命名为Companion


初始化时间


- 对象表达式是在使用他们的地方立即执行(及初始化)的;

- 对象声明是在第一次被访问到时延迟初始化的;

- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。


内联类


内联类的出现主要是用来处理,某些场景我们需要扩展原生类型已达到我们的需求,但是使用继承的方式会打破很多系统对原生类型的优化,进而导致性能的损耗。内联类的使用方式


```KOTLIN

inline class Name(val s: String) {

val length: Int

get = s.length


fun greet {

println("Hello, $s")

}

}


fun main {

val name = Name("Kotlin")

name.greet // `greet` 方法会作为一个静态方法被调用

println(name.length) // 属性的 get 方法会作为一个静态方法被调用

}

```


当然限制也是颇多


- 不能包含init代码块

- 不能使用附后字段,也就是说get和set方法只能是用简单的逻辑

- 只能继承接口,且只能是final的

- Java 中不能调用使用内联类作为形参的函数


在生成的代码中,Kotlin 编译器为每个内联类保留一个包装器。内联类的实例可以在运行时表示为包装器或者基础类型。这就类似于 Int 可以表示为原生类型 int 或者包装器 Integer。


因为内联类既可以表示为基础类型有可以表示为包装器,引用相等(===)对于内联类而言毫无意义,因此这也是被禁止的。


类型别名


类型别名为现有类型提供替代名称。 如果类型名称太长,你可以另外引入较短的名称,并使用新的名称替代原类型名。这点有些类似于C中的defind。

为类型


```KOTLIN

typealias NodeSet = Set<Network.Node>

```


为方法类型


```KOTLIN

typealias Predicate<T> = (T) -> Boolean

```


为内部类或嵌套类


```KOTLIN

class A {

inner class Inner

}

class B {

inner class Inner

}


typealias AInner = A.Inner

typealias BInner = B.Inner

```




Kotlin中的特殊的类就先介绍到这,将来会陆续整理Kotlin中其他的部分。