目录

Kotlin系列(十二):类的定义与多形式的构造器的使用


一、前言:

  • 面向对象的编程思维是与人们日常思考问题的思维非常相似的。
  • 而面向对象的语言也具有很好的特性,当前主流的C++/C#/Java/Object-C等都是面向对象的语言。就连广受前端开发者欢迎的Javascript的发展也是向着面向对象靠拢的。
  • Kotlin同样作为一门面向对象的语言。学习面向对象的语言,语法是次要的,更重要的是我们要培养面向对象的思维,一切皆对象。
  • Kotlin 作为类 Java 语言,在面向对象上具有与 Java 相似的特性,但是针对不同的情况进行了不同的优化。
  • 今天主要参考Kotlin官网提供的教程来学习Kotlin 中的类和构造函数,该篇不会非常深入,作为入门教程献给有需要的小伙伴们。

二、定义类和创建类的实例

  • Kotlin 中定义类与 Java 相同,使用 class 关键字:
  • 语法如下

[访问控制符] class 类名 [访问控制符] constructor 主构造函数{ //类成员 //次构造函数 }]

  • Kotlin 中的类名与 Java 相同,采用骆峰式命名法,首字母大写。

  • 如我们创建一个Person类,我们的类里面先暂时什么都没写,代码如下:

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person {
    
       }
    
  • Ok,我们来获取一个Person的实例对象,注意:要创建一个类的实例,只需要调用类的构造函数,不使用 new 关键字

  • 代码如下:

      @Test
      fun test1() {
      	var person = Person();
      	println(person) //Person@1b701da1
    
      }
    
  • Ok,可以看到我们打印出了Person的实例对象。

  • 如果Kotlin 中的类如果是空类,没有任何语句,则可以省略大括号。

  • 也就是说,假如我们只是定义了那么一个类,但是里面是空的,此时我们可以连中括号都省略掉。

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person
    
  • 这样子也是可以调用的

      @Test
      fun test1() {
      	var person = Person();
      	println(person) //Person@1b701da1
    
      }
    

三、初始化函数与主构造函数

1、初始化函数

  • kotlin提供了一个init的初始化函数来供我们编译一些初始化的代码,在类创建对象时自动调用。

  • OK,我们给Person类添加一个name属性,并通过初始化函数进行初始化。

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person {
      	var name: String;
    
      	init {
      		println("init be called")
      		this.name = "anjie"
        }
    
      	fun printName() {
      		println("my name is ${this.name}");
      	}
      }
    
  • 测试代码:

      @Test
      fun test2() {
      	var person = Person();
      	person.printName();
      }
    
  • 调用结果:

init be called my name is anjie

  • 可以看到,其在创建实例时调用了init函数,所以我们创建的类如果需要做一些初始化的操作,可以将其写在init函数体内。

2、构造函数

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

  • 构造函数是类在创建实例对象时调用的方法,如果你一个构造函数都没写,默认会给你创建一个无参的构造函数。但是,如果你编写有构造函数,其将不会自动创建无参构造函数。

  • OK,我们修改上边代码, 使其可以通过在创建实例时通过构造函数指定我们的name属性的值。

  • 代码如下:

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person {
      	var name: String;
    
      	init {
      		println("init be called")
      		this.name = "anjie"
        }
      	constructor(){
      		println("first constructor be called");
    
      	}
      	constructor(name:String){
      		println("second constructor be called");
      		this.name = name;
      	}
    
      	fun printName() {
      		println("my name is ${this.name}");
      	}
      }
    
  • 测试代码:

      @Test
      fun test2() {
      	var person1 = Person();
      	person1.printName();
    
      	var person2 = Person("wo bu shi anjie");
      	person2.printName();
      }
    
  • 结果输出为:

init be called first constructor be called my name is anjie init be called second constructor be called my name is wo bu shi anjie

  • 可以看到,在调用构造函数之前,其还是会先调用init函数,所以init的调用是在构造函数之前的。
  • 我们编写了两个构造函数,一个是无参数的,因为不编写他,我们在创建Person时就必须传入一个name的值进行初始化,否则编译会不通过。
  • 然后,我们可以看到,在不传参数时调用的是无参数的构造函数,在传参数时,调用的是有参数的构造函数

3、带默认值的构造函数

  • Kotlin提供了参数的默认值的功能,这个学过C++或者Swift的会比较熟悉,Java是没有这个特性的,这个是我比较讨厌的一个点,因为默认值是比较方便的一个特性,个人认为。

  • Ok,接下来开始我们的学习之旅。

  • 本次我们为Person添加一个age属性,次级构造函数我们给参数提供默认值。

  • 代码修改为:

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person {
      	var name: String;
      	var age: Int;
    
      	init {
      		println("init be called")
      		this.name = "anjie"
        this.age = 0;
      	}
    
      	constructor() {
      		println("first constructor be called");
    
      	}
    
      	constructor(name: String = "i am default value", age: Int = 10) {
      		println("second constructor be called");
      		this.name = name;
      		this.age = age;
      	}
    
      	override fun toString(): String {
      		return "Person(name='$name', age=$age)"
        }
    
      }
    
  • 测试代码

      @Test
      fun test2() {
      	var person1 = Person();
      	println(person1.toString())
    
      	var person2 = Person(age = 10);
      	println(person2.toString())
    
      }
    
  • 结果输出为

init be called first constructor be called Person(name=‘anjie’, age=0) init be called second constructor be called Person(name=‘hello’, age=10)

  • 代码解析:
  • 可以看到我们只传了age参数的值为10,name为默认值,其结果与预期一致。

4、构造函数的简略写法

  • 当然我们的主构造函数还可以直接在类名旁边编写,如下:

      package com.anjie.demo
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person constructor(name: String = "default'name", age: Int = 0) {
      	var name: String;
      	var age: Int;
    
      	init {
      		println("init be called")
      		this.name = name
      		this.age = age;
      	}
    
      	override fun toString(): String {
      		return "Person(name='$name', age=$age)"
        }
    
      }
    
  • 其代码等同于

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person  {
      	var name: String;
      	var age: Int;
    
      	constructor(name: String = "default'name", age: Int = 0) {
      		this.name = name
      		this.age = age;
      	}
    
      	override fun toString(): String {
      		return "Person(name='$name', age=$age)"
        }
    
      }
    
  • 其形式决定了主构造函数的形式。

  • 其还可以进一步简化如下:

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person constructor(var name: String = "default'name", var age: Int = 0) {
      	init {
      		println("init be called")
      	}
    
      	override fun toString(): String {
      		return "Person(name='$name', age=$age)"
        }
    
      }
    
  • 变化解析:

  • 如果主构造函数中定义的参数使用 val 或者 var 修饰,则会创建与这个参数同名的成员变量,并使用传入的参数值初始化这个成员变量。

  • 简单来说,就是把“定义成员变量”和“使用构造函数传入的参数初始化成员变量”简化为一个 val 或者 var 关键字

  • 不仅如此,如果你的构造器使用的是kotlin默认的访问权限,仍可以进一步省略掉constructor关键字

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person constructor(var name: String = "default'name", var age: Int = 0) {
      	init {
      		println("init be called")
      	}
    
      	override fun toString(): String {
      		return "Person(name='$name', age=$age)"
        }
    
      }
    

四、次级构造函数

  • 我们前面说过,一个类只有一个主构造函数,可以有0个或者多个次级构造函数,次级构造函数我们需要让其调用我们的主构造函数或者调用次级构造函数间接地调用我们的主构造函数。

  • 示例代码修改如下

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */class Person {
      	var name: String;
      	var age: Int;
    
      	init {
      		println("init be called")
      		this.name = "anjie"
        this.age = 0;
      	}
    
      	constructor() {
      		println("first constructor be called");
    
      	}
    
      	constructor(name: String = "i am default value", age: Int = 10) : this() {
      		println("second constructor be called");
      		this.name = name;
      		this.age = age;
      	}
    
      	override fun toString(): String {
      		return "Person(name='$name', age=$age)"
        }
    
      }
    
  • 我们的次级构造函数通过:this(参数列表)调用了我们的主构造函数。

  • 测试代码如下:

      @Test
      fun test2() {
      	var person1 = Person();
      	println(person1.toString())
    
      	var person2 = Person(age = 10);
      	println(person2.toString())
    
      }
    
  • 结果输出如下:

init be called first constructor be called Person(name=‘anjie’, age=0) init be called first constructor be called second constructor be called Person(name=‘i am default value’, age=10)

  • 结果解析:
  • 可以看到,其掉用次级构造函数之前先调用了主构造函数

五、默认参数的优点

  • 写过java的同学都知道,java没有默认参数的概念。

  • 假如我们的person类有多种情况,每种情况可能只需要传入不同的参数进行初始化即可。

  • 我们看一下java代码:

      public class Person {
        long id;
        String name = "";
        int age = 0;
    
        public Person(long id, String name, int age) {
      	this.id = id;
      	this.name = name;
      	this.age = age;
        }
        public Person(long id, String name) {
      	this.id = id;
      	this.name = name;
        }
        public Person(long id, int age) {
      	this.id = id;
      	this.age = age;
        }
        public Person(long id) {
      	this.id = id;
        }
    
      }
    
  • 可以看到,其需要的构造方法非常的多,但是我们的kotlin只需要一行带默认参数的构造函数即可.

      package com.anjie.demo
    
      /**
       * Created by 安杰 on 2017/10/23. */
       class Person (id:Long,age: Int=0,name: String=""){
       }
    
  • 没错,就是这么简单,喜欢的话就一起学习吧,虽然语言只是一个工具,作为一名程序员更多应该学习和培养的是编程的思维,但是好的工具能给予更高效的表达能力。


六、总结

  • 本次我们虽然只是简单的学习了kotlin类的定义以及构造函数相关的知识点,但是知识的积累是需要坚持的,再则,技术学习不宜操之过急,希望能帮到大家,谢谢!
  • 人生苦短我用Kotlin。