《Kotlin极简教程》第五章 Kotlin面向对象编程(OOP)

一个OOP版本的HelloWorld

正式上架:《Kotlin极简教程》Official on shelves: Kotlin Programming minimalist tutorial
京东JD:https://item.jd.com/12181725.html
天猫Tmall:https://detail.tmall.com/item.htm?id=558540170670

/**
 * Here we have a class with a primary constructor and a member function.
 * Note that there's no `new` keyword used to create an object.
 * See http://kotlinlang.org/docs/reference/classes.html#classes
 */

class Greeter(val name: String) {
    fun greet() {
        println("Hello, ${name}");
    }
}

fun main(args: Array<String>) {
    Greeter(args[0]).greet()
}

构造函数传参

/**
 * This example introduces a concept that we call destructuring declarations.
 * It creates multiple variable at once. Anything can be on the right-hand
 * side of a destructuring declaration, as long as the required number of component
 * functions can be called on it.
 * See http://kotlinlang.org/docs/reference/multi-declarations.html#multi-declarations
 */

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

Data Class

We frequently create a class to do nothing but hold data. In such a class some standard functionality is often mechanically derivable from the data. In Kotlin, this is called a data class and is marked as data

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

The compiler automatically derives the following members from all properties declared in the primary constructor:

  • equals()/ hashCode() pair
  • toString() of the form "User(name=John, age=42)",
  • componentN() functions corresponding to the properties in their order of declaration,
  • copy() function
/**
 *  Data class gets component functions, one for each property declared
 *  in the primary constructor, generated automatically, same for all the
 *  other goodies common for data: toString(), equals(), hashCode() and copy().
 *  See http://kotlinlang.org/docs/reference/data-classes.html#data-classes
 */

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

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

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

    // or

    val (name, id) = getUser()
    println("name = $name, id = $id")

    // or

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

定义接口&实现之

package ch04.ex1_1_1_InterfacesInKotlin

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

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

写pojo 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
}



定一个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())
}


封装个日期工具类

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)
}



示例代码1

/**
 * This example introduces a concept that we call destructuring declarations.
 * It creates multiple variable at once. Anything can be on the right-hand
 * side of a destructuring declaration, as long as the required number of component
 * functions can be called on it.
 * See http://kotlinlang.org/docs/reference/multi-declarations.html#multi-declarations
 */

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

    val (num, name) = pair

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

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

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

示例代码2


/**
 *  Data class gets component functions, one for each property declared
 *  in the primary constructor, generated automatically, same for all the
 *  other goodies common for data: toString(), equals(), hashCode() and copy().
 *  See http://kotlinlang.org/docs/reference/data-classes.html#data-classes
 */

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

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

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

    // or

    val (name, id) = getUser()
    println("name = $name, id = $id")

    // or

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

类和继承

类声明Kotlin使用关键字*class *{:.keyword}

class Invoice {
}

这个类声明被花括号包围,包括类名、类头(指定其类型参数,主构造函数等)和这个类的主干。类头和主干都是可选的;
如果这个类没有主干,花括号可以被省略。

class Empty

构造

在Kotlin中的类可以有主构造函数和一个或多个二级构造函数。主构造函数是类头的一部分:它跟在这个类名后面(和可选的类型参数)

class Person constructor(firstName: String) {
}

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

class Person(firstName: String) {
}

这个主构造函数不能包含任何的代码。初始化的代码可以被放置在initializer blocks(初始的模块),以init为前缀作为关键字{:.keyword}

class Customer(name: String) {
    init {
        logger.info("Customer initialized with value ${name}")
    }
}

请注意,主构造的参数可以在初始化模块中使用。它们也可以在类体内声明初始化的属性:

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

事实上,声明属性和初始化主构造函数,Kotlin有简洁的语法:

class Person(val firstName: String, val lastName: String, var age: Int) {
  // ...
}

与普通属性一样,主构造函数中声明的属性可以是可变的或者是只读的

If the constructor has annotations or visibility modifiers, the constructor{: .keyword } keyword is required, and
the modifiers go before it:
如果构造函数有注解或可见性修饰符,这个constructor{: .keyword }需要被关键字修饰。

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

更多请查看Visibility Modifiers

扩展构造函数

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

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果类有一个主构造函数,每个二级构造函数需要委托给主构造函数,直接或间接地通过另一个二级函数。
委托到另一个使用同一个类的构造函数用this{: .keyword }关键字

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

如果一个非抽象类没有声明任何构造函数(原发性或继发性),这将有一个生成的主构造函数不带参数。构造函数的可见性是public。如果你不希望你的类有一个公共构造函数,你需要声明与非缺省可见一个空的主构造函数:

class DontCreateMe private constructor () {
}

注意在JVM上,如果所有的主构造函数的参数有默认值,编译器会产生一个额外的参数的构造函数,将使用默认值。
这使得更易于使用kotlin与通过参数构造函数创建类的实例,如使用Jackson或JPA库的时候。

