深入解析Kotlin类与对象:构造、伴生、单例全面剖析

前言

本篇文章将带您了解Kotlin编程中的重要概念:类及构造函数、访问修饰符、伴生对象和单例模式。就像搭积木一样,我们会逐步揭开这些概念的面纱,让您轻松理解它们的作用和用法。无论您是编程新手还是有经验的开发者,本文都将为您提供有趣而易懂的内容,帮助您更好地掌握Kotlin中类与对象的重要知识点。让我们一起开始这段有趣的学习之旅吧!

一、Kotlin 的类以及构造函数

1、声明一个类

当你使用 class 关键字在 Kotlin 中声明一个类时,其基本的语法格式如下所示:

class ClassName {// 类的成员变量和方法在这里定义
}

2、关于Kotlin类的继承和接口实现的规则

(1)继承父类

如果一个类有父类,你可以在类的声明中使用冒号 : 后面跟着父类的名称。这称为类的继承。例如:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivityclass MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 在这里可以进行一些初始化操作}
}
  • 继承父类无参构造函数:如果父类有一个无参构造函数,子类可以省略括号,并且不需要在子类中声明构造函数。这适用于父类的构造函数没有参数的情况,或者所有参数都有默认值。

  • 继承父类有参构造函数:如果父类的构造函数有参数,或者你想在子类中添加自己的构造函数,就需要根据具体情况使用括号并传递必要的参数。

(2)默认父类

如果一个类没有显式声明父类,它的默认父类是 Any 类,而不是 Object 类。Any 是 Kotlin 根类层次结构的顶层类型。所有的 Kotlin 类都直接或间接地继承自 Any 类。

Any 类的声明如下:

public open class Any

在 Kotlin 中,默认情况下,所有类都隐式地继承自 Any 类,即使你没有明确指定。因此,以下两个类声明是等价的:

class MyClass
class MyClass : Any()

这就是为什么你在 Kotlin 中无需显式指定父类,而默认情况下所有类都已经继承自 Any

需要注意的是,Any 类定义了一些通用方法,如 equalshashCodetoString,因此所有的 Kotlin 类都会具备这些方法。

(3)接口实现

在 Kotlin 中,可以在类的声明后面使用冒号 : 来实现一个或多个接口。你不需要显式使用 implements 关键字。例如:

import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivityclass MainActivity : AppCompatActivity(), View.OnClickListener {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val button = findViewById<Button>(R.id.button)button.setOnClickListener(this)}override fun onClick(v: View?) {when (v?.id) {R.id.button -> {// 在这里定义按钮点击事件的逻辑}}}
}

在上面的示例中,我们首先让 MainActivity 类实现了 View.OnClickListener 接口,并重写了其中的 onClick 方法。然后,我们在 onCreate 方法中使用 setOnClickListener(this) 将当前 MainActivity 对象作为按钮的点击监听器。

在 Kotlin 中,你是可以先写接口后写父类,或者先写父类后写接口,都是完全合法的。在类的声明中,继承父类和实现接口的顺序可以根据你的需求进行灵活安排。

3、open 关键字的作用

在 Kotlin 中,默认情况下,类是 publicfinal 的,这意味着它们对外部可见,并且不能被继承。如果你希望其他类能够继承这个类,你需要使用 open 关键字来声明这个类,使它变成可继承的。

在 Kotlin 中,修饰符的含义如下:

  • public:表示该成员(类、函数、属性等)在任何地方都可见。
  • final:表示类、函数或属性不能被继承或覆盖(重写),在类级别指的是类不能被继承。
  • open:用于修饰类、函数或属性,表示它们可以被继承或覆盖。

如果你想要创建一个可被继承的类,你需要使用 open 修饰符。

例如,当你想在 Android 应用中创建一个可被继承的 MainActivity 类时,你需要在类的声明前面加上 open 修饰符,以允许其他类继承它。这样,其他类就可以继承 MainActivity 并在子类中进行扩展。

以下是一个示例,展示如何在 Android 应用中创建一个可被继承的 MainActivity 类:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivityopen class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 在这里可以进行一些初始化操作}
}

在这个示例中,我们创建了一个名为 MainActivity 的类,继承自 AppCompatActivity,这是 Android 中用于创建活动(Activity)的基类。注意,在类的声明中,我们使用了 open 修饰符,允许其他类继承这个 MainActivity

其他类可以继承这个 MainActivity 并在子类中进行扩展,例如:

class CustomActivity : MainActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 在这里可以进行自定义的初始化操作}
}

在上面的示例中,CustomActivity 类继承自 MainActivity,并在子类中重写了 onCreate 方法,实现了自定义的初始化操作。

总结,通过使用 open 修饰符,你可以在 Android 应用中创建一个可被继承的基类,让其他类能够继承它并在子类中添加自己的功能。

4、主构造函数和次构造函数

构造函数是用于创建对象的特殊方法,它在对象创建时被调用,通常用于执行初始化操作。在 Kotlin 中,类可以有一个主构造函数和多个次构造函数。

(1)主构造函数

当涉及到在 Kotlin 类中创建实例时,主构造函数扮演着重要的角色。主构造函数允许在对象被实例化时接收参数,并且对对象的属性进行初始化。以下是关于主构造函数的概要:

  • 声明方式:主构造函数是类定义的一部分,紧随类名后面的括号中可以包含参数列表。如果主构造函数没有其他修饰符,那么括号可以省略。
  • 参数传递:主构造函数的参数可以在整个类范围内使用,不仅限于初始化代码块。你可以将这些参数直接用于属性的初始化。
  • 初始化代码块:主构造函数也可以包含初始化代码块,使用 init 关键字定义。在这个代码块中,你可以编写在对象初始化时执行的代码。

以下是一个示例,展示了如何声明主构造函数、使用参数初始化属性,并在初始化代码块中执行逻辑,其中使用了自定义视图类 CustomView

class CustomView(context: Context, attrs: AttributeSet): View(context, attrs) {private val paint: Paint = Paint()init {paint.color = Color.BLACK}
}

在上述示例中,CustomView 类继承自 View,并在主构造函数中接收了 contextattributeSet 参数。

在类的主体中,声明了一个名为 paint 的私有属性,并在初始化时进行了属性初始化。使用了 Paint 类,这是 Android 中用于绘制图形和文本的工具类。

在类的 init 块中,设置了 paint 的颜色为黑色(Color.BLACK)。这将确保 paint 对象在视图实例化时就被配置为黑色,以便在后续的绘制操作中使用。

当创建 CustomView 的实例时,会触发 init 块中的代码执行,从而完成属性的初始化设置。init 块是一个在对象实例化时执行初始化代码的地方,它允许你在构造函数中接收参数,并在 init 块中执行必要的初始化逻辑。在自定义视图类中,你可以使用 init 块来确保对象在使用之前处于正确的状态。

(2)次构造函数

次构造函数是在 Kotlin 中用于提供额外的构造函数选项的一种方式。它允许你在同一个类中定义多个不同参数集的构造函数,以满足不同的初始化需求。次构造函数通常调用主构造函数或者其他次构造函数,确保共享的初始化逻辑被重复使用。

以下是关于次构造函数的一些重要信息:

  • 声明方式:次构造函数通过 constructor 关键字声明,紧随其后的括号内包含参数列表。次构造函数可以有不同的参数集,但不能直接初始化属性。
  • 调用主构造函数:在次构造函数内部,你可以使用 this() 调用主构造函数,确保共享的初始化逻辑被执行。调用主构造函数时,可以传递相应的参数。
  • 调用其他次构造函数:在次构造函数内部,你也可以使用 this() 调用同一个类中的其他次构造函数。这样可以避免重复编写初始化逻辑。

以下是一个示例,展示了如何在 CustomView 类中使用次构造函数:

class CustomView : View {constructor(context: Context) : this(context, null, 0)constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在这里执行初始化操作}}

在上面的示例中,CustomView 类继承自 Android 中的 View 类,并且有三个构造函数:

  • 第一个构造函数接收一个 context 参数,这是主构造函数。它通过调用次构造函数,为 attrsdefStyleAttr 提供默认值。
  • 第二个构造函数接收 contextattrs 参数,调用了具有更多参数的次构造函数。
  • 第三个构造函数接收 contextattrsdefStyleAttr 参数,它是主要的构造函数。它通过调用父类 View 的构造函数来实现,并在构造函数内部调用了 initialize 函数来执行额外的初始化操作。

(3)大家来找茬

可以尝试将下面示例代码放到编译器中,会发现编译不通过, super 处会提示 “Primary constructor call expected”。问题出在了哪里?

class CustomView(context: Context) : View(context) {constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {initialize(context, attrs, 0)}constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在这里执行初始化操作}}

错误消息 “Primary constructor call expected” 意味着在构造函数的调用位置需要调用主构造函数,但实际上没有调用。

在 Kotlin 中,如果一个类有一个主构造函数,那么所有的次构造函数必须委托给主构造函数。这是语言的规定,确保类的初始化能够始终通过主构造函数完成。

在我们的代码中,主构造函数是 class CustomView(context: Context) : View(context) 这一行。由于它是一个主构造函数,所有的次构造函数必须通过 this() 来调用主构造函数。因此,不能在次构造函数中使用 super 关键字来调用父类的构造函数。

以下是修正后的代码示例,满足了 Kotlin 的构造函数规定:

class CustomView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {constructor(context: Context) : this(context, null, 0) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)init {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在这里执行初始化操作}
}

在这个修正后的代码中,主构造函数是 class CustomView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) ,并且所有的次构造函数都通过 this() 来委托主构造函数。统一在 init 添加初始化操作,以满足我们的需求。

(4)@JvmOverloads 注解

使用了 @JvmOverloads 注解来为 CustomView 类的构造函数生成所有可能的参数组合,以简化构造函数的使用。这样,你可以使用主构造函数,也可以选择性地使用次构造函数。

我们将上述代码再次改造一下:

class CustomView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {init {initialize(context, attrs, defStyleAttr)}private fun initialize(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {// 在这里执行初始化操作}
}

使用这个示例,你可以这样创建一个 CustomView 实例:

val customView1 = CustomView(context)  
val customView2 = CustomView(context, attrs)  
val customView3 = CustomView(context, attrs, defStyleAttr)  

@JvmOverloads 是 Kotlin 中的一个注解,用于在编译器级别生成多个重载版本的函数或构造函数,以便在 Java 代码中更方便地使用。这个注解通常用于改善 Kotlin 类在 Java 代码中的互操作性。

在特定情况下,Kotlin 的函数或构造函数可能具有默认参数,这使得在 Kotlin 中调用它们非常方便,因为可以省略部分参数。但在 Java 中,调用具有默认参数的 Kotlin 函数会变得不那么直观,因为 Java 不直接支持 Kotlin 的默认参数语法。

这时,@JvmOverloads 就派上用场了。当你在 Kotlin 函数或构造函数上使用 @JvmOverloads 注解时,Kotlin 编译器会自动为每个省略参数的版本生成一个重载版本,以便在 Java 代码中使用。这样,Java 开发者就可以根据需要传递不同数量的参数,而不必理解或设置 Kotlin 的默认参数。

具体来说,对于构造函数来说,@JvmOverloads 会在编译时为每个可能的参数组合生成一个单独的构造函数,以确保 Java 开发者能够使用所有参数组合。

总结:@JvmOverloads 是一个用于改善 Kotlin 代码在 Java 中互操作性的注解,它为具有默认参数的函数或构造函数生成重载版本,使得 Java 开发者能够更方便地使用这些函数或构造函数。

(5)init 函数,父类的构造函数与次构造函数之间调用顺序

  • 首先,会执父类的 init 块(如果有的话)
  • 其次,会执行父类的构造函数(主构造函数或者被次构造函数调用的构造函数)。
  • 然后,会执行当前类的 init 块(如果有的话)。
  • 最后,会执行当前类的次构造函数(如果有的话)。

以下是一个示例来说明这个调用顺序:

open class Parent {init {println("Parent's init block")}constructor() {println("Parent's constructor")}
}class Child : Parent {constructor() : super() {println("Child's constructor")}init {println("Child's init block")}
}fun main() {val obj = Child()
}

在这个示例中,当创建 Child 类的实例时,首先会调用 Parent 类的 init 块,其次是 Parent 类的构造函数,然后执行 Child 类的 init 块,最后执行 Child 类的次构造函数。因此,输出将是:

Parent's init block
Parent's constructor
Child's init block
Child's constructor

二、访问修饰符

在 Kotlin 中,访问修饰符用于控制类、接口、函数、属性等成员的可见性。Kotlin 提供了一些访问修饰符,用于指定成员在哪些范围内可见。以下是常见的访问修饰符:

  1. public(默认):没有显式指定访问修饰符时,默认为 public。在这种情况下,成员对所有其他代码都可见。
  2. private:成员只在声明它的文件中可见。私有成员对同一文件中的其他类都不可见。
  3. protected:成员在声明它的类及其子类中可见。但在同一包内的其他类中不可见。
  4. internal:成员对于同一模块内的所有代码都可见。模块是一组一起编译的 Kotlin 文件。它更多的去用于我们现在做一些项目的结构化的扩展的时候去使用这个修饰符。

示例:

class MyClass {private val privateVar = 1protected val protectedVar = 2internal val internalVar = 3val publicVar = 4 // 默认为 public
}fun main() {val obj = MyClass()println(obj.internalVar) // 可以访问,因为处于同一模块内println(obj.publicVar)   // 可以访问,因为是 public// 下面的代码不能编译通过,因为 private 和 protected 变量不在可见范围内// println(obj.privateVar)// println(obj.protectedVar)
}

注意:

  • 对于顶层声明(不在类内部的声明),只有 publicinternal 修饰符有效。
  • internal 修饰符在 Kotlin 中非常有用,它允许模块内的代码访问成员,同时限制了模块之间的访问。
  • privateprotected 对于顶层声明(不在类内部的声明)是无效的,因为它们只对类内部的成员可见性起作用。

综上所述,访问修饰符用于控制代码中成员的可见性范围,确保代码的封装和安全性。

补充一下,什么是模块?

每当创建一个 module,这就是一个模块。如果你的一个变量名通过internal修饰的话,在同一个模块内的时候,是可以互相调用的,但是跨模块的话,你是无法访问到这个变量的。这就是 kotlin 的这个访问修饰符,它更多的去用于我们现在做一些项目的结构化的扩展的时候去使用这个修饰符。

在这里插入图片描述

三、伴生对象

在 Kotlin 中,每个类都可以有一个伴生对象(Companion Object),它是该类的一个特殊对象实例。伴生对象的成员可以像 Java 静态成员一样在类级别上访问,不需要通过类的实例来调用。伴生对象通常用于创建类级别的静态成员、工厂方法、单例模式等。

在 JAVA 中,我们经常看到这样的一段代码,就是工具类,里面定义了一些的静态方法,可以直接用工具类的类名 . 方法名,去调用它。如下所示:

public class StringUtils {public static final String EMPTY = "";public static boolean isEmpty(CharSequence cs) {return cs == null || cs.length() == 0;}public static boolean isNotEmpty(CharSequence cs) {return !StringUtils.isEmpty(cs);}
}

但是在 kotlin 中是没有静态方法的,Kotlin 要怎么改写呢?

方法有两种,第一种办法是我们在《Kotlin 代码与 Java 代码集成》中介绍过,使用 @JvmStatic 注释暴露 Kotlin 类的静态方法。

那么第二种方法就是现在要介绍的伴生对象 Companion Object ,它必须被写在一个类的内部,作为这个类的伴生对象。伴生对象可以包含成员函数、属性和初始化代码块,类似于 Java 中的静态成员。它的一个主要作用是允许在不创建类实例的情况下访问这些成员,就像访问静态成员一样。

那么我们来改写一下:

class StringUtils {companion object {const val EMPTY = ""fun isEmpty(cs: CharSequence?): Boolean {return cs.isNullOrEmpty()}fun isNotEmpty(cs: CharSequence?): Boolean {return !isEmpty(cs)}}
}

在上面的示例中,StringUtils 类中定义了一个伴生对象,其中包含一个常量 EMPTY 和 两个方法isEmptyisNotEmpty。那么就可以在类级别上直接访问伴生对象的成员和方法,就像访问类的静态成员和静态方法一样。

在 Java 代码中调用,如下所示:

 public class JavaExample {public static void main(String[] args) {String str = "";boolean isEmpty = StringUtils.Companion.isEmpty(str);boolean isNotEmpty = StringUtils.Companion.isNotEmpty(str);System.out.println("isEmpty: " + isEmpty);System.out.println("isNotEmpty: " + isNotEmpty);}
}

注意,StringUtils.Companion 中的 Companion 是伴生对象的默认名称,因为我们并未显式地为我们的伴生对象命名,伴生对象的名称可以省略,那么此时使用默认的名称 Companion

实际上伴生对象在编译好以后会在这个类的内部生成一个静态对象。叫companion的对象,那么我们Java在调用的时候,实际上是通过这个类的companion对象去调用companion内部的一些变量。

如果你为伴生对象显式命名,例如 MyCompanion,那么需要使用该名称来访问伴生对象的成员。以下是一个使用了命名伴生对象的示例:

class StringUtils {companion object MyCompanion {const val EMPTY = ""fun isEmpty(cs: CharSequence?): Boolean {return cs.isNullOrEmpty()}fun isNotEmpty(cs: CharSequence?): Boolean {return !isEmpty(cs)}}
}

在 Java 代码中调用,如下所示:

 public class JavaExample {public static void main(String[] args) {String str = "";boolean isEmpty = StringUtils.MyCompanion.isEmpty(str);boolean isNotEmpty = StringUtils.MyCompanion.isNotEmpty(str);System.out.println("isEmpty: " + isEmpty);System.out.println("isNotEmpty: " + isNotEmpty);}
}

四、单例

半生对象在 Kotlin 中还有一个非常有用的特性,就是可以用于声明单例。在前面的文章中,我们已经介绍了通过 object 关键字来创建匿名的内部类,这种方式其实也可以用来实现单例。然而,更加推荐的写法是利用伴生对象来创建单例。

使用伴生对象来实现单例具有许多优点:

  1. 线程安全: 伴生对象的属性和方法在类加载时进行初始化,确保了线程安全。这样即使在多线程环境下也能保证只有一个实例被创建。
  2. 延迟初始化: 伴生对象的属性和方法是在首次访问时才会被初始化,这也被称为"惰性初始化"。这种方式能够节省资源,只有在实际需要时才会创建实例。
  3. 清晰可见: 伴生对象使得单例模式的实现更加清晰和易于理解。它将单例的代码组织在一起,使得代码结构更加整洁。

总的来说,虽然在 Kotlin 中可以使用 object 关键字来创建单例,但更加推荐使用伴生对象来实现。这种方式不仅更加优雅,还能够带来诸多优势,如线程安全和延迟初始化等。如果你正在考虑实现单例模式,不妨考虑使用伴生对象来达到更好的效果。

以下是如何使用伴生对象来实现单例模式的示例:

class Single private constructor() {companion object {fun get(): Single {return Holder.instance}}private object Holder {val instance = Single()}
}
  • Single 类的主构造函数被声明为私有,这意味着外部无法直接通过构造函数来实例化该类。
  • Single 类的伴生对象中,我们定义了一个名为 get 的方法,用于获取 Single 类的单例实例。该方法返回了一个 Holder 内部对象的实例,这是通过 Holder.instance 访问的。
  • 内部的 Holder 对象的实例化是通过 Kotlin 中的对象声明(object)实现的。由于 Kotlin 的对象声明在首次访问时就会被初始化,且只会被初始化一次,这确保了线程安全的单例创建。

上述代码展示了一种在 Kotlin 中使用伴生对象来创建单例的方式,这是一种非常常见的做法。这种方式结合了私有的主构造函数和内部对象,确保了单例的线程安全性和延迟初始化。

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

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

相关文章

笔记本家庭版本win11上win+r,运行cmd默认没有管理员权限,如何调整为有管理员权限的

华为matebookeGo 笔记本之前有段时间不知怎么回事&#xff0c;打开运行框&#xff0c;没有了那一行“使用管理权限创建此任务”&#xff0c;而且cmd也不再是默认的管理员下的&#xff0c;这很不方便,虽然每次winr &#xff0c;输入cmd后可以按ctrlshitenter以管理员权限运行&am…

驱动开发错误汇编

本博文将会不定期更新。以便记录我的驱动开发生涯中的一些点点滴滴的技术细节和琐事。 生成时link找不到导出函数&#xff0c;比如"LNK2019 无法解析的外部符号 _FltCreateCommunicationPort32"。出现这种情况的原因是&#xff0c;驱动的编译环境忽略了所有的默认库&…

windows的redis配置sentinel

1、先安装好redis主从&#xff0c;参考我的文章&#xff0c;链接如下 redis主从&#xff08;windows版本&#xff09;_rediswindows版本_veminhe的博客-CSDN博客 2、然后配置sentinel 参考在windows上搭建redis集群&#xff08;Redis-Sentinel&#xff09; 配置时&#xf…

mac 安装java1.8

1、下载jdk1.8 https://www.oracle.com/java/technologies/downloads/#java8-mac 2、 安装jdk1.8 一路默认&#xff0c;安装后的位置在这儿。 /Library/Java/JavaVirtualMachines/jdk-1.8.jdk 3、配置环境 打开终端&#xff0c;输入命令 sudo vim /etc/profile 添加以下配…

java八股文面试[JVM]——如何打破双亲委派模型

双亲委派模型的第一次“被破坏”是重写自定义加载器的loadClass(),jdk不推荐。一般都只是重写findClass()&#xff0c;这样可以保持双亲委派机制.而loadClass方法加载规则由自己定义&#xff0c;就可以随心所欲的加载类&#xff0c;典型的打破双亲委派模型的框架和中间件有tomc…

数学建模--一维插值法的多种插值方式的Python实现

目录 1.算法流程步骤 2.算法核心代码 3.算法效果展示 1.算法流程步骤 #算法的核心就是利用scipy中的interpolate来完成工作 #一共是5种一维插值算法形式: #插值方法&#xff1a;1.阶梯插值 2.线性插值 3.2阶样条插值 4.3阶样条插值 #"nearest"阶梯插值 #"zero&…

lv3 嵌入式开发-7 linux shell脚本编程(分支语句、循环语句)

目录 1 分支语句 2 多路分支语句 3 for的用法 4 while的用法 5 循环控制语句 6 练习 1 分支语句 语法结构: if 表达式then 命令表fi 如果表达式为真, 则执行命令表中的命令; 否则退出if语句, 即执行fi后面的语句。 if和fi是条件语句的语句括号, 必须成对使用; …

LinkedList(4):多线程LinkedList 不安全情况

多线程不安全演示&#xff0c;线程越多&#xff0c;现象越明显&#xff0c;这边只启了四个线程。 package com.example.demo;import java.util.LinkedList; import java.util.UUID;public class LInkedListThread {public static void main(String[] args) {final LinkedList&…

Notepad++下载安装

自己在 找Notepad发现网上的网址参差不齐&#xff0c;自己找到的一个不错下载链接见文末&#xff01; Notepad 是一个免费的代码编辑器&#xff0c;专为在微软 Windows 环境下使用。它是一个开源项目&#xff0c;采用 GPL 许可证&#xff0c;并使用 C 编程语言结合 Win32 API 和…

ZLMeidaKit在Windows上启动时:计算机中丢失MSVCR110.dll,以及rtmp推流后无法转换为flv视频流解决

场景 ZLMediaKit在Windows上实现Rtmp流媒体服务器以及模拟rtmp推流和http-flv拉流播放&#xff1a; ZLMediaKit在Windows上实现Rtmp流媒体服务器以及模拟rtmp推流和http-flv拉流播放_zlm流媒体服务器_霸道流氓气质的博客-CSDN博客 按照以上教程启动MediaServer.exe时提示&am…

深入浅出AXI协议(4)——猝发传输

一、前言 在之前的文章中&#xff0c;我们着重介绍了关于AXI4的握手协议它可以使得传输的双方都可以自如地控制传输的速率&#xff0c;我们主要介绍了握手协议出现的3种可能情况。然后对于AXI4交易通信的握手信号的关系做出了介绍&#xff1a;&#xff08;1&#xff09;在AXI4互…

【webrtc】接收/发送的rtp包、编解码的VCM包、CopyOnWriteBuffer

收到的rtp包RtpPacketReceived 经过RtpDepacketizer 解析后变为ParsedPayloadRtpPacketReceived 分配内存,执行memcpy拷贝:然后把 RtpPacketReceived 给到OnRtpPacket 传递:uint8_t* media_payload = media_packet.AllocatePayload(rtx_payload.size());RTC

ExpressLRS开源代码之框架结构

ExpressLRS开源代码之框架结构 1. 源由2. Arduino应用框架3. ExpressLRS应用框架4. 硬件设计框架4.1 单天线4.2 双天线单PA4.3 双天线双PA 5. 应用软件设计6. 参考资料 1. 源由 最近为了理解《ExpressLRS开源之基本调试数据含义》&#xff0c;做了一些源代码的研读。 概念、文…

二进制搭建kubernetes(K8S)

二进制搭建kubernetes&#xff08;K8S&#xff09; 一、常见的K8S部署方式1.Minikube2.Kubeadmin3.二进制安装部署 二、二进制搭建K8S(单台master)1.部署架构规划2.系统初始化配置3.部署 docker引擎4.部署 etcd 集群4.部署 Master 组件5.部署 Worker Node 组件6.部署网络组件 三…

数据可视化工具中的显眼包:奥威BI自带方案上阵

根据经验来看&#xff0c;BI数据可视化分析项目是由BI数据可视化工具和数据分析方案两大部分共同组成&#xff0c;且大多数时候方案都需从零开始&#xff0c;反复调整&#xff0c;会耗费大量时间精力成本。而奥威BI数据可视化工具别具匠心&#xff0c;将17年经验凝聚成标准化、…

【juc】ReentrantReadWriteLock之缓存(仅当学习)

目录 一、说明二、代码示例2.1 pom依赖2.2 示例代码2.3 实体类 三、示例截图 一、说明 1.针对于读多写少的情况 2.先查缓存&#xff0c;没有再去查库 二、代码示例 2.1 pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"h…

文心一言api接入如何在你的项目里使用文心一言

文心一言api接入在项目里接入文心一言 一、百度文心一言API二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、百度文心一言API 基于百度文心一言语言大模型的智能文本对话AI机器人…

Linux代码初试__进度条

前言 在我们的日常生活中&#xff0c;进度条是十分常见的&#xff0c;比如在软件下载中&#xff0c;应用加载中等等~~~那么进度条有什么特点&#xff1f;他又如何实现。 下面我们将结合下面的图展开讲解 一、前置理论知识 1.1回车和换行的区别 在我们的日常生活中&#x…

Android Studio新版本New UI及相关设置丨遥遥领先版

1、前言 俗话说工欲善其事必先利其器嘛&#xff0c;工具用不好怎么行呢&#xff0c;借着Android Studio的更新&#xff0c;介绍一下新版本中的更新内容&#xff0c;以及日常开发中那些好用的设置。 2、关于新版本 2.1、最新正式版本 Android Studio Giraffe | 2022.3.1 Pat…

FreeRTOS中断与任务之间同步(Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,422 )

前言&#xff1a; FreeRTOS中&#xff0c;中断需要注意几点&#xff1a; 何时使用中断&#xff1b;中断服务函数&#xff08;ISR&#xff09;要处理的数据量有多大&#xff0c;通常我们希望中断的切换越快越好&#xff0c;也就是说&#xff0c;ISR尽量采用耗时较少的处理方式…