Kotlin—什么是类构造函数?

类构造函数用于根据类定义构建对象。在这篇文章中,我们将讨论并比较Kotlin的构造函数与C++的语言特性。作为示例,我们将使用一个简单的点类。我们的示例类将x和y坐标保存为整数值。

1、概括

在Kotlin中,类的构造函数可以分为三个部分:主构造函数、初始化块和辅助构造函数。所有部件都是可选的。如果未定义,主构造函数和初始化函数将自动生成。主构造函数是类头的一部分,位于类名和可选类型参数之后。

2、默认/主构造函数

在每种OOP语言中,都使用特定的子例程来创建和准备新对象。Kotlin中的每个类都可以有一个主构造函数,如果没有使用其他修饰符,则可以省略constructor关键字。

2.1、自动生成

如果没有显示定义构造函数或初始化块,编译器将自动生成一个。在下面的代码中,点类将其成员变量初始化为0。这实际在Kotlin和C++中是相同的行为。

// Kotlin
class Point() {
    var x = 0
    var y = 0
}

甚至

// Kotlin
class Point {
    var x = 0
    var y = 0
}

// C++
class Point {
public:
    int x = 0;
    int y = 0
};

在下面的代码中,必须在对象构造期间分配点类的成员变量

// Kotlin
class Point(var x: Int, var y: Int)

// C++
class Point {
public:
    Point(int x, int y) : 
        x(x), y(y)
    {
    
    }
    
    int x = 0;
    int y = 0;
};

2.2、初始化块

即使构造函数是自动生成的,初始化块(简称为init)也会被执行。以下代码将为每个新对象打印Init。可以有任意数量的初始化块。它们将按照它们在代码中出现的顺序执行。

// Kotlin
class Point(var x: Int, var y: Int) {
    init {
        doSomething()
    }
    
    private fun doSomething() {}
}

// C++
class Point {
public:
    Point(int x, int y) : 
        x(x), y(y)
    {
        doSomething();
    }
    
    int x = 0;
    int y = 0;
private:
    void doSomething() {}
};

重要的是要知道,即使未指定主构造函数,也会执行初始化块。该调用将在辅助构造函数的主题运行之前发生。可以有超过1个初始化块。它们将按照它们在代码中出现的顺序运行。

// Kotlin
class Point {
    var x = 0
    var y = 0
    
    init {
        println("init 1")
    }
    
    constructor() {
        println("secondary")
    }
    
    init {
        println("init 2")
    }
}

// prints:
// init 1
// init 2
// secondary

那么主构造函数和init有什么区别呢?主构造函数不能有代码体。因此,init块是一个助手。

2.3、默认值/默认参数

在Kotlin中,您可以为任何构造函数参数指定默认值。第一个默认参数放置在哪个位置并不重要。

// Kotlin
class Pointer(var x: Int = 1, var y: Int) {

}

C++
// does not compile
class Point {
public:
    Point(int x = 1, int y):
        x(x). y(y)
    {
    
    }
    
    int x = 0;
    int y = 0;
};

3、次要构造函数/构造函数重载

在最后的示例中,我们展示了如何以单一方式创建对象。但是如何在Kotlin中重载构造函数呢?如何创建辅助构造函数?

如果类具有主构造函数,则每个辅助构造函数都需要直接或通过另一个辅助构造函数间接委托给主构造函数。

// Kotlin
class Point(var x: Int, var y: Int) {
    init {
        doSomething()
    }
    
    constructor(vec: Point, origin: Point): this(origin.x + vec.x, origin.y + vec.y) {
    
    }
    
    private fun doSomething() {
    
    }
}

// Kotlin
class Point(
    var x: Int = 0,
    var y: Int = 0
) {
    init {
        doSomething()
    }
    
    constructor(vec: Point, origin: Point): this() {
        x = origin.x + vec.x
        y = origin.y + vec.y
        doSomething()
    }
    
    private fun doSomething() {
    
    }
}

// C++
class Point {
public:
    Point(int x, int y):
        x(x), y(y)
    {
        doSomething()
    }
    
    Point(const Point &vec, const Point &origin) {
        x = origin.x + vec.x;
        y = origin.y + vec.y;
        doSomething();
    }
    
    int x = 0;
    int y = 0;
    
private:
    void doSomething() {
    
    }
};

如果您不调用主构造函数,您将收到错误Primary constructor call expected

正如您所看到的,在某些情况下,doSomething方法在对象创建期间会执行多次。您不希小心创建一个干净的代码结构以避免这种情况。一种可能性是使用静态工厂方法。

4、工厂方法

当使用设计模式工厂方法时,我们可以将对象创建委托给单个(主)构造函数。设置对象的逻辑位于静态访问函数内。优点之一使我们可以使用更好的命名。在以下代码示例中,可以从向量和另一个点创建一个点。工厂方法计算x和y坐标并将它们传递给主构造函数。

// Kotlin
class Point(var x: Int = 0, var y: Int = 0) {
    companion object {
        fun fromVector(vec: Point, origin: Point): Point {
            val x = origin.x + vec.x
            val y = origin.y + vec.y
            return Point(x, y)
        }
    }
    
    init {
        doSomething()
    }
    
    private fun doSomething() {
    
    }
}

class Point {
public:
    Point(int x,int y):
        x(x), y(y)
    {
        doSomething();
    }
    
    static Point fromVector(const Point &vec, const Point &origin) {
        int x = origin.x + vec.x;
        int y = origin.y + vec.y;
        return Point(x, y);
    }
    
