第五章 Kotlin 面向对象编程(OOP)

1. 面向对象的​​HelloWorld​​

开篇我们来看一个OOP版本的HelloWorld:

package com.easy.kotlin
class Greeter(
val name: String// 属性
) {

// 行为
fun greet() {
println("Hello, ${name}");
}
}

fun main(args: Array<String>) {
Greeter("Jack").greet() // Kotlin创建对象,不再使用`new` keyword
}

Kotlin 同Java、 Scala、Groovy 一样,都使用关键字​​class​​ 来定义类。

新建一个类的实例无需像 Java 一样使用 new 关键字,直接调用构造函数​​Greeter("Jack")​​ 即可。

Kotlin 文件名以​​.kt​​​为后缀,源代码文件中可以定义多个类。​​Greeter.kt​​​经过编译之后的类文件默认命名是文件名加上​​Kt​​​结尾,即为​​GreeterKt.class​​。

2. 面向对象编程思想简述

在20世纪60年代,软件曾出现过严重危机,由软件错误而引起的信息丢失、系统报废事件屡有发生。为此,1968年,荷兰E.W.Dijkstra提出了程序设计中常用的GOTO语句的三大危害:

破坏了程序的动静一致性;
程序不易测试;
限制了代码优化。

此举引起了软件界长达数年的论战,并由此产生了结构化程序设计方法,同时诞生了基于这一设计方法的程序设计语言Pascal。   

由瑞士Niklaus Wirth开发的Pascal,具备优秀的数据结构和控制结构,为程序员提供了极大的方便性与灵活性,大受欢迎。笔者中学时候,第一门启蒙语言就是Pascal。至今还清晰记得那台式屏幕上蓝色的Turbo Pascal界面,闪烁着白色的代码的场景。

结构化程序设计思想采用了模块分解与功能抽象和自顶向下、分而治之的方法,从而有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子程序,便于开发和维护。因此,结构化方法迅速走红,并在整个20世纪70年代的软件开发中占绝对统治地位。   

但是,到了70年代末期,随着计算机科学的发展和应用领域的不断扩大,对计算机技术的要求越来越高。结构化程序设计语言和结构化分析与设计已无法满足用户需求的变化,于是面向对象编程(OOP)技术随之而来。 面向对象程序设计在未来的软件开发领域引起了大的变革,极大地提高了软件开发的效率。  

面向对象语言借鉴了20世纪50年代的人工智能语言LISP,引入了动态绑定的概念和交互式开发环境的思想;始于20世纪60 年代的离散事件模拟语言SIMULA67,引入了类和继承。于20世纪70年代的Smalltalk逐渐发展成熟。Java借鉴了SmallTalk,统治了互联网开发领域的大片江山。

面向对象编程思想,是为了解决现实问题而应运而生的。面向对象编程是一种自顶向下的程序设计方法.万事万物都是对象,对象有其行为(方法), 状态(成员变量,属性)。

所谓“类”和“对象”,对应过程式语言(例如,C语言)里面的结构体(struct),本质是一个逻辑抽象的代码 “映射”(map)(一切皆是映射)。

计算机领域中的所有问题,都可以通过向上一层进行抽象封装来解决.这里的封装的本质概念,其实就是”映射“。

从面向过程到面向对象,再到设计模式,架构设计,面向服务,各种软件理论五花八门,但万变不离其宗——你要解决一个怎样的问题?你对这个世界的本质认知是怎样的?你的业务领域的逻辑问题,流程等等。

Grady Booch:我对OO编程的目标从来就不是复用。相反,对我来说,对象提供了一种处理复杂性的方式。这个问题可以追溯到亚里士多德:您把这个世界视为过程还是对象?在OO兴起运动之前,编程以过程为中心--例如结构化设计方法。然而,系统已经到达了超越其处理能力的复杂性极点。有了对象,我们能够通过提升抽象级别来构建更大的、更复杂的系统--我认为,这才是面向对象编程运动的真正胜利。

