Kotlin 进阶函数式编程技巧

Kotlin 进阶函数式编程技巧

Kotlin 简介

软件开发环境不断变化,要求开发人员不仅适应,更要进化。Kotlin 以其简洁的语法和强大的功能迅速成为许多人进化过程中的信赖伙伴。虽然 Kotlin 的初始吸引力可能是它的简洁语法和与 Java 的互操作性,但 Kotlin 的真正优势在于其更深层次的函数式编程能力。一旦掌握这些技术,就有可能改变我们处理问题、设计解决方案甚至理解代码的方式。

本文深入探讨 Kotlin 中的高级函数式编程,提供见解和现实世界的示例,旨在提高您的编码技能。无论你是在提高自己的技能还是初步接触这个领域,这里都是一个旨在与现代开发人员的挑战和愿景共鸣的指南。

Kotlin 的函数式基础

Kotlin 函数式编程的核心在于不可变性的概念和将函数视为一等公民。

1. 不可变数据结构

基本语法

在 Kotlin 中,“val”关键字表示只读(不可变)变量。虽然变量本身是不可变的,但它所指向的数据不一定是不可变的。这就是为什么 Kotlin 还提供了不可变集合。

val readOnlyList = listOf("a", "b", "c")

真实示例

考虑一个典型的电子商务应用程序。当用户查看他们的个人资料时,他们会看到他们过去的订单列表。为了在显示这些订单时防止意外修改,最好确保订单列表保持不可变。

data class Order(val orderId: Int, val product: String, val price: Double)// 假设我们从数据库或 API 中获取该列表
val userOrders: List<Order> = fetchOrdersFromDatabase()// 如果我们想要打折,我们可以通过创建具有更新价格的新列表来避免修改原始列表。
val discountedOrders = userOrders.map { order ->if (order.price > 100.0) {order.copy(price = order.price * 0.9)  // 10% 折扣} else {order}
}

2. 一等公民函数

基本语法

Kotlin 支持将函数分配给变量、将它们作为参数传递或从其他函数中返回,这意味着它们可以作为一等公民。

fun greet(name: String) = "Hello, $name!"
val greetingFunction: (String) -> String = ::greet
println(greetingFunction("Bob"))  // 输出:Hello, Bob!

真实示例

在图形渲染软件中,可以将各种效果(如模糊、锐化或颜色反转)应用于图像。通过将函数视为一等公民,可以将这些效果表示为函数并以各种方式组合。

fun blur(image: Image): Image = ...
fun sharpen(image: Image): Image = ...
fun invertColors(image: Image): Image = ...val effects = listOf(::blur, ::sharpen, ::invertColors)// 顺序地在图像上应用所有效果
val processedImage = effects.fold(originalImage) { img, effect -> effect(img) }

高级集合函数

Kotlin 提供了丰富的集合操作函数。除了基础知识,理解这些函数的复杂性可以极大提高代码的清晰度和效率。

1. 使用 map 和 flatMap 进行转换

基本语法

“map” 函数使用提供的转换函数转换集合中的每个元素。“flatMap” 可以转换和扁平化集合。

val numbers = listOf(1, 2, 3)
val squared = numbers.map { it * it }  // [1, 4, 9]

真实示例

假设您有一个字符串列表,表示潜在 URL,并想要提取域名。不是每个字符串都是有效的 URL,因此这就是 “flatMap” 起作用的地方。

val potentialUrls = listOf("https://example.com/page", "invalid-url", "https://another-example.com/resource")val domains = potentialUrls.flatMap { url ->runCatching { URL(url).host }.getOrNull()?.let { listOf(it) } ?: emptyList()
}
// Result: ["example.com", "another-example.com"]

2. 使用 filter 和 filterNot 进行过滤

基本语法

“filter” 返回满足给定谓词的元素列表。“filterNot” 则相反。

val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filterNot { it % 2 == 0 }  // [1, 3, 5]

真实示例

想象一下,基于多个动态条件(如价格范围、评分和可用性)而不仅仅是一个条件来筛选产品。

data class Product(val id: Int, val price: Double, val rating: Int, val isAvailable: Boolean)val products = fetchProducts()  // 假设这会获取产品列表val filteredProducts = products.filter { product ->product.price in 10.0..50.0 && product.rating >= 4 && product.isAvailable
}

Sure! Here is the content organized using Markdown:

使用 fold 和 reduce 进行累积操作

fold 和 reduce 的概述

foldreduce 都用于累积操作,但它们在使用场景和语法上有一些不同。

fold
用途:对集合的元素执行操作,需要一个初始的累加器值和一个组合操作。可以处理任何类型的集合。
基本语法

val numbers = listOf(1, 2, 3, 4)
val sumStartingFrom10 = numbers.fold(10) { acc, number -> acc + number }  // 结果: 20

例子:例如,将字符串连接起来

val words = listOf("apple", "banana", "cherry")
val concatenated = words.fold("Fruits:") { acc, word -> "$acc $word" }
// 结果: "Fruits: apple banana cherry"

reduce
用途:与 fold 类似,但不需要一个初始的累加器值。它使用集合的第一个元素作为初始的累加器。

基本语法

val numbers = listOf(1, 2, 3, 4)
val product = numbers.reduce { acc, number -> acc * number }  // 结果: 24

例子:结合自定义数据结构。假设我们有一个范围的列表,我们想要合并范围:

val ranges = listOf(1..5, 3..8, 6..10)
val combinedRange = ranges.reduce { acc, range -> acc.union(range) }
// 结果: 1..10

关键区别

  • 初始值:
    • fold 需要一个显式的初始累加器值。
    • reduce 使用集合的第一个元素作为初始值。
  • 适用性:
    • fold 可以处理任何大小的集合,包括空集合(因为有初始累加器值)。
    • reduce 在空集合上会抛出异常,因为没有初始值来开始操作。
  • 灵活性:
    • fold 更灵活,允许定义与集合元素类型不同的初始值。
    • reduce 有类型约束,要求累加器和集合的元素必须是相同的类型。

使用 groupBy 和 associateBy 进行分区

groupBy 和 associateBy 的概述

groupBy 根据键选择器函数的结果返回一个将元素分组的 Map。associateBy 根据提供的键选择器将每个元素作为键返回一个 Map。

基本语法

val words = listOf("apple", "banana", "cherry")
val byLength = words.groupBy { it.length }  // {5=[apple], 6=[banana, cherry]}

示例

假设我们有一个学生对象的列表,我们想要根据学生的 ID 对其进行分组。

data class Student(val id: String, val name: String, val course: String)val students = fetchStudents()// 假设 students 包含:
// Student("101", "Alice", "Math"), Student("101", "Eve", "History"), Student("102", "Bob", "Science")val studentsById = students.associateBy { it.id }
// 结果的 Map 将是:
// {"101"=Student("101", "Eve", "History"), "102"=Student("102", "Bob", "Science")}

在上面的例子中,Eve 覆盖了 Alice,因为它们都有相同的 ID “101”。结果的 Map 只保留了具有该 ID 的最后一个学生的详细信息。

关键区别

  • groupBy 创建一个 Map,其中每个键指向原始集合中的项目列表。
  • associateBy 创建一个 Map,其中每个键指向原始集合中的单个项目。如果存在重复项,最后一个元素将覆盖其他元素。

在选择使用 groupBy 还是 associateBy 时,主要考虑是否需要保留具有相同键的所有元素(使用 groupBy),还是只保留最后一个元素(使用 associateBy)。

在 Kotlin 中的函数组合

想象一下你有一个玩具工厂的装配线,在这条线上的每个工位上,玩具都要经历特定的变化。玩具从一个工位移动到下一个工位,每个步骤都会进行修改。

在编程中,尤其是在 Kotlin 中,当你将两个函数链接在一起,使得第一个函数的结果成为下一个函数的输入时,就像玩具从一个工位流畅地移动到终点一样。

想象一下我们的玩具工厂有三个工位:

  • A工位:给玩具上色。
  • B工位:将轮子安装到已上色的玩具上。
  • C工位:在已装有轮子的玩具上贴上贴纸。

这些工位就像函数一样,每个函数按照顺序执行自己的任务。

在 Kotlin 中,让我们将这些工位表示为函数:

fun paint(toy: Toy): Toy { /*上色并返回玩具*/ }
fun attachWheels(toy: Toy): Toy { /*安装轮子并返回玩具*/ }
fun placeSticker(toy: Toy): Toy { /*贴上贴纸并返回玩具*/ }

我们不想手动地将玩具从一个工位移动到下一个工位,我们希望有一个自动化的过程,使得玩具可以顺利地从开始到结束。这就是函数组合发挥作用的地方。

为了使其在 Kotlin 中生效,我们将定义一个 compose 函数:

infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C {return { x -> this(g(x)) }
}

这个 compose 函数是我们链接两个工位(函数)的工具。它确保一个工位的输出成为下一个工位的输入。

现在,使用 compose 函数,我们可以定义我们的自动化玩具装配线:

val completeToyProcess = ::placeSticker compose ::attachWheels compose ::paint

当你将原始玩具放入 completeToyProcess 中时,它会自动被上色、安装轮子,然后贴上贴纸。

实际示例

val rawToy = Toy()
val finishedToy = completeToyProcess(rawToy)

在这个例子中,原始玩具经过整个过程,变成了完成的玩具——上色、安装轮子和贴上贴纸,全部在一个流畅的操作中完成。

为什么这很有用?

清晰明了:就像我们的玩具工厂类比一样,您可以一次性看到整个装配线过程。您可以快速了解玩具经历的变化顺序。
灵活性:如果您需要不同的结果,您可以轻松地更改顺序或添加/删除工位(或函数)。
效率:您无需在每次修改后存储玩具;它只需通过装配线不断移动。
需要注意的事项

顺序很重要:就像不能在玩具上涂贴标签之前连续涂色一样,链接函数的顺序至关重要。
保持简单:如果您的装配线(或函数链)太长,就会变得难以理解或管理。这就像我们的玩具工厂中有太多工位一样。因此,平衡是关键!

科里化 - 增量决策的力量

想象一下您正在一家多功能咖啡店。他们不提供现成的饮料,而是给您一系列的选择。首先,您选择咖啡豆的类型,然后决定使用牛奶(或替代品),最后选择任何额外的口味或配料。

现在,假设您是一位常客,总是选择阿拉比卡咖啡豆,但会根据心情变化其他选择。咖啡店不会让您每次都从头开始选择,而是记住您的豆子偏好。这种方法节省时间,减少决策疲劳,并让您可以专注于当下最重要的事情。

这就像科里化在编程中所实现的功能。

分解问题

简化复杂的决策:就像选择一杯咖啡需要几个步骤一样,一些函数有很多参数。科里化将这些多参数函数简化为一系列更简单的函数链。该链中的每个函数都接受一个参数并返回下一个要使用另一个参数调用的函数。
记住偏好:通过科里化函数,您可以“记住”特定的决策(或函数参数)。在我们的咖啡示例中,您对阿拉比卡咖啡豆的喜好被记住了,让您可以进行其他选择。
专注于重要事项:有时,您并没有所有的信息。科里化允许您在信息可用时进行决策。就像当您来到柜台时,即使几天前选择了咖啡豆类型,您也可以稍后再决定使用牛奶和口味。

代码示例

假设有一个订购咖啡的函数。
当您使用科里化时,可以在函数调用过程中使用标记来指定特定的参数。这样做可以提供更灵活和可读性更高的代码。

在函数签名中使用标记:

fun orderCoffee(bean: String): (milk: String) -> (flavor: String) -> Coffee { ... }

在这个示例中,我们在函数签名中为 milkflavor 参数添加了标记。这使得在函数调用时可以明确地指定每个参数的值。

使用标记进行函数调用:

val arabicaOrder = orderCoffee("Arabica")
val myCoffee = arabicaOrder(milk = "Almond Milk")(flavor = "Vanilla")

通过在函数调用中使用标记,我们可以清楚地表达每个参数的含义,并且不需要按照特定顺序传递参数。这提高了代码的可读性,并且对于具有多个可选参数的函数尤其有用。

对于具有多个标记参数的函数,您可以根据自己的需求选择要使用的参数,并省略其他参数。例如:

val arabicaWithFlavor = arabicaOrder(flavor = "Caramel")
val myCoffee = arabicaWithFlavor(milk = "Whole Milk")

在这个示例中,我们只指定了 milkflavor 参数,并忽略了 bean 参数。这样,我们可以通过只提供所需的标记参数来创建定制的函数,而无需重复指定其他参数。

使用标记进行函数调用可以提高代码的可读性和灵活性,并使函数调用更具表达力和可维护性。标记参数允许您以更直观的方式指定参数,并且不需要依赖于参数的顺序。

单子——编程的安全网

想象一下组装一个DIY家具套件。说明书中的每个步骤都依赖于前一个步骤。然而,并不是所有的步骤都那么简单,有时你可能会发现缺少一个部件或者意识到你在之前的步骤中犯了个错误。

如果说明书带有内置的安全网岂不是太好了?例如,如果你准备在错误的地方固定螺丝钉,说明书会立即提醒你。或者,如果有一块零件缺失,它会提供一个权宜之计或告诉你如何在没有它的情况下继续进行。

这种“安全网”概念在DIY世界中就是单子给编程带来的。

理解单子

  • 依赖步骤——就像家具组装涉及一系列依赖步骤一样,编程中的操作通常是一个链条,其中每个链接都依赖前面的成功。
  • 安全机制——单子充当了一个安全机制,确保如果一个步骤失败或没有产生有效值,后续的步骤能够意识到并做出相应反应。
  • 封装挑战——单子将值与产生这些值的上下文捆绑在一起,无论是通过成功、错误还是某些副作用产生的。

实际应用

Kotlin的Optional是一种单子形式。想象一下从数据库查询用户资料的情况 -

fun findUserProfile(id: Int): Optional<UserProfile> {// 一些获取资料的逻辑
}

假设我们想获取用户的电子邮件 -

val emailOpt = findUserProfile(123).flatMap { profile -> profile.email }

如果findUserProfile找不到资料,它可能会返回一个空的Optional。flatMap操作不会崩溃或抛出错误;它只会产生另一个空的Optional。

这就像我们的DIY说明书的安全网。如果一个步骤不能完成,它不会停止整个过程,而是给你一个安全继续进行的方式。

单子引起注意

  • 优雅的失败:单子允许函数以优雅的方式失败。它们确保进程继续前行,即使是为了传达一个错误。
  • 直观的流程:有了单子,代码的流程变得更直观,更能反映现实生活中的决策过程。
  • 增强的可组合性:由于它们可链式使用的特性,单子导致更模块化和适应性更强的代码。

惰性求值和序列——提供高效的操作

曾经去过自助餐厅,决定只拿你确定会吃的菜,而不是一次性把盘子填满,可能浪费食物吗?这种策略让你在需要时消耗所需,确保最大限度地享受,最少的浪费。

编程中的惰性求值采用了类似的策略。它不是预先计算所有东西,而是在需要时计算所需的内容。在Kotlin中,序列是实现这一目标的主要方式。让我们深入了解!

理解惰性求值

  • 惰性求值是一种计算策略,其中表达式仅在实际需要其结果时进行评估。这可以提高内存使用效率和执行速度,尤其是在处理大型集合时。

Kotlin Sequences

在 Kotlin 中,sequences(Sequence)表示一种惰性计算的集合。与列表不同,sequences 不保存数据;相反,它们描述在请求时生成数据元素的计算过程。

实际应用——Sequences vs. Lists

考虑一个数字列表,我们想要在平方后找到第一个可被 5 整除的数。

使用列表:

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val result = numbers.map { it * it } // 平方所有数字.filter { it % 5 == 0 } // 过滤所有可被 5 整除的平方数.first() // 获取第一个项目
println(result)  // 25

在这种方法中,我们对所有数字进行平方和过滤,只使用一个值。那太低效了!

使用 sequence:

val numbersSeq = numbers.asSequence()val resultSeq = numbersSeq.map { it * it }.filter { it % 5 == 0 }.first()println(resultSeq)  // 25

使用 sequences,每个数字都会被平方,检查是否可被 5 整除,然后当找到第一个这样的数字时,该过程停止。因此,在这种情况下,sequence 只会平方和过滤,直到它找到数字 5。那是高效的!

通过 Sequences 的惰性评估带来的好处:

  • 效率——只计算必要的部分。
  • 灵活性——可以表示无限数据结构。
  • 内存节省——在处理大型数据集时尤为重要。

在 Kotlin 中采用 sequences 和惰性评估,就像采用“边走边消费”的方法一样。它使开发人员能够编写高效和可扩展的代码,特别是在涉及大量数据操作的场景下。

尾递归——利用 Kotlin 编写高效的递归

这样想——你站在高楼的底层,向上凝视着永无止境的楼梯。如果你一个接一个地爬每个台阶,你可能很快就会累垮,或者感到不知所措。但是,如果你可以一次跳过多个楼层,并使用爬一个台阶所需的相同的能量呢?这就是 Kotlin 中尾递归的魔力!

解析递归

递归是一种编程技术,其中函数调用自身以将复杂问题分解为简单的子问题。但是,标准递归可以很快占用大量内存,特别是对于大型输入。每个函数调用都会添加到调用堆栈中,而对于深度递归,这可能会导致堆栈溢出错误。

引入尾递归

尾递归是递归的一种特殊形式,其中递归调用是函数中执行的最后一件事。Kotlin 的编译器优化尾递归函数以使用恒定的堆栈空间,防止堆栈溢出错误。

简单示例——阶乘

没有尾递归:

fun factorial(n: Int): Int {if (n == 1) return 1return n * factorial(n - 1)
}

使用尾递归:

fun factorial(n: Int, accumulator: Int = 1): Int {if (n == 1) return accumulatorreturn factorial(n - 1, n * accumulator)
}

在尾递归版本中,递归调用的结果(与当前操作相结合)作为累加器传递。它确保在递归调用后没有额外的操作待处理,使其成为有效的尾调用。

为什么使用尾递归?

  • 效率——它使用恒定的堆栈空间,防止堆栈溢出。
  • 清晰度——对于某些问题,递归解决方案可能更直观。
  • Kotlin 的支持——只需添加 tailrec 修饰符,Kotlin 即可处理优化!

重要说明

必须确保递归真正处于尾部位置。如果递归调用后有任何操作待处理,该函数将不是尾递归,并且 Kotlin 的编译器将无法优化它。

尾递归背后发生的事情是什么?

在传统递归中,每个函数调用都会被堆叠,等待下一个函数完成其自身计算之前。随着函数调用的堆叠,内存使用量将增加,特别是对于大型输入数字。

在我们的尾递归版本中,发生了以下情况:

  • 每个递归调用都被优化以重用当前函数的堆栈帧,因为在递归调用后没有剩余的计算(例如阶乘中的乘法)。
  • 累加器充当运行总数,保存中间结果。这意味着,到达基本情况(n == 1)时,我们已经在累加器中得到了答案,无需“往回走”。
  • Kotlin 编译器看到 tailrec 修饰符,并识别出函数是尾递归的。然后,它在幕后优化字节码,以确保函数使用恒定的堆栈内存,无论输入大小如何。

实质上,我们的阶乘函数,当调用 factorial(5) 时,从计算:

5 * 4 * 3 * 2 * 1

转换为:

(((5 * 1) * 4) * 3) * 2

这种转换确保在到达基本情况时即可得到答案,同时使用恒定的堆栈空间。

另一个说明

尽管尾递归优化是 Kotlin 中的一个强大功能,但值得注意的是,这个概念并不是该语言所专有的。许多其他编程语言,包括函数式语言(如 Haskell)和更通用的语言(如 Scala),都支持尾递归。但是,它们实现和优化的方式可能不同。在过渡各种语言或与来自不同背景的开发人员讨论该主题时,请始终考虑这一点。

结论

在我们探讨 Kotlin 中的高级函数式编程时,我们已经看到了 Kotlin 提供的深度和多功能性。从集合函数的复杂性、函数组合的优雅性到尾递归的效率,Kotlin 为开发人员提供了强大的工具。虽然这些概念在 Kotlin 中得到了强调,但它们是更广泛的函数式编程世界中的支柱。通过掌握它们,您不仅可以优化 Kotlin 技能,而且还可以使用永恒的编程原则。在您继续前进时,请让这些工具和技巧指导您的 Kotlin 之旅,以生成更有效、更干净、更易于维护的代码。

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

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

相关文章

教你烧录Jetson Orin Nano的ubuntu20.04镜像

Jetson Orin Nano烧录镜像 视频讲解 教你烧录Jetson Orin Nano的ubuntu20.04镜像 1. 下载sdk manager https://developer.nvidia.com/sdk-manager sudo dpkg -i xxxx.deb2. 进入recovery 插上typeC后&#xff0c;短接J14的FORCE_RECOVERY和GND&#xff0c;上电 如下图&#…

基于GB28181-2022实现web无插件播放H265视频

目前发布的GB28181-2022增加了对前端设备视频H265编码格式的支持&#xff0c;所以实现国标平台通过浏览器对H265视频流的无插件的解码播放将是未来的趋势。 目前大多的方案都是通过平台端把H265转码为H264&#xff0c;再推送到web前端进行解码播放&#xff0c;这种方式因为需要…

专访HuggingFace CTO:开源崛起、创业故事和AI民主化丨智源独家

导读 HuggingFace CTO Julien Chaumond认为&#xff0c;在大模型时代&#xff0c;AI民主化至关重要。随着大语言模型和复杂人工智能系统的崛起&#xff0c;持续提升AI技术的可及性有助于确保这些技术的获取和控制不集中在少数强大实体手中。技术民主化促进了机会均等&#xff0…

Java锁常见面试题

图片引用自&#xff1a;不可不说的Java“锁”事 - 美团技术团队 1 java内存模型 java内存模型(JMM)是线程间通信的控制机制。JMM定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存中&#xff0c;每个线程都有一个私有的本地内存&#xff0c;本地内存中存储了该…

c语言从入门到实战——函数递归

函数递归 前言1. 递归是什么&#xff1f;2. 递归的限制条件3. 递归举例3.1 举例1&#xff1a;求n的阶乘3.1.1 分析和代码实现3.1.2 画图推演 3.2 举例2&#xff1a;3.2.1 分析和代码实现3.2.2 画图推演 4. 递归与迭代 前言 函数递归是指一个函数直接或间接地调用自身&#xff…

使用javafx,结合讯飞ai,搞了个ai聊天系统

第一步&#xff1a;先在讯飞ai那边获取接入的api 点进去&#xff0c;然后出现这个页面&#xff1a; 没有的话&#xff0c;就点击免费试用&#xff0c;有了的话&#xff0c;就点击服务管理&#xff1a; 用v2.0的和用3的都行&#xff0c;不过我推荐用2.0版本 文档位置&#xff1…

【每日一题】数组中两个数的最大异或值

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希集合 其他语言python3 写在最后 Tag 【哈希集合】【位运算-异或和】【数组】【2023-11-04】 题目来源 421. 数组中两个数的最大异或值 题目解读 找出数组中两个数的最大异或结果。 解题思路 一看数据量达到了 …

Flink日志采集-ELK可视化实现

一、各组件版本 组件版本Flink1.16.1kafka2.0.0Logstash6.5.4Elasticseach6.3.1Kibana6.3.1 针对按照⽇志⽂件⼤⼩滚动⽣成⽂件的⽅式&#xff0c;可能因为某个错误的问题&#xff0c;需要看好多个⽇志⽂件&#xff0c;还有Flink on Yarn模式提交Flink任务&#xff0c;在任务执…

vSLAM中IMU预积分的作用--以惯性导航的角度分析

作为一个学过一点惯导的工程师&#xff0c;在初次接触视觉slam方向时&#xff0c;最感兴趣的就是IMU预积分了。但为什么要用这个预积分&#xff0c;在看了很多材料和书后&#xff0c;还是感觉模模糊糊&#xff0c;云里雾里。 在接触了vSLAM的更多内容后&#xff0c;站在历史研究…

如何避免 JavaScript 中的内存泄漏?

一、什么是内存泄漏&#xff1f; JavaScript 就是所谓的垃圾回收语言之一&#xff0c;垃圾回收语言通过定期检查哪些先前分配的内存仍然可以从应用程序的其他部分“访问”来帮助开发人员管理内存。垃圾回收语言中泄漏的主要原因是不需要的引用。如果你的 JavaScript 应用程序经…

java中:cmd界面输入javac后提示:找不到或无法加载主类,怎么解决

找不到或无法加载主类 检查环境变量cmd下用 java命令运行文件,提示找不到主类待续、更新中 检查环境变量 CLASSPATH 少写.;安装jdk过程有两部,一步为安装jdk文件夹,全部一致; 另一步为安装jre文件夹与jdk文件夹不一致(或者文件夹安装位置, 一路全部默认)path中将java变量移到顶…

js调整table表格上下相邻元素顺序

有时候我们会遇到要通过箭头控制table表格上下顺序的需求,如下: 点击向下就将该元素下移一位,下面的一位元素就移上来,点击向上就将该元素上移一位,上面的一位元素就移下来,也就是相邻元素互换位置顺序: <el-table :data="targetTable" border style=&quo…

HTTP 协议请求头 If-Match、If-None-Match 和 ETag

概述 在 HTTP 协议中&#xff0c;请求头 If-Match、If-None-Match、If-Modified-Since、If-Unmodified-Since、If-Range 主要是为了解决浏览器缓存数据而定义的请求头标准&#xff0c;按照协议规范正确的判断和使用这几个请求头&#xff0c;可以更精准的处理浏览器缓存&#x…

Springboot3整合Mybatis-plus3.5.3报错

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 报错以及Bug ✨特色专栏&#xff1a; …

研发效能DevOps: Git安装

目录 一、理论 1.Git 2.Git 工具 二、实验 1.Git安装 2.配置Git 3. VS Code加载Git 一、理论 1.Git &#xff08;1&#xff09;简介 Git 是一个分布式版本控制及源代码管理工具;Git 可以为你的项目保存若干快照&#xff0c;以此来对整个项目进行版本管理。 Git 是一个…

ASTM F963-23美国玩具安全新标准发布

新标准发布 2023年10月13日&#xff0c;美国材料与试验协会&#xff08;ASTM&#xff09;发布了新版玩具安全标准ASTM F963-23。 主要更新内容 与ASTM F963-17相比&#xff0c;此次更新包括&#xff1a;单独描述了基材重金属元素的豁免情况&#xff0c;更新了邻苯二甲酸酯的管控…

Java与Redis的集成以及Redis中的项目应用

一、Java连接Redis Redis与MySQL都是数据库&#xff0c;java操作redis其实跟操作mysql的过程是一样的。 1.1 导入依赖 打开IDEA&#xff0c;进入Java项目&#xff0c;导入pom依赖&#xff0c;代码如下&#xff1a; <dependency><groupId>redis.clients</gro…

MySQL笔记--Ubuntu安装MySQL并基于C++测试API

目录 1--安装MySQL 2--MySQL连接 3--代码案例 1--安装MySQL # 安装MySQL-Server sudo apt install mysql-server# 设置系统启动时自动开启 sudo systemctl start mysql # sudo systemctl enable mysql# 检查MySQL运行状态 sudo systemctl status mysql# 进入MySQL终端 sudo…

视频剪辑技巧:批量合并视频,高效省时,添加背景音乐提升品质

随着社交媒体的兴起&#xff0c;视频制作越来越受到人们的关注。掌握一些视频剪辑技巧&#xff0c;可以让我们轻松地制作出令人惊艳的视频。本文将介绍一种高效、省时的视频剪辑技巧&#xff0c;帮助您批量合并视频、添加背景音乐&#xff0c;并提升视频品质。现在一起来看看云…

hadoop配置文件自检查(解决常见报错问题,超级详细!)

本篇文章主要的内容就是检查配置文件&#xff0c;还有一些常见的报错问题解决方法&#xff0c;希望能够帮助到大家。 一、以下是大家可能会遇到的常见问题&#xff1a; 1.是否遗漏了前置准备的相关操作配置&#xff1f; 2.是否遗的将文件夹(Hadoop安装文件夹&#xff0c;/dat…