Kotlin——什么是类构造函数?

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

1、概括

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

2、默认/主构造函数

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

2.1、自动生成

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

kotlin 复制代码
// Kotlin
class Point() {
    var x = 0
    var y = 0
}

甚至

kotlin 复制代码
// Kotlin
class Point {
    var x = 0
    var y = 0
}
ini 复制代码
// C++
class Point {
public:
    int x = 0;
    int y = 0
};

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

kotlin 复制代码
// Kotlin
class Point(var x: Int, var y: Int)
arduino 复制代码
// C++
class Point {
public:
    Point(int x, int y) : 
        x(x), y(y)
    {
    
    }
    
    int x = 0;
    int y = 0;
};

2.2、初始化块

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

kotlin 复制代码
// Kotlin
class Point(var x: Int, var y: Int) {
    init {
        doSomething()
    }
    
    private fun doSomething() {}
}
csharp 复制代码
// 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 复制代码
// 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 复制代码
// Kotlin
class Pointer(var x: Int = 1, var y: Int) {

}
ini 复制代码
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 复制代码
// 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 复制代码
// 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() {
    
    }
}
ini 复制代码
// 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 复制代码
// 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() {
    
    }
}
csharp 复制代码
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 复制代码
// Kotlin
class Point public constructor() {
    var x = 0
    var y = 0
}

// same as:
class Point (){
    var x = 0
    var y = 0
}
ini 复制代码
// C++
class Point {
public:

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

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

kotlin 复制代码
// 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()
}
arduino 复制代码
// C++
class Point {
public:
    int x = 0;
    int y = 0;
    
protected:
    Point() {
    
    }
};

class DerivedPoint: public Point {

};

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

kotlin 复制代码
// Kotlin
open class Point private constructor() {
    var x = 0;
    var y = 0;
}

class DerivedPoint: Point() {
    // does not compile
}
arduino 复制代码
// C++
class Point {
public:
    int x = 0;
    int y = 0;
private:
    Point() {
    
    }
};

class DerivedPoint: public Point {

};

5.1、单例

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

kotlin 复制代码
// Kotlin
class Point private constructor() {
    var x = 0
    var y = 0
    
    companion object {
        val instance = Point()
    }
}

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

csharp 复制代码
// Kotlin
object Point {
    var x = 0
    var y = 0
}
arduino 复制代码
// C++
class Point {
public:
    int x = 0;
    int y = 0;
    
    static Point & instance() {
        static Point p;
        return p;
    }
    
private:
    Point() {
    
    }
};

6、复制构造函数

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

arduino 复制代码
//C++
auto obj1; // default constructor
auto obj2 = obj1; // copy constructor

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

ini 复制代码
// Kotlin
var obj1 = AnyClass()
var obj2 = obj1
ini 复制代码
// C++
anto obj1 = std::make_shared<AnyClass>();
auto obj2 = obj1;

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

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

kotlin 复制代码
// 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++中还是非常微妙的。
csharp 复制代码
// Kotlin
// does not compile
data class Point() {

}
arduino 复制代码
// C++
struct Point {

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

}
arduino 复制代码
// C++
struct Point {
    Point(int x, int y):
        x(x)
    {
    
    }
    int x = 0;
};

8、继承

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

kotlin 复制代码
// Kotlin
open class Point {
    var x = 0
    var y = 0
}

class DerivedPoint: Point() {

}
ini 复制代码
// C++
class Poiint {
public:
    int x = 0;
    int y = 0;
};

class DerivedPoint: public Point {

};

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

kotlin 复制代码
// Kotin
open class Point(var x: Int, var y: Int) {
    constructor(): this(0, 0) {
    
    }
}

class DerivedPoint: Point() {

}
arduino 复制代码
// 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 复制代码
// 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
相关推荐
Dnelic-2 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen4 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
JIAY_WX11 小时前
kotlin
开发语言·kotlin
建群新人小猿14 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神15 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛15 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法16 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter17 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快18 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android