人的生命只有一次。生命太短暂,所以不要去做一些重复无聊的事情。能交给计算机做的,就尽量交给计算机去做。此乃编程的滥觞之地。

纵览整个计算机的发展史,最重要的思想非“抽象”莫属。

一层层的抽象封装了实现的细节,计算机开疆扩土,南征北战,发展到了今天蔚为壮观的互联网,云计算,大数据,机器智能的时代。

同时,也使得程序员写代码,从最初的拿着符号表在纸袋上打孔,到使用近似自然语言的高级程序设计语言来编程,以及当今各种库,api,框架,集成开发工具集,智能化的编码提示,代码生成等等技术,使得我们现在程序员,能更多的去关注问题本身以及逻辑的实现。

从只有少数技术人会用的命令行的操作系统unix、dos,到人性化的GUI图形界面操作系统,以及移动互联网时代的智能设备,计算机越来越融入到人类生活的方方面面。

正如解决数学问题通常我们会谈“思想”,诸如反证法、化繁为简等,解决计算机问题也有很多非常出色的思想。思想之所以称为思想,是因为“思想”有拓展性与引导性,可以解决一系列问题。

解决问题的复杂程度直接取决于抽象的种类及质量。过将结构、性质不同的底层实现进行封装,向上提供统一的API接口,让使用者觉得就是在使用一个统一的资源,或者让使用者觉得自己在使用一个本来底层不直接提供、“虚拟”出来的资源。

计算机中的所有问题 , 都可以通过向上抽象封装一层来解决。

《易传·系辞上传》:“易有太极,是生两仪,两仪生四象,四象生八卦。” 如今的互联网世界,其基石却是01(阴阳),不得不佩服我华夏先祖的博大精深的智慧。

就好比通过的电子电路中的电平进行01逻辑映射,布尔代数逻辑体系映射到了数字逻辑电路系统;

我们最早使用机器码01来控制电平高低,进而控制数字电路操作寄存器,CPU等尝试着表达思想中的逻辑, 控制硬件计算和显示, 发现是可行的。

后来,我们把其中的最常用的操作步骤,进一步封装抽象成CPU指令集映射,于是诞生了汇编助记符语言——比机器指令更容易记忆;

再接着, 创造了编译器、解释器和计算机高级语言。通过汇编语言的向上抽象封装一层编译器,于是有了pascal,fortran,C语言;在牺牲少量性能的情况下, 获得比汇编语言更强且更容易使用的语句控制能力:条件、分支、循环, 以及更多的语言特性: 指针、结构体、联合体、枚举等, 还创造了函数, 能够将一系列指令封装成一个独立的逻辑块反复使用;再对核心函数api进行封装形成开发包(Development Kit) 。

逐渐地,产生了面向过程的编程方法;

后来, 人们发现将数据和逻辑封装成对象, 更接近于现实世界, 且更容易维护大型软件, 又出现了面向对象的编程语言和编程方法学, 增加了新的语言特性: 继承、 多态、 模板、 异常错误。

为了不必重复开发常见工具和任务, 人们创造和封装了容器及算法、SDK, 垃圾回收器, 甚至是并发库;

为了让计算机语言更有力更有效率地表达各种现实逻辑, 消解软件开发中遇到的冲突, 还在语言中支持了元编程、 高阶函数, 闭包 等有用特性。

为了更高效率地开发可靠的软件和应用程序, 人们逐渐构建了代码编辑器、 IDE、 代码版本管理工具、公共库、应用框架、 可复用组件、系统规范、网络协议、 语言标准等, 针对遇到的问题提出了许多不同的思路和解决方案, 并总结提炼成特定的技术和设计模式, 还探讨和形成了不少软件开发过程, 用来保证最终发布的软件质量。 尽管编写的这些软件和工具还存在不少 BUG ,但是它们都“奇迹般地存活”, 并共同构建了今天蔚为壮观的互联网时代的电商,互联网金融,云计算,大数据,物联网,机器智能等等的“虚拟世界”。