    int x = 0;
    int y = 0;

private:
    void doSomething() {
    
    }
};

5、私有/受保护的构造函数

Kotlin中的可见性规则与C++中的相同。然而,Kotlin中的默认行为是public,而在C++中它是private。我们可以在public、protected和private中创建主要和次要构造函数。

public构造函数。该对象可以由客户端创建并进行子类化。

// Kotlin
class Point public constructor() {
    var x = 0
    var y = 0
}

// same as:
class Point (){
    var x = 0
    var y = 0
}

// C++
class Point {
public:

    Point() {
    
    }
    
    int x = 0;
    int y = 0;
};

protected的主构造函数。该对象不能由客户端创建,但可以进行子类化。为了能够由客户端创建对象点类至少需要一个public/protected构造函数。

// Kotlin
open class Point protected constructor() {
    var x = 0;
    var y = 0;
}

class DerivedPoint: Point() {

}

fun main() {
    var p = Point()// does not compile
    var p2 = DerivedPoint()
}

// C++
class Point {
public:
    int x = 0;
    int y = 0;
    
protected:
    Point() {
    
    }
};

class DerivedPoint: public Point {

};

private主构造函数。该对象不能由客户端创建,也不能被子类化。为了能够创建点类的子类,它将至少需要一个public或protected构造函数。

// Kotlin
open class Point private constructor() {
    var x = 0;
    var y = 0;
}

class DerivedPoint: Point() {
    // does not compile
}

// C++
class Point {
public:
    int x = 0;
    int y = 0;
private:
    Point() {
    
    }
};

class DerivedPoint: public Point {

};

5.1、单例

使用protected/private构造函数是一个示例是实现单例设计模式。

// Kotlin
class Point private constructor() {
    var x = 0
    var y = 0
    
    companion object {
        val instance = Point()
    }
}

或真是直接使用静态对象。

// Kotlin
object Point {
    var x = 0
    var y = 0
}

// C++
class Point {
public:
    int x = 0;
    int y = 0;
    
    static Point & instance() {
        static Point p;
        return p;
    }
    
private:
    Point() {
    
    }
};

6、复制构造函数

在C++中,声明复制构造函数也很常见。每当新创建一个对象并由另一个对象直接赋值时,就会调用此构造函数。下面的额代码演示了这一点。

//C++
auto obj1; // default constructor
auto obj2 = obj1; // copy constructor

在Kotlin中情况有些不同。首先,Kotin使用指针/引用。指针将有一个引用计数。为了演示该行为,我们将使用标准库中的std::shared_ptr

// Kotlin
var obj1 = AnyClass()
var obj2 = obj1

// C++
anto obj1 = std::make_shared<AnyClass>();
auto obj2 = obj1;

如果使用数据类,就会有可以调用的赋值函数来实际复制对象。对于所有其他情况,您必须自己实施解决方案。这样做要小心,因为每个(成员)变量的行为都像一个shared_ptr

为了实际复制对象,您必须实现自己的解决方案。

// Kotlin
class Point: Cloneable {
    var x = 0
    var y = 0
    
    public override fun clone(): Any {
        val newPoint = Point()
        newPoint.x = this.x
        newPoint.y = this.y
        return newPoint
    }
}

fun main() {
    var p = Point()
    var p2 = p.clone()
}

7、数据类构造函数

大多数事情也适用于数据类构造函数。但是,也有一些例外情况。

  1. 在Kotlin中,数据类默认构造函数是不可能的。以下代码将无法编译。作为C++中的等价物,我们使用结构体。通常,C++中的结构体用于纯结构体(无成员函数),即便如此,结构体和类的区别在C++中还是非常微妙的。
// Kotlin
// does not compile
data class Point() {

}

// C++
struct Point {

};

  1. 在构造函数中只允许使用valvar。这意味着每个构造函数参数都必须是数据类的一部分。
// does not compile
data class Point(var x: Int, y: Int) {

}

// C++
struct Point {
    Point(int x, int y):
        x(x)
    {
    
    }
    int x = 0;
};

8、继承

当使用继承和派生类时,Kotlin的行为类似于C++。如果默认构造函数可用,则直接调用它。但是必须在派生类中显示调用它。

// Kotlin
open class Point {
    var x = 0
    var y = 0
}

class DerivedPoint: Point() {

}

// C++
class Poiint {
public:
    int x = 0;
    int y = 0;
};

class DerivedPoint: public Point {

};

派生类必须调用一个可用的构造函数。在以下示例中,主构造函数有参数,而辅助构造函数没有参数。派生类调用父类的辅助构造函数。

// Kotin
open class Point(var x: Int, var y: Int) {
    constructor(): this(0, 0) {
    
    }
}

class DerivedPoint: Point() {

}

// C++
class Point {
public:
    Point(int x, int y):
        x(x), y(y)
    {
    
    }
    
    Point(){
    
    }
    
    int x = 0;
    int y = 0;
};

class DerivedPoint: public Point {

};

首先,构建整个父级(包括所有init块)然后构建子级。执行顺序如上所述。

// Kotlin
open class Point(var x: Int, var y: Int) {
    constructor(): this(0, 0) {
        println("secondary constructor parent")
    }
    
    init {
        println("init parent")
    }
}

class DerivedPoint: Point() {
    init {
        println("init child")
    }
}

// prints:
// init parent
// secondary constructor parent
// init child

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

推荐阅读更多精彩内容