class Customer(val customerName: String = "")

{:.info}

创建类的实例

要创建一个类的实例,我们调用构造函数,就好像它是普通的函数:

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意Kotlin不能有“new”关键字

类成员

类可以包括

继承

在Kotlin所有的类中都有一个共同的父类Any,这是一个默认的父类且没有父类型声明:

class Example // Implicitly inherits from Any

Any不属于java.lang.Object;特别是,它并没有任何其他任何成员,甚至连equals()hashCode()toString()都没有。

请参阅Java的互操作性更多的细节部分。

要声明一个明确的父类,我们把类型放到类头冒号之后:

open class Base(p: Int)

class Derived(p: Int) : Base(p)

如上所见,父类可以(并且必须)在声明继承的地方,用原始构造函数初始化。

如果类没有主构造,那么每个次级构造函数初始化基本类型
使用super{:.keyword}关键字,或委托给另一个构造函数做到这一点。
注意,在这种情况下,不同的二级构造函数可以调用基类型的不同的构造:

class MyView : View {
    constructor(ctx: Context) : super(ctx) {
    }

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
    }
}

父类上的open{:.keyword}标注可以理解为Java中final{:.keyword}的反面,它允许其他他类
从这个类中继承。默认情况下,在Kotlin所有的类都是final,
对应于 Effective Java
书中的17条:设计并显示标注继承,否则就禁止它

覆盖成员

我们之前提到过,Kotlin力求清晰显式。不像Java中,Kotlin需要明确的
标注覆盖的成员(我们称之为open)和重写的函数。(继承父类并覆盖父类函数时,Kotlin要求父类必须有open标注,被覆盖的函数必须有open标注,并且子类的函数必须加override标注。):

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

Derived.v()函数上必须加上override标注。如果没写,编译器将会报错。
如果父类的这个函数没有标注open,则子类中不允许定义同名函数,不论加不加override
在一个final类中(即没有声明open的类),函数上也不允许加open标注。

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

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

等等!!这样我怎么hack我的库?

我们这样设计继承和覆盖的方式(类和成员默认final),会让人很难继承第三方的类,因此很难进行hack。

我们认为这不是一个劣势,原因如下:

  • 最佳实践已经表明不应该使用这些hacks
  • 其他的有类似机制的语言(C++, C#)已经证明是成功的
  • 如果人们实在想hack,仍然有办法:比如某些情况下可以使用Java进行hack,再用Kotlin调用;或者使用面向切面的框架(Aspect)。(请参阅Java的互操作)

重写的规则

在Kotlin中,实现继承的调用通过以下规则:
如果一个类继承父类成员的多种实现方法,可以直接在子类中引用,
它必须重写这个成员,并提供其自己的实现(当然也可以使用父类的)。
为了表示从中继承的实现而采取的父类型,我们使用super{:.keyword}在尖括号,如规范的父名super<Base>

open class A {
  open fun f() { print("A") }
  fun a() { print("a") }
}

interface B {
  fun f() { print("B") } // interface members are 'open' by default
  fun b() { print("b") }
}

class C() : A(), B {
  // The compiler requires f() to be overridden:
  override fun f() {
    super<A>.f() // call to A.f()
    super<B>.f() // call to B.f()
  }
}

类C同时继承A和B是可以的,而且我们在调用a()和b()函数时没有任何问题,因为他们在C的基类中只有一个实现。
但是f()函数则在A,B中都有实现,所以我们必须在C中覆盖f(),并且提供我们的实现以消除歧义。

抽象类

类和其中的某些实现可以声明为abstract{:.keyword}。
抽象成员在本类中可以不用实现。。
因此,当一些子类继承一个抽象的成员,它并不算是一个实现:

abstract class A {
  abstract fun f()
}

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

class C() : A(), B {
  // We are not required to override f()
}

Note that we do not need to annotate an abstract class or function with open – it goes without saying.

We can override a non-abstract open member with an abstract one

需要注意的是,我们并不需要标注一个抽象类或者函数为open - 因为这不言而喻。

我们可以重写一个open非抽象成员使之为抽象的。

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

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

同伴对象

在Kotlin中,不像Java或C#,类没有静态方法。在大多数情况下,它建议简单地使用包级函数。

如果你需要写一个可以调用的函数,而不依赖一个类的实例,但需要访问的内部一个类(例如,一个工厂方法),你可以写为[对象声明](object_declarations.html)中的一员里面的那个类。

更具体地讲,如果你声明一个同伴对象在你的的类中,
你就可以在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;

/**
 * Created by santoshm1 on 04/06/14.
 *
 * Adds support for runtime property files. Run with -Dspring.profiles.active={production,default,development,test}
 * defaults to development.
 */

@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
        }
    }


}

参考文档:

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


Kotlin 开发者社区

国内第一Kotlin 开发者社区公众号,主要分享、交流 Kotlin 编程语言、Spring Boot、Android、React.js/Node.js、函数式编程、编程思想等相关主题。

开发者社区 QRCode.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容