纵观计算机编程发展史,人类的大脑不断抽象、努力封装一个又一个逻辑体系——使得我们逐渐能够以更加友好、更加自然的方式去编写程序。

我们知道,编程的本质就是在创造世界。当然,这是一个虚拟的世界。是人类大脑对我们真实的世界的逻辑映射。既然是一个世界,就必然会有​​存在(对象,数据结构)​​​, 以及无限可能变化的​​运动(算法,方法,函数)​​。

不管是面向对象(存在)编程,还是函数式(运动(算法,方法,函数))编程,都是我们人类大脑对我们现实世界的问题的解决方案过程中,所建立的思维模型。模型毕竟还是模型,不可能装下全部的真实的世界。正是有像​​01​​​,​​阴阳​​ 这种形而上的哲学概念,才有了世界的无数种可能。

以上算是一些关于编程的粗浅的思考。好了,下面言归正传,进入正题。

3.Kotlin 面向对象编程(OOP)

3.1 声明类

Kotlin使用关键字class 声明类

class Book {
}

这个类声明被花括号包围,包括

  • 类名
  • 类head头(指定其类型参数,主构造函数等)
  • 类body。

类头和主干都是可选的。如果这个类没有body,花括号可以被省略。

class Empty

3.2 类修饰符

open 修饰符

Kotlin 默认会为每个变量和方法添加 final 修饰符。也就是说,在 Kotlin 中默认每个类都是不可被继承的。这么做的目的是为了程序运行的性能。

其实在 Java 程序中,你也应该尽可能为每个类添加final 修饰符( 见 《Effective Java 》第四章 17 条)。

如果你确定这个类是会被继承的,那么你需要给这个类添加 open 修饰符。

internal 修饰符

Java 有三种访问修饰符,public/private/protected。没有修饰符,是默认的包级别访问权限。

在 Kotlin 中,有private、protected、internal以及 public等四种修饰符,它们可用于修饰类、对象、接口、构造器、函数、属性、以及属性的set方法等。默认的访问权限是 public。其中 internal,是模块级别的访问权限。

模块(module)是指一起编译的一组 Kotlin 源代码文件:

  • 一个 IntelliJ IDEA 模块
  • 一个 Maven 工程, 或 Gradle 工程
  • 通过 Ant 任务的一次调用编译的一组文件

3.3 构造函数

在Kotlin中的类可以有主构造函数(Primary Constructor)和一个或多个二级构造函数(Secondary Constructor)。在 Scala 中称为 Main ConstructorSlave Constructor,Kotlin换了个名字,意思基本相同。

主构造函数是类头的一部分, 代码示例如下:

class Book1 constructor(val name:String, val author:String){

}

如果这个主构造函数没有任何注解或者可见的修饰符,这个constructor关键字可以被省略

class Book2 (val name:String, val author:String){

}

主构造方法的参数可以声明为 val 或 var ,使用方法与其声明为成员变量时相同。

这个主构造函数不能包含任何的代码。不过,初始化的代码可以被放置在initializer blocks(初始的语句块),以init为前缀作为关键字。该语句块中的所有可执行语句都属于主构造器,在对象被创建时都会被调用。

class Book2 (val name:String, val author:String){
init {
println("Book2 initialized with value (name= ${name}, author=${author})")
name = name.toUpperCase()
}
}

完整代码示例:

package com.easy.kotlin

/**
* Created by jack on 2017/5/30.
*/

class Empty


class Book1 constructor(val name:String, val author:String){
override fun toString(): String {
return "Book1(name='$name', author='$author')"
}
}

class Book2 (var name:String, val author:String){
init {
println("Book2 initialized with value (name= ${name}, author=${author})")
name = name.toUpperCase()
}

override fun toString(): String {
return "Book2(name='$name', author='$author')"
}


}

fun main(args:Array<String>){
println(Empty())
println(Book1("Easy Kotlin", "Jack"))
println(Book2("Easy Kotlin", "Jack"))
}

运行输出:

com.easy.kotlin.Empty@4b1210ee
Book1(name='Easy Kotlin', author='Jack')
Book2 initialized with value (name= Easy Kotlin, author=Jack)
Book2(name='EASY KOTLIN', author='Jack')

次(扩展)构造函数

类也可以拥有被称为"二级构造函数"(实现多个构造函数),通常被加上前缀"constructor"。

1、次构造函数不能有声明 val 或 var
2、如果类有一个主构造函数(无论有无参数),每个次构造函数需要直接或间接委托给主构造函数(即调用主构造方法或其它次构造函数),用this关键字来调用。

class Person {

var name: String = ""
var age: Int = 0

constructor() {
println("constructor 1 called!")

}

constructor(name: String) : this() {
println("constructor 2 called!")
this.name = name

}

constructor(name: String, age: Int) : this(name) {
println("constructor 3 called!")
this.name = name
this.age = age
}

override fun toString(): String {
return "Person(name='$name', age=$age)"
}

}


fun main(args: Array<String>) {
println(Person())
println(Person("Jack"))
println(Person("Jack",29))
}

运行输出:

constructor 1 called!
Person(name='', age=0)
constructor 1 called!
constructor 2 called!
Person(name='Jack', age=0)
constructor 1 called!
constructor 2 called!
constructor 3 called!
Person(name='Jack', age=29)

我们可以看出,调用构造函数​​Person("Jack",29)​​ 创建对象,编译器通过层层向上委托,最终完成该对象的创建。

构造函数传参

fun main(args: Array<String>) {
val pair = Pair(1, "one")

val (num, name) = pair

println("num = $num, name = $name")

val triple = Triple(10,"B",10.0)
val (a,b,c) = triple
println("a=$a, b=$b, c=$c")
}

class Pair<K, V>(val first: K, val second: V) {
operator fun component1(): K {
return first
}

operator fun component2(): V {
return second
}
}

class Triple<K,V,T>(val first: K,val second:V,val third:T){
operator fun component1():K{return first}
operator fun component2():V{return second}
operator fun component3():T{return third}
}

运行结果:

num = 1, name = one
a=10, b=B, c=10.0

3.4 创建类的实例

要创建一个类的实例,我们只要像普通的函数那样调用其构造函数即可:

val person = Person("Jack",29)

Kotlin中,不再使用​​new​​关键字

类成员

类可以包括

  • 构造和初始化模块
  • 函数
  • 属性
  • 匿名类
  • 内部类
  • 对象声明

3.5 继承

在Kotlin所有的类中都有一个共同的父类​​Any​​(这跟Scala一样),这是一个默认的open根父类。我们看一下Any类的源码吧:

package kotlin

/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
*/
public open class Any {
/**
* Indicates whether some other object is "equal to" this one. Implementations must fulfil the following
* requirements:
*
* * Reflexive: for any non-null reference value x, x.equals(x) should return true.
* * Symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
* * Transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true
* * Consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
*
* Note that the `==` operator in Kotlin code is translated into a call to [equals] when objects on both sides of the
* operator are not null.
*/
public open operator fun equals(other: Any?): Boolean

/**
* Returns a hash code value for the object. The general contract of hashCode is:
*
* * Whenever it is invoked on the same object more than once, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.
* * If two objects are equal according to the equals() method, then calling the hashCode method on each of the two objects must produce the same integer result.
*/
public open fun hashCode(): Int

/**
* Returns a string representation of the object.
*/
public open fun toString(): String
}

仔细阅读注释部分的内容,我们基本就能知道这个Any是啥了。

我们使用Kotlin编程,声明的所有类,都默认继承这个Any类。

class Example // Implicitly inherits from Any

Kotlin中所有的类默认都是不可继承的(​​final​​​),所以我们只能继承那些明确声明​​open​​​或者​​abstract​​的类。

当我们只有单个构造器时,我们需要在从父类继承下来的构造器中指定需要的参数。

代码示例:

open class Person {

var name: String = ""
var age: Int = 0

constructor() {
println("constructor 1 called!")

}

constructor(name: String) : this() {
println("constructor 2 called!")
this.name = name

}

constructor(name: String, age: Int) : this(name) {
println("constructor 3 called!")
this.name = name
this.age = age
}

override fun toString(): String {
return "Person(name='$name', age=$age)"
}

fun sayHi() {
val name = this.name
println("${name} say Hi to u!")
val b = Book("Easy Kotlin")
println(b)
}


class Book {
var name: String = ""

constructor() {

}

constructor(name: String) : this() {
this.name = name
}

override fun toString(): String {
return "Book(name='$name')"
}


}

}

class Student : Person {
var id: String = ""
var sex: String = ""

constructor() {
}

constructor(id: String, sex: String) : super() {
this.id = id
this.sex = sex
}

constructor(name: String, id: String, sex: String) : super(name) {
this.id = id
this.sex = sex
}

constructor(name: String, age: Int, id: String, sex: String) : super(name, age) {
this.id = id
this.sex = sex
}

override fun toString(): String {
return "Student(id='$id', sex='$sex', name=${super.name}, age = ${super.age})"
}


}

我们使用​​super.name​​​, ​​super.age​​来调用父类中的属性字段。

3.6 接口和抽象类

Kotlin接口使用interface关键字。Kotlin 的接口类似于 Java 8。可以包含抽象方法,以及方法的实现。和抽象类不同的是,接口不能保存状态;可以有属性但必须是抽象的 或 提供访问实现。

Kotlin抽象类使用​​abstract​​关键字声明。

Kotlin中的继承抽象类,实现接口的处理方式,跟Java一样,采用“单继承,多实现”的方式。代码示例如下:

abstract class A {
abstract fun fa()
abstract fun f()
}

interface B {
fun fb() {
print("FB")
}

fun f() {
print("B")
}
}

class C : A, B {
override fun fa() {
}

override fun fb() {
}

override fun f() {
}

constructor() {

}


}

我们直接使用​​class C : A, B​​这样的写法。比Java中使用extends, implements要简洁。

接口和抽象类的函数,默认是​​open​​的。我们可以不用标注。

另外,我们可以重写一个 ​​open​​​ 非抽象类的​​open​​函数,得到一个抽象类的抽象函数。

open class Base {
open fun f() {}
}

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

3.7 实现接口

package com.easy.kotlin

/**
* Created by jack on 2017/5/30.
*/

interface Clickable {
fun click()
}
class Button : Clickable {
override fun click() = println("I was clicked")
}

fun main(args: Array<String>) {
Button().click()
}

Kotlin中实现接口,使用冒号:关键字标识。实现类前使用override 关键字修饰。

3.8 override重写覆盖父类函数

Kotlin追求简洁显式的风格。

Kotlin在继承父类并覆盖父类函数时,要求父类必须有​​open​​​标注,被覆盖的函数必须有​​open​​​标注,并且子类的函数必须加​​override​​标注:

open class Base {
open fun v() {}
fun notopenv() {}
}

class Derived : Base() {
override fun v() {}
// override fun notopenv(){}// 'notopenv' in 'Base' is final, so it cannot be overridden
// fun notopenv() {} // 这样写也是不允许的
}

Derived.v()函数上必须加上​​override​​标注。如果没写,编译器将会报错。

如果父类的这个函数

open fun v() {}

没有标注​​open​​​,则子类中不允许定义同名函数,不论加不加​​override​​。

成员标记为override的本身是开放的,也就是说,它可以在子类中重写。如果你想禁止重写,使用final关键字:

open class AnotherDerived() : Base() {
final override fun v() {}
}

3.9 使用伴生对象声明静态类和方法

Kotlin中的伴生对象(companion objects),应用的场景就是Java或C#单例类。伴生对象里面的函数,对应的就是静态方法。

代码示例:

​java代码​

package com.restfeel.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@PropertySource(value = {"classpath:common.properties"})
public class PropertyConfig {

public PropertyConfig() {}

@Bean
public static PropertySourcesPlaceholderConfigurer myPropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile({"devlopment", "default"})
@PropertySource(value = {"classpath:env-development.properties"})
static class Dev {
}

/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile("test")
@PropertySource(value = {"classpath:env-test.properties"})
static class Test {
}

/**
* Properties to support the 'production' mode of operation.
*/
@Configuration
@Profile("production")
@PropertySource(value = {"classpath:env-production.properties"})
static class Production {
// Define additional beans for this profile here
}

}

对应的​​kotlin代码​​:

package com.restfeel.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.context.annotation.PropertySource
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer

/**
* Created by jack on 2017/3/29.
*/

@Configuration
@PropertySource(value = *arrayOf("classpath:common.properties"))
class ApplicationConfig {

@Bean
fun myPropertySourcesPlaceholderConfigurer(): PropertySourcesPlaceholderConfigurer {
return PropertySourcesPlaceholderConfigurer();
}

//静态类,伴生对象
companion object {
/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile(*arrayOf("devlopment", "default"))
@PropertySource(value = *arrayOf("classpath:env-development.properties"))
class Dev {
}

/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile("test")
@PropertySource(value = *arrayOf("classpath:env-test.properties"))
class Test {
}

/**
* Properties to support the 'production' mode of operation.
*/
@Configuration
@Profile("production")
@PropertySource(value = *arrayOf("classpath:env-production.properties"))
class Production {
// Define additional beans for this profile here
}
}


}

我们使用

//静态类,伴生对象
companion object {}

来声明静态类和方法。

3.10 嵌套类Nested Class

类可以嵌套在其他类中:

class Outer {
private val bar: Int = 1

class Nested {
fun foo1() = 2
//fun foo11() = bar// 访问不到
}

}

fun main(args: Array<String>) {
val nestedDemo = Outer.Nested().foo1() // 2
println(nestedDemo)
}

但是,这两个类​​Outer​​​, ​​Nested​​ 对象成员之间不能直接访问。比如说,

class Nested {
fun foo1() = 2
//fun foo11() = bar// 访问不到
}

​Nested​​​里面的函数​​foo11​​​ 是不能直接使用​​Outer​​​的成员变量​​bar​​的。要想访问外部类的成员变量,我们可以使用内部类。

3.11 内部类Inner Class

类可以标记为 ​​inner​​ 以便能够访问外部类的成员。内部类会带有一个对外部类的对象的引用:

class Outer {
private val bar: Int = 1

class Nested {
fun foo1() = 2
//fun foo11() = bar// 访问不到
}

inner class Inner {
fun foo2() = bar // inner class 能够访问外部类的成员
}
}

fun main(args: Array<String>) {
val nestedDemo = Outer.Nested().foo1() // 2
val innerDemo = Outer().Inner().foo2() // 1

println(nestedDemo)
println(innerDemo)
}

3.12 使用Kotlin的对象表达式创建匿名内部类

Kotlin使用对象表达式创建匿名内部类实例:

window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}

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

如果对象是函数式 Java 接口(即具有单个抽象方法的 Java 接口)的实例,
我们可以直接使用lambda表达式创建它:

val listener = ActionListener { println("clicked") }

多个超类型可以由跟在冒号后面的逗号分隔的列表指定:

open class AA(x: Int) {
open val y: Int = x
}

interface BB {}

val ab: AA = object : AA(1), BB {
override val y = 100
}

如果我们只需要“一个对象”,那么我们可以简单地写:

fun adHocf() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}

println("adHoc.x + adHoc.y = " + (adHoc.x + adHoc.y))
}

完整代码示例:

package com.easy.kotlin

/**
* Created by jack on 2017/5/30.
*/

class Outer {
private val bar: Int = 1

class Nested {
fun foo1() = 2
//fun foo11() = bar// 访问不到
}

inner class Inner {
fun foo2() = bar // inner class 能够访问外部类的成员
}
}


open class AA(x: Int) {
open val y: Int = x
}

interface BB {}

val ab: AA = object : AA(1), BB {
override val y = 100
}


class Hoc{
fun adHocf() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}

println("adHoc.x + adHoc.y = " + (adHoc.x + adHoc.y))
}
}



fun main(args: Array<String>) {
val nestedDemo = Outer.Nested().foo1()
val innerDemo = Outer().Inner().foo2()

println(nestedDemo)// 2
println(innerDemo)// 1

val h = Hoc()
println(h.adHocf())
val a = AA(1)
println(a.y) //1
println(ab.y) // 15
}

3.13 数据类data class

data 修饰的类称之为数据类。它通常用在我们写的一些 POJO 类上。

代码示例:

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

这个对应我们写的Java的Bean类。这些类有如下基本方法:

  • equals()/ hashCode() 函数
  • toString() 默认格式是: "User(name=John, age=42)",
  • ​componentN() 函数​​ corresponding to the properties in their order of declaration,
  • copy() 函数

其中,componentN()函数是通过属性位置来直接访问属性值。

完整代码示例:

package com.easy.kotlin

/**
* Created by jack on 2017/5/30.
*/

data class User(val name: String, val id: Int, val password: String)


fun main(args: Array<String>) {
val user = getUser()
println("name = ${user.name}, id = ${user.id}, password = ${user.password}")

val (name, id) = getUser()
println("name = $name, id = $id")
println("name = ${getUser().component1()}, id = ${getUser().component2()}")
}

fun getUser(): User {
return User("Alex", 1, "123456")
}

数据库实体类bean例子

package jason.chen.mini_springboot.restful.entity

import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
class Customer(
val firstName: String,
val lastName: String,
val gmtCreated: Date,
val gmtModified: Date,
val isDeleted: Int, //1 Yes 0 No
val deletedDate:Date,
@Id @GeneratedValue(strategy = GenerationType.AUTO)
val id: Long = -1) {
override fun toString(): String {
return "Customer(firstName='$firstName', lastName='$lastName', gmtCreated=$gmtCreated, gmtModified=$gmtModified, isDeleted=$isDeleted, deletedDate=$deletedDate, id=$id)"
}
}




data class Shop(val name: String, val customers: List<Customer>)

data class Customer(val name: String, val city: City, val orders: List<Order>) {
override fun toString() = "$name from ${city.name}"
}

data class Order(val products: List<Product>, val isDelivered: Boolean)

data class Product(val name: String, val price: Double) {
override fun toString() = "'$name' for $price"
}

data class City(val name: String) {
override fun toString() = name
}

3.14 建造者模式:构建一个对象

定义Rectangle对象,代码如下:

package geometry.shapes

import java.util.Random

class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}

fun createRandomRectangle(): Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}

3.15 封装一个日期工具类

package jason.chen.mini_springboot.restful.utils

import java.text.SimpleDateFormat
import java.util.*

/**
* Created by jack on 2017/3/11.
* @author jack
* @date 2017/03/11
*
* val date = Date()
date + 1 //后一天
date - 1 //前一天
date + Month(2) //后2月
date - Year(3) //前3年
date++ //本月的最后一天
date-- //本月的第一天
取年月日时分秒 date[0] date[1] date[2] 。。。

//日期比较
if( date1 > date2){

}

*/


enum class DateOptUnit {
YEAR,MONTH,DATE;
fun parseType():Int{
var value = Calendar.DATE
when(this){
YEAR -> value = Calendar.DATE
MONTH -> value = Calendar.MONTH
DATE -> value = Calendar.DATE
}
return value
}
}

data class DateOperator(val unit :DateOptUnit,val value: Int)

fun Any.year(value:Int):DateOperator {
return DateOperator(DateOptUnit.YEAR,value)
}

fun Any.month(value:Int):DateOperator {
return DateOperator(DateOptUnit.MONTH,value)
}

fun Any.day(value:Int):DateOperator {
return DateOperator(DateOptUnit.DATE,value)
}

/**
* date+1
* 往后的几天
*/
operator fun Date.plus(nextVal:Int):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(Calendar.DATE, nextVal)
return calendar.time
}

/**
* date-1
*/
operator fun Date.minus(nextVal:Int):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(Calendar.DATE, nextVal*-1)
return calendar.time
}

/**
* date+year(3)
* 往后的几天
*/
operator fun Date.plus(nextVal:DateOperator):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(nextVal.unit.parseType(), nextVal.value)
return calendar.time
}

/**
* date-month(4)
*/
operator fun Date.minus(nextVal:DateOperator):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(nextVal.unit.parseType(), nextVal.value*-1)
return calendar.time
}

/**
* 得到月末
*/
operator fun Date.inc():Date {
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(Calendar.MONTH, 1);
calendar.set(Calendar.DAY_OF_MONTH, 0);
return calendar.time
}

/**
* 得到月初
*/
operator fun Date.dec():Date {
val calendar = GregorianCalendar()
calendar.time = this
calendar.set(Calendar.DAY_OF_MONTH, 1)
return calendar.time
}

/**
* 取 年月日时分秒 0 - 5
* 例如 2015-12-21 22:15:56
* date[0]:2015 date[1]:12 date[2]:21
*/
operator fun Date.get(position:Int):Int {
val calendar = GregorianCalendar()
calendar.time = this
var value = 0
when(position) {
0 -> value = calendar.get(Calendar.YEAR)
1 -> value = calendar.get(Calendar.MONTH)+1
2 -> value = calendar.get(Calendar.DAY_OF_MONTH)
3 -> value = calendar.get(Calendar.HOUR)
4 -> value = calendar.get(Calendar.MINUTE)
5 -> value = calendar.get(Calendar.SECOND)
}
return value
}

/**
* 比较2个日期
* if(date1 > date2) {
* }
*/

operator fun Date.compareTo(compareDate : Date):Int {
return (time - compareDate.time).toInt()
}

/**
* 日期转化为字符串
*/
fun Date.stringFormat(formatType:String):String{
return SimpleDateFormat(formatType).format(this)
}

3.16 枚举类

在 Kotlin 中,每个枚举常量都是一个对象。枚举常量用逗号分隔。 代码示例:

package jason.chen.mini_springboot.restful.config

/**
* Created by jack on 2017/6/1.
*/
enum class KotlinBin(val binPath: String) {
KOTLINC("src/main/resources/kotlinc/bin/kotlinc "),
KOTLIN("src/main/resources/kotlinc/bin/kotlin ")
}

代码这样调用

package jason.chen.mini_springboot.restful.service

import jason.chen.mini_springboot.restful.config.KotlinBin
import org.springframework.stereotype.Service
import java.io.File

/**
* Created by jack on 2017/5/31.
*/

@Service
class KotlincService {

fun kotlinc(ktFile:String){
val file = File(".")
file.listFiles().forEach(::println)
val kotlinc = KotlinBin.KOTLINC.binPath + ktFile
val runtime: Runtime = Runtime.getRuntime()
val process: Process = runtime.exec(kotlinc)
val exitValue = process.waitFor()
if (exitValue != 0) {
println("exit with $exitValue")
return
}

process.inputStream.bufferedReader().lines().forEach {
println(it)
}
}
fun kotlin(ktFile:String):String{
kotlinc(ktFile)

val ktClass = ktFile.substring(0, ktFile.indexOf(".kt")) + "Kt"

val kotlin = KotlinBin.KOTLIN.binPath + ktClass
val runtime: Runtime = Runtime.getRuntime()
val process: Process = runtime.exec(kotlin)
val exitValue = process.waitFor()
if (exitValue != 0) {
println("exit with $exitValue")
return "exit with $exitValue"
}

var result=""
process.inputStream.bufferedReader().lines().forEach {
println(it)
result+=it
}
return result
}
}

3.17 sealed 密封类

sealed 修饰的类称为密封类,用来表示受限的类层次结构。例如当一个值为有限集中的 类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

小结

参考文档:

​https://github.com/kymjs/KotlinDoc-cn/blob/master/unit3/ClassesInheritance.md​