Kotlin 中的类和构造方法

Kotlin 中的类与接口和 Java 中的类与接口还是有区别的。例如,Koltin 中的接口可以包含属性声明,与 Java 不同的是。Kotlin 的声明默认是 final 和 public 的。此外,嵌套的类默认并不是内部类:它们并没有包含对其它外部类的隐式引用。

1 Kotlin 中的类以及接口

对象是什么?任何可以描述的事物都可以看作对象。我们以鸟为例,来分析它的组成:

  • 形状、颜色等可以看作是鸟的静态属性;
  • 年龄、大小等可以看作是鸟的动态属性;
  • 飞行、进食等可以看作是鸟的行为;
1.1 Kotlin 中的类

对象是由状态和行为组成的,我们可以通过它描述一个事物。 下面就是用 Kotlin 来抽象一个 Bird 类:

class Bird {val weight: Double = 500.0val color: String = "blue"val age: Int = 1fun fly() { // 全局可见}
}

在 Kotlin 中我们仍然可以使用熟悉的 class 结构体来声明一个类。

但是,Kotlin 中的类和 Java 中的类也有很多不同,将 Kotlin 代码反编译成 Java 版本:

public final class Bird {private final double weight = 500.0;@NotNullprivate final String color = "blue";private final int age = 1;public final double getWeight() {return this.weight;}@NotNullpublic final String getColor() {return this.color;}public final int getAge() {return this.age;}public final void fly() {}
}

可以看出主要存在以下不同:

  • 不可变属性成员:Kotlin 支持用 val 在类中声明引用不可变的属性成员,这是利用 Java 中的 final 修饰符来实现的,使用 var 声明的属性则反之引用可变;
  • 默认属性值:因为 Java 中的属性都是默认值,比如 int 类型的默认值为 0,引用类型的默认值为 null,所以在声明属性的时候我们不需要指定默认值。而在 Kotlin 中,除非显式地声明延迟初始化,不然就需要指定属性的默认值;
  • 不同的可访问修饰符:Kotlin 类中的成员默认是全局可见的,而 Java 的默认可见域是包作用域,因此在 Java 的版本中,我们必须采用 public 修饰才能达到相同的效果;
1.2 可带有属性和默认方法的接口

下面我们继续来看看 Kotlin 和 Java 中接口的差异。

以下是 Java 8 版本的接口:

public interface Flyer {public String kind();default public void fly(){System.out.println("I can fly");}
}

Java 8 引入了一个新特性 —— 接口方法支持默认实现。这使得我们在向接口中新增方法的时候,之前继承过该接口的类则可以不需要实现这个新方法。

Kotlin 的接口与 Java 8 中的相似:它们可以包含抽象方法的定义以及非抽象方法的实现(与 Java 8 中的默认方法类似),但它们不能包含任何状态。

接下来看在 Kotlin 中如何声明一个接口:

interface Flyer {val speed: Intfun kind()fun fly() {println("I can fly")}
}

同样,我们也可以用 Kotlin 定义一个带有方法实现的接口。同时,它还支持抽象属性(如上面的 speed)。然而,Kotlin 是基于 Java 6 的,那么它是如何支持这种行为的呢?

我们将上面的 Kotlin 声明的接口转换为 Java 代码,提取其中关键的代码:

public interface Flyer {int getSpeed();void kind();void fly();@Metadata(mv = {1, 8, 0},k = 3)public static final class DefaultImpls {public static void fly(@NotNull Flyer $this) {String var1 = "I can fly";System.out.println(var1);}}
}

通过以上的代码可以知道,Kotlin 编译器是通过定义了一个静态内部类 DefaultImpls 来提供 fly 方法的默认实现的。同时,虽然 Kotlin 接口支持属性声明,然而它在 Java 源码中是通过 get 方法来实现的。在接口中的属性并不能像 Java 接口那样,被直接赋值一个常量。 如以下这样做是错误的:

interface Flyer {val height = 1000 // error Property initializers are not allowed in interfaces
}

Kotlin 提供了另外一种方式来实现这种效果:

interface Flyer {val heightget() = 1000
}

这种写法与 Kotlin 实现该机制的背景有关。Kotlin 接口中的属性背后其实是用方法来实现的。所以说如果我们要为变量赋值常量,那么就需要编译器原生就支持方法默认实现。但 Kotlin 是基于 Java 6 的,当时并不支持这种特性,所以我们并不能像 Java 那样给一个接口的属性直接赋值一个常量。

下面我们来看如何在 Kotlin 接口中定义一个普通属性:

interface Flyer {val height: Long
}

它同方法一样,若没有指定默认行为,则在实现该接口的类中必须对该属性进行初始化。

接下来看如何使用这个接口:

class Brid(override val speed: Int) : Flyer {override fun kind() {TODO("Not yet implemented")}
}

Kotlin 类名后面使用冒号来代替了 Java 中的 extends 和 implements 关键字。和 Java 一样,一个类后面可以实现任意接口,但是只能继承一个类。

另外,和 Java 中的 @Override 注解类似,override 修饰符用来标注被重写的父类或者接口的方法和属性。与 Java 不同的是,在 Kotlin 中使用 override 修饰符是强制要求的。这会避免先写出实现方法再添加抽象方法造成的意外重写:我们的代码将不能编译,除非我们显式地将这个方法标注为 override 或者重命名它。

总体来说,Kotlin 的类与接口的声明和 Java 很类似,但它的语法整体上要显得更加简洁。

2 更简洁地构造类的对象

Kotlin 中并没有 new 关键字,我们可以通过以下的代码来直接声明一个类的对象:

val bird = Bird()

当前没有给 Bird 传入任何参数。现实中,很有可能因为需要传入不同的参数组合,而在类中创建多个构造方法,在 Java 中这是利用构造方法重载来实现的。

public class Bird {private double weight;private int age;private String color;public Bird(double weight, int age, String color) {this.weight = weight;this.age = age;this.color = color;}public Bird(int age, String color) {this.age = age;this.color = color;}public Bird(double weight) {this.weight = weight;}...
}

Java 中的这种方式存在两个缺点:

