类构造函数用于根据类定义构建对象。在这篇文章中,我们将讨论并比较Kotlin的构造函数与C++的语言特性。作为示例,我们将使用一个简单的点类。我们的示例类将x和y坐标保存为整数值。
1、概括
在Kotlin中,类的构造函数可以分为三个部分:主构造函数[1]、初始化块[2]和辅助构造函数[3]。所有部件都是可选的。如果未定义,主构造函数和初始化函数将自动生成。主构造函数是类头的一部分,位于类名和可选类型参数之后。
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、数据类构造函数
大多数事情也适用于数据类构造函数。但是,也有一些例外情况。
- 在Kotlin中,数据类默认构造函数是不可能的。以下代码将无法编译。作为C++中的等价物,我们使用结构体。通常,C++中的结构体用于纯结构体(无成员函数),即便如此,结构体和类的区别在C++中还是非常微妙的。
// Kotlin
// does not compile
data class Point() {
}
// C++
struct Point {
};
- 在构造函数中只允许使用
val
或var
。这意味着每个构造函数参数都必须是数据类的一部分。
// 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
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题