  • 如果要支持任意参数组合来创建对象,那么需要实现的构造方法将非常多;
  • 每个构造方法中的代码会存在冗余,如前两个构造方法都对 age 和 color 进行了相同的赋值操作;

Kotlin 通过引入新的构造语法来解决这些问题。

2.1 构造方法默认参数

要解决构造方法过多的问题,似乎也很简单。在 Kotlin 中我们可以给构造方法的参数指定默认值,从而避免不必要的方法重载。 现在用 Kotlin 来改写上述的例子:

class Bird(val weight: Double = 0.00, val age: Int = 0, val color: String = "blue")

使用:

val bird = Bird(color = "black")
val bird2 = Bird(weight = 1000.00, color = "black")

需要注意的是,由于参数默认值的存在,我们在创建一个类对象时,最好指定参数的名称,否则必须按照实际的参数的顺序进行赋值。否则会出现以下错误:

类型不匹配

我们在 Bird 类中可以用 val 或者 var 来声明构造方法的参数。这一方面代表了参数的引用可变性,另一方面它也使得我们在构造类的语法上得到了简化。

为什么这么说呢?事实上,构造方法的参数名前当然可以没有 val 或者 var,然而带上它们之后就等价于在 Bird 类内部声明了一个同名的属性,我们可以用 this 来进行调用。

以下是 Bird 类反编译成 Java 代码:

public final class Bird {private final double weight;private final int age;@NotNullprivate final String color;public final double getWeight() {return this.weight;}public final int getAge() {return this.age;}@NotNullpublic final String getColor() {return this.color;}public Bird(double weight, int age, @NotNull String color) {Intrinsics.checkNotNullParameter(color, "color");super();this.weight = weight;this.age = age;this.color = color;}// $FF: synthetic methodpublic Bird(double var1, int var3, String var4, int var5, DefaultConstructorMarker var6) {if ((var5 & 1) != 0) {var1 = 0.0;}if ((var5 & 2) != 0) {var3 = 0;}if ((var5 & 4) != 0) {var4 = "blue";}this(var1, var3, var4);}public Bird() {this(0.0, 0, (String)null, 7, (DefaultConstructorMarker)null);}
}
2.2 init 语句块

Kotlin 引入了一种叫做 init 语法块的语法,它属于上述构造方法的一部分,两者在表现形式上确是分离的。

Bird 类的构造方法在类的外部,它只能对参数进行复制。如果我们需要在初始化时进行其他的额外操作,那么我就可以使用 init 语句块来执行。比如:

class Bird(weight: Double, age: Int, color: String) {init {println("do some other things")println("the weight is ${weight}")}
}

反编译成 Java 代码:

public final class Bird {public Bird(double weight, int age, @NotNull String color) {Intrinsics.checkNotNullParameter(color, "color");super();String var5 = "do some other things";System.out.println(var5);var5 = "the weight is " + weight;System.out.println(var5);}
}

当没有使用 val 或者 var 的时候,构造方法的参数可以在 init 语句块中被直接调用。其实它们还可以用于初始化内部的属性成员的情况。 如:

class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {val weight: Double = weight // 在初始化属性成员时调用 weightval age: Int = ageval color: String = color
}

反编译成 Java 代码:

public final class Bird {private final double weight;private final int age;@NotNullprivate final String color;public final double getWeight() {return this.weight;}public final int getAge() {return this.age;}@NotNullpublic final String getColor() {return this.color;}public Bird(double weight, int age, @NotNull String color) {Intrinsics.checkNotNullParameter(color, "color");super();this.weight = weight;this.age = age;this.color = color;}// $FF: synthetic methodpublic Bird(double var1, int var3, String var4, int var5, DefaultConstructorMarker var6) {if ((var5 & 1) != 0) {var1 = 0.0;}if ((var5 & 2) != 0) {var3 = 0;}if ((var5 & 4) != 0) {var4 = "blue";}this(var1, var3, var4);}public Bird() {this(0.0, 0, (String)null, 7, (DefaultConstructorMarker)null);}
}

除此之外,我们并不能在其他地方使用。以下是一个错误的用法:

错误用法

事实上,我们的构造方法还可以拥有多个 init,它们会在对象被创建时按照类中从上到下的顺序先后执行。看看以下代码的执行结果:

class Bird(weight: Double, age: Int, color: String) {val weight: Doubleval age: Intval color: Stringinit {this.weight = weightprintln("The bird's weight is ${this.weight}")this.age = ageprintln("The bird's age is ${this.age}")}init {this.color = colorprintln("The bird's color is ${this.color}")}
}fun main() {val bird = Bird(1000.0, 2, "blue")
}// The bird's weight is 1000.0
// The bird's age is 2
// The bird's color is blue

可以发现,多个 init 语句块有利于我们进一步对初始化的操作进行职能分离,这在复杂的业务开发(如 Android)中显得特别有用。

再来思考一种场景,现实中我们在创建一个类对象时,很可能不需要对所有的属性都进行传值。其中存在一些特殊的属性,比如鸟的性别,我们可以根据它的颜色来进行区分,所以它并不需要出现在构造方法的参数列表中。

有了 init 语句块的语法支持,我们很容易实现这一点。假设黄色的鸟儿都是雌性,剩余的都是雄鸟,我们就可以如此设计:

class Bird(val weight: Double, val age: Int, val color: String) {val sex: Stringinit {this.sex = if (this.color == "yellow") "male" else "female"}
}

接下来继续修改需求。这一次我们并不想在 init 语句块中对 sex 直接赋值,而是调用一个专门的 printSex 方法来进行,如:

错误信息

报错了,主要由以下两个原因导致:

  • 正常情况下,Kotlin 规定类中的所有非抽象属性成员都必须在对象创建时被初始化值;
  • 由于 sex 必须被初始化值,上述的 printSex 方法中,sex 会被视为二次赋值,这对 val 声明的变量来说也是不允许的;

第 2 个问题比较容易解决,我们把 sex 变成用 var 声明,它就可以被重复修改。关于第 1 个问题,最直观的方法就是指定 sex 的默认值,但这可能是一种错误的性别含义;另一种方法是引用可空类型,即把 sex 声明为 String? 类型,则它的默认值为 null。这可以让程序正确运行,然而实际上也许我们又不想让 sex 具有可空性,而只是想稍后再进行赋值,所以这种方案也有局限性。

2.3 延迟初始化:by lazy 和 lateinit

更好的做法是让 sex 能够延迟初始化,即它可以不用在类对象初始化的时候就必须有值。 在 Kotlin 中,我们主要是使用 lateinit 和 by lazy 这两种语法来实现延迟初始化的效果。

下面来看如何使用它们?如果这是一个用 val 声明的变量,我们可以用 by lazy 来修饰:

class Bird(val weight: Double, val age: Int, val color: String) {val sex: String by lazy {if (color == "yellow") "male" else "female"}
}

by lazy 的语法特点如下所示:

  • 该变量必须是引用不可变的,而不能用 var 来声明;
  • 在被首次调用时,才会进行赋值操作。一旦被赋值,后续它将不能被更改;

lazy 背后是接受一个 lambda 并返回一个 Lazy<T> 实例的函数,第一次访问该属性的时候,会执行 lazy 对应的 Lambda 表达式并记录结果,后续访问该属性时只是返回记录的结果。

另外,系统会给 lazy 属性默认加上同步锁,也就是 LazyThreadSafetyMode.SYNCHRONIZED,它在同一时刻只允许一个线程对 lazy 属性进行初始化,所以它是线程安全的。但是如果能确认该属性可以并行执行,没有线程安全问题,那么可以给 lazy 传递 LazyThreadSafetyMode.PUBLICATION 参数。我们还可以给 lazy 传递 LazyThreadSafetyMode.NONE 参数,这将不会有任何线程方面的开销,当然也不会有任何线程安全的保证。 比如:

val sex: String by lazy(LazyThreadSafetyMode.PUBLICATION) {// 并行模式if (color == "yellow") "male" else "female"
}val sex: String by lazy(LazyThreadSafetyMode.NONE) {// 不做任何线程保证也不会有任何线程开销if (color == "yellow") "male" else "female"
}

与 lazy 不同,lateinit 主要用于 var 声明的变量,然而它不能用于基本数据类型,如 Int、Long 等,我们需要用 Integer 这种包装类作为替代:

class Bird(val weight: Double, val age: Int, val color: String) {lateinit var sex: String // sex 可以延迟初始化fun printSex() {this.sex = if (this.color == "yellow") "male" else "female"println(this.sex)}
}fun main() {val bird = Bird(1000.0, 2, "blue")bird.printSex()
}// female

那么是如何让用 var 声明的基本数据类型变量也具有延迟初始化的效果,一种可参考的解决方案是通过 Delegates.notNull<T>,这是利用 Kotlin 中委托的语法来实现的。

var test by Delegates.notNull<Int>()fun doSomething() {test = 1println("test value is $test")test = 2
}

总而言之,Kotlin 并不主张用 Java 中的构造方法来重载,来解决多个构造参数组合调用问题。取而代之的方案是利用构造参数默认值及用 val、var 来声明构造参数的语法,以便更简洁地构造一个类对象。

3 主从构造方法

有些时候,我们可能需要从一个特殊的数据中获取构造类的参数值,这时候如果可以定义一个额外的构造方法,接收一个自定义的参数会显得特别方便。

同样以鸟为例,先把之前的 Bird 类简化为:

class Bird(age: Int) {val age: Intinit {this.age = age}
}

假设当前我们知道鸟的生日,希望可以通过生日来得到鸟的年龄,然后创建一个 Bird 类对象。如何实现?

第 1 种方案是在别处定义一个工厂方法,如:

fun Bird(birth: Date) = Bird(getAgeByBirth(birth))

应该在哪里声明这个工厂方法呢?这种方式的缺点在于,Bird 方法与 Bird 类的代码层面的分离显得不够直观。

另外,我们还可以像 Java 那样新增一个构造方法来解决这个问题。其实,Kotlin 也支持多构造方法的语法,然而与 Java 的区别在于,它在多构造方法之间建立了“主从”的关系。

我们下面来用 Kotlin 中的多构造方法实现这个例子:

class Bird(age: Int) {val age: Intinit {this.age = age}constructor(birth: Date) : this(getAgeByBirth(birth)) {... }
}

下面来看看这个新的构造方法是如何运作的:

  • 通过 constructor 方法定义了一个新的构造方法,它被称为从构造方法。相应地,我们熟悉的在类外部定义的构造方法被称为主构造方法;
    • 每个类可最多存在一个主构造方法和多个从构造方法,如果主构造方法存在注解或可见性修饰符,也必须像从构造方法一样加上 constructor 关键字:internal public Bird @inject constructor(age: Int) { }
  • 每个从构造方法由两部分组成。一部分是对其他构造方法委托,另一部分是由花括号包裹的代码块。执行顺序上会先执行委托的方法,然后执行自身代码块的逻辑。

通过 this 关键字来调用要委托的构造方法。如果一个类存在主构造方法,那么每个从构造方法都要直接或者间接的委托给它。比如,可以把从构造方法 A 委托给从构造方法 B,再将从构造方法 B 委托给主构造方法。 举个例子:

class Bird(age: Int) {val age: Intinit {this.age = age}constructor(timestamp: Long) : this(DateTime(timestamp)) // 构造函数 Aconstructor(birth: DateTime) : this(getAgeByByBirth(birth)) // 构造函数 Bfun getAgeByByBirth(birth: DateTime): Int {return Year.yearsBetween(birth, DateTime.now()).years}}

其实,从构造方法的设计除了解决我们以上的场景之外,还有一个很大的作用就是可以对某些 Java 类库进行很好的扩展,因为我们经常要基于第三方 Java 库中的类,扩展自定义的构造方法。典型的例子就是定制业务中特殊的 View 类。

比如以下代码:

class KotlinView : View {constructor(context: Context) : this(context, null)constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr)}

可以看出,利用从构造方法,我们能使用不同参数来初始化第三方类库中的类了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/782669.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实施阶段(2024年3月)

【项目活动1】需求分析 学生&#xff1a;在系统中可以账号登陆&#xff0c;查看今日菜谱&#xff0c;点餐反馈。 食堂管理人员&#xff1a;对原始数据整合&#xff0c;显示菜品结果统计&#xff0c;并根据统计结果对菜品供应量进行调整反馈&#xff0c;避免浪费。 【项目活动…

MySQL开窗函数

测试环境&#xff1a;mysql8.0.18 官方文档&#xff1a;https://dev.mysql.com/doc/refman/8.0/en/window-functions.html 一、窗口函数介绍二、语法结构三、自定义窗口1.rows&#xff08;重点&#xff09;2.range3.默认窗口 四、常用窗口函数示例1.row_number & rank &…

C++之红黑树插入+查找功能实例(二百六十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

SeaTunnel 与 DataX 、Sqoop、Flume、Flink CDC 对比

产品概述 Apache SeaTunnel 是一个非常易用的超高性能分布式数据集成产品&#xff0c;支持海量数据的离线及实时同步。每天可稳定高效同步万亿级数据&#xff0c;已应用于数百家企业生产&#xff0c;也是首个由国人主导贡献到 Apache 基金会的数据集成顶级项目。 SeaTunnel 主…

当代深度学习模型介绍--循环神经网络(RNNs)

AI大模型学习 方向一&#xff1a;AI大模型学习的理论基础 模型和应用的多样化&#xff1a;随着研究的深入&#xff0c;深度学习领域出现了多种创新的模型架构&#xff1a; 卷积神经网络&#xff08;CNNs&#xff09;专门针对图像处理任务进行了优化&#xff0c;通过模拟生物视…

『Apisix系列』破局传统架构:探索新一代微服务体系下的API管理新范式与最佳实践

文章目录 『Apisix基石篇』『Apisix入门篇』『Apisix进阶篇』『Apisix安全篇』 『Apisix基石篇』 &#x1f680; 手把手教你从零部署APISIX高性能API网关 利用Docker-compose快速部署Apache APISIX及其依赖组件&#xff0c;实现高效的API网关搭建通过编写RPM安装脚本来自动化安…

IDEA MyBatisCodeHelper Pro最新版(持续更新)

目录 0. 你想要的0.1 包下载0.2 使用jh 1. 功能介绍2. 下载安装2.1 在idea中插件市场安装2.2 在jetbrains插件市场下载安装 3. 简单使用3.1 创建一个SpringBoot项目3.2 配置数据库3.3 一键生成实体类、mapper 0. 你想要的 0.1 包下载 测试系统&#xff1a;Windows&#xff08…

ERP与MES系统深度对接详细解决方案

此方案适合绝大部分生产型企业&#xff0c;企业如果有用到ERP&#xff0c;MES&#xff0c;WMS等其他系统的。可以借鉴以下的对接方案。 在ERP与MES系统对接的具体内容中&#xff0c;物料管理、出入库信息、物料清单&#xff08;BOM&#xff09;、生产计划、生产领料、生产用料的…

【吊打面试官系列】Redis篇 -Redis 回收进程如何工作的?

大家好&#xff0c;我是锋哥。今天分享关于 【Redis 回收进程如何工作的&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; ​Redis 回收进程如何工作的&#xff1f; 一个客户端运行了新的命令&#xff0c;添加了新的数据。Redi 检查内存使用情况&#xff0c;如 果…

浅析AI大模型当前存在的技术瓶颈和限制及解决方案

方向五&#xff1a;未来发展趋势与挑战 提示&#xff1a;展望AI大模型学习的未来发展趋势&#xff0c;并讨论当前面临的主要挑战。可以关注新技术、新方法的出现&#xff0c;以及它们对AI大模型学习的影响&#xff1b;同时&#xff0c;也可以分析当前存在的技术瓶颈和限制&…

P2802 回家

P2802 回家 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 虽然是普及-难度的题&#xff0c;但是感觉细节有很多。 细节&#xff1a; bfs第一次到 ( i , j ) (i, j) (i,j)&#xff0c;但是距离不一定是最小的 鼠标是一次性物品 血量到达 ( x x , y y ) (xx, yy) (xx,yy)为…

【文献分享】 机器学习 + 分子动力学 + 第一性原理计算 + 热力学性质(熔化温度 热导率 热膨胀系数)

分享一篇关于机器学习 分子动力学 第一性原理 熔化温度&#xff08;熔化温度 & 热导率 & 热膨胀系数&#xff09;的文章。 感谢论文的原作者&#xff01; 关键词&#xff1a; 1. Al−Li alloy 2. Neural network potential 3. Molecular dynamics 4. Thermal pr…

wireshark 使用

wireshark介绍 wireshak可以抓取经过主机网卡的所有数据包&#xff08;包括虚拟机使用的虚拟网卡的数据包&#xff09;。 环境安装 安装wireshark: https://blog.csdn.net/Eoning/article/details/132141665 安装网络助手工具&#xff1a;https://soft.3dmgame.com/down/213…

源支付V7开源版2.99,修复各种提示错误

源支付V7开源版2.99&#xff0c;修复各种提示错误 加密说明&#xff1a;200拿来的&#xff0c;只有8.1这个文件加密&#xff0c;其他文件无任何加密&#xff0c;已修复各种提示错误 测试其他开源版安装提示错误&#xff0c;有几个文件是加密的 注&#xff1a;开发不易&#…

AJAX(二):axios 和 fetch函数发送AJAX请求、同源策略、 jsonp、CORS

一、各种发送AJAX请求 jquery基于回调函数&#xff0c;axios基于promise 1.axios发送AJAX请求!!! axios (v1.5.0) - Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 Node.js 中。 | BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务 服务器&#xff1a; app.…

Redis类型 Stream Bitfield

Stream 类型 Stream类型就是Redis里的mq,是redis为了占领市场份额的产物 今天我们就来介绍一下Stream Redis的消息队列一般是两个方案 第一个是Lpush Rpop 队列的异步队列方案(一对一) 第二个方案就是pubsub(发布订阅)模式 (一对多) 注:这里如果没有消费者了,队列中的数据就直…

代码随想录第25天 | 组合总和||| 、 电话号码的字母组合

一、前言 参考文献&#xff1a;代码随想录 今天的还是回溯算法&#xff0c;主要用到了昨天的回溯组合方法和巧妙思路方法&#xff0c;让我们继续为算法打基础吧&#xff01; 二、组合总和||| 1、思路&#xff1a; 这一题和昨日的组合没啥太大区别只是遍历的范围变为了固定…

未来智慧停车:技术架构解析与创新应用

随着城市化进程的不断加速&#xff0c;停车难题已成为城市居民生活中的一大痛点。传统的停车方式已经无法满足日益增长的停车需求&#xff0c;而智慧停车系统则成为了解决这一难题的重要途径。本文将深入探讨智慧停车系统的技术架构&#xff0c;并探索其在城市管理和用户体验上…

echarts双柱状堆叠图

效果 代码 option {tooltip: {trigger: axis,axisPointer: { // 坐标轴指示器&#xff0c;坐标轴触发有效type: shadow // 默认为直线&#xff0c;可选为&#xff1a;line | shadow},formatter: function (params) { var res<div><p>时间&am…

数据库聚簇索引和非聚簇索引的区别

聚簇索引&#xff08;Clustered Index&#xff09;和非聚簇索引&#xff08;Non-clustered Index&#xff09;是数据库中两种不同的索引类型&#xff0c;它们的主要区别在于数据的存储方式和索引的结构&#xff1a; 数据存储方式&#xff1a; 聚簇索引&#xff1a;索引的叶子节…