kotlin 知识点 七 泛型的高级特性
对泛型进行实化
泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的,因为Java 中完全没有这个概
念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java 的泛型擦除机制才行。
在JDK 1.5之前,Java 是没有泛型功能的,那个时候诸如List之类的数据结构可以存储任意类型
的数据,取出数据的时候也需要手动向下转型才行,这不仅麻烦,而且很危险。比如说我们在
同一个List中存储了字符串和整型这两种数据,但是在取出数据的时候却无法区分具体的数据类
型,如果手动将它们强制转成同一种类型,那么就会抛出类型转换异常。
于是在JDK 1.5中,Java 终于引入了泛型功能。这不仅让诸如List之类的数据结构变得简单好
用,也让我们的代码变得更加安全。
但是实际上,Java 的泛型功能是通过类型擦除机制来实现的。什么意思呢?就是说泛型对于类
型的约束只在编译时期存在,运行的时候仍然会按照JDK 1.5之前的机制来运行,JVM是识别不
出来我们在代码中指定的泛型类型的。例如,假设我们创建了一个List集合,虽然
在编译时期只能向集合中添加字符串类型的元素,但是在运行时期JVM并不能知道它本来只打算
包含哪种类型的元素,只能识别出来它是个List。
所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,其中当然也包括了
Kotlin 。这种机制使得我们不可能使用a is T或者T::class.java这样的语法,因为T的实际
类型在运行的时候已经被擦除了。
然而不同的是,Kotlin 提供了一个内联函数的概念,我们在第6章的Kotlin 课堂中已经学过了这
个知识点。内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样的话也就不存
在什么泛型擦除的问题了,因为代码在编译之后会直接使用实际的类型来替代内联函数中的泛
型声明,其工作原理如图

可以看到,bar()是一个带有泛型类型的内联函数,foo()函数调用了bar()函数,在代码编
译之后,bar()函数中的代码将可以获得泛型的实际类型。
这就意味着,Kotlin 中是可以将内联函数中的泛型进行实化的。
那么具体该怎么写才能将泛型实化呢?首先,该函数必须是内联函数才行,也就是要用inline
关键字来修饰该函数。其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行
实化。示例代码如下:
inline fun <reified T> getGenericType() {
}
上述函数中的泛型T就是一个被实化的泛型,因为它满足了内联函数和reified关键字这两个前
提条件。那么借助泛型实化,到底可以实现什么样的效果呢?从函数名就可以看出来了,这里
我们准备实现一个获取泛型实际类型的功能,代码如下所示:
inline fun <reified T> getGenericType() = T::class.java
虽然只有一行代码,但是这里却实现了一个Java 中完全不可能实现的功能:
getGenericType()函数直接返回了当前指定泛型的实际类型。T.class这样的语法在Java
中是不合法的,而在Kotlin 中,借助泛型实化功能就可以使用T::class.java这样的语法了。
现在我们可以使用如下代码对getGenericType()函数进行测试:
fun main() { val result1 = getGenericType<String>() val result2 = getGenericType<Int>() println("result1 is $result1") println("result2 is $result2")
}
这里给getGenericType()函数指定了两种不同的泛型,由于getGenericType()函数会将
指定泛型的具体类型返回,因此这里我们将返回的结果进行打印。
现在运行一下main()函数,结果如图

泛型实化功能的运行结果
可以看到,如果将泛型指定成了String,那么就可以得到java.lang.String的类型;如果
将泛型指定了Int,就可以得到java.lang.Integer的类型。
关于泛型实化的基本用法就介绍到这里,接下来我们看一看,泛型实化在Andr oid 项目当中具体
可以有哪些应用。
泛型实化的应用
泛型实化功能允许我们在泛型函数当中获得泛型的实际类型,这也就使得类似于a is T、
T::class.java这样的语法成为了可能。而灵活运用这一特性将可以实现一些不可思议的语法
结构,下面我们赶快来看一下吧。
到目前为止,我们已经将Andr oid 的四大组件全部学完了,除了ContentP rovider 之外,你会
发现其余的3个组件有一个共同的特点,它们都是要结合Intent 一起使用的。比如说启动一个
Activity 就可以这么写:
val intent = Intent(context, TestActivity::class.java)
context.startActivity(intent)
有没有觉得TestActivity::class.java这样的语法很难受呢?当然,如果在没有更好选择
的情况下,这种写法也是可以忍受的,但是Kotlin 的泛型实化功能使得我们拥有了更好的选择。
新建一个reified.kt 文件,然后在里面编写如下代码:
inline fun <reified T> startActivity(context: Context) { val intent = Intent(context, T::class.java) context.startActivity(intent)
}
这里我们定义了一个startActivity()函数,该函数接收一个Context参数,并同时使用
inline和reified关键字让泛型T成为了一个被实化的泛型。
接下来就是神奇的地方了,Intent 接收的第二个参数本来应该是一个具体Activity 的Class类型,但由于现在T已经是一个
被实化的泛型了,因此这里我们可以直接传入T::class.java。最后调用Context的
startActivity()方法来完成Activity 的启动。
现在,如果我们想要启动TestA ctivity ,只需要这样写就可以了:
startActivity<TestActivity>(context)
Kotlin 将能够识别出指定泛型的实际类型,并启动相应的Activity 。怎么样,是不是觉得代码瞬
间精简了好多?这就是泛型实化所带来的神奇功能。
不过,现在的startActivity()函数其实还是有问题的,因为通常在启用Activity 的时候还可
能会使用Intent 附带一些参数,比如下面的写法:
val intent = Intent(context, TestActivity::class.java)
intent.putExtra("param1", "data")
intent.putExtra("param2", 123)
context.startActivity(intent)
而经过刚才的封装之后,我们就无法进行传参了。
这个问题也不难解决,只需要借助高阶函数就可以轻松搞定。回到
reified.kt 文件当中,这里添加一个新的startActivity()函数重载,如下所示:
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {val intent = Intent(context, T::class.java) intent.block() context.startActivity(intent)
}
可以看到,这次的startActivity()函数中增加了一个函数类型参数,并且它的函数类型是
定义在Intent 类当中的。在创建完Intent 的实例之后,随即调用该函数类型参数,并把Intent 的
实例传入,这样调用startActivity()函数的时候就可以在Lambda 表达式中为Intent 传递
参数了,如下所示:
startActivity<TestActivity>(context) { putExtra("param1", "data") putExtra("param2", 123)
}
不得不说,这种启动Activity 的代码写起来实在是太舒服了,泛型实化和高阶函数使这种语法结
构成为了可能,感谢Kotlin 提供了如此多优秀的语言特性。
泛型的协变
泛型的协变和逆变功能不太常用,而且我个人认为有点不容易理解。但是Kotlin 的内置API中使
用了很多协变和逆变的特性,因此如果想要对这个语言有更加深刻的了解,这部分内容还是有
必要学习一下的。
我在学习协变和逆变的时候查阅了很多资料,这些资料大多十分晦涩难懂,因此也让我对这两
个知识点产生了一些畏惧。但是真正掌握之后,发现其实也并不是那么难,所以这里我会尽量
使用最简明的方式来讲解这两个知识点,希望你可以轻松掌握。
在开始学习协变和逆变之前,我们还得先了解一个约定。一个泛型类或者泛型接口中的方法,
它的参数列表是接收数据的地方,因此可以称它为in位置,而它的返回值是输出数据的地方,因
此可以称它为out 位置,如图

有了这个约定前提,我们就可以继续学习了。首先定义如下3个类:
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)
这里先定义了一个Person类,类中包含name和age这两个字段。然后又定义了Student和
Teacher这两个类,让它们成为Person类的子类。
现在我来问你一个问题:如果某个方法接收一个Person类型的参数,而我们传入一个Student
的实例,这样合不合法呢?很显然,因为Student是Person的子类,学生也是人呀,因此这是
一定合法的。
那么我再来升级一下这个问题:如果某个方法接收一个List类型的参数,而我们传
入一个List的实例,这样合不合法呢?看上去好像也挺正确的,但是Java 中是不
允许这么做的,因为List不能成为List的子类,否则将可能存在类型
转换的安全隐患。
为什么会存在类型转换的安全隐患呢?下面我们通过一个具体的例子进行说明。这里自定义一
个SimpleData类,代码如下所示:
class SimpleData<T> { private var data: T? = null fun set(t: T?) { data = t } fun get(): T? { return data }
}
SimpleData是一个泛型类,它的内部封装了一个泛型data字段,调用set()方法可以给data
字段赋值,调用get()方法可以获取data字段的值。
接着我们假设,如果编程语言允许向某个接收SimpleData参数的方法传入
SimpleData的实例,那么如下代码就会是合法的:
fun main() { val student = Student("Tom", 19) val data = SimpleData<Student>() data.set(student) handleSimpleData(data) // 实际上这行代码会报错,这里假设它能编译通过val studentData = data.get()
}
fun handleSimpleData(data: SimpleData<Person>) { val teacher = Teacher("Jack", 35) data.set(teacher)
}
发现这段代码有什么问题吗?在main()方法中,我们创建了一个Student的实例,并将它封装
到SimpleData当中,然后将SimpleData作为参数传递给
handleSimpleData()方法。但是handleSimpleData()方法接收的是一个
SimpleData参数(这里假设可以编译通过),那么在handleSimpleData()方法
中,我们就可以创建一个Teacher的实例,并用它来替换SimpleData参数中的原
有数据。这种操作肯定是合法的,因为Teacher也是Person的子类,所以可以很安全地将
Teacher的实例设置进去。
但是问题马上来了,回到main()方法当中,我们调用SimpleData的get()方法
来获取它内部封装的Student数据,可现在SimpleData中实际包含的却是一个
Teacher的实例,那么此时必然会产生类型转换异常。
所以,为了杜绝这种安全隐患,Java 是不允许使用这种方式来传递参数的。换句话说,即使
Student是Person的子类,SimpleData并不是SimpleData的子
类。
不过,回顾一下刚才的代码,你会发现问题发生的主要原因是我们在handleSimpleData()方
法中向SimpleData里设置了一个Teacher的实例。如果SimpleData在泛型T上是
只读的话,肯定就没有类型转换的安全隐患了,那么这个时候SimpleData可不可
以成为SimpleData的子类呢?
讲到这里,我们终于要引出泛型协变的定义了。假如定义了一个MyClass的泛型类,其中A
是B的子类型,同时MyClass< A > 又是 MyClass< B > 的子类型,那么我们就可以称 MyClass 在T
这个泛型上是协变的。
但是如何才能让 MyClass< A > 成为 MyClass< B > 的子类型呢?
刚才已经讲了,如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。而要实现这一点,则
需要让MyClass类中的所有方法都不能接收T类型的参数。换句话说,T只能出现在out 位置
上,而不能出现在in位置上。
现在修改SimpleData类的代码,如下所示:
class SimpleData<out T>(val data: T?) { fun get(): T? { return data }
}
这里我们对SimpleData类进行了改造,在泛型T的声明前面加上了一个out关键字。这就意味
着现在T只能出现在out 位置上,而不能出现在in位置上,同时也意味着SimpleData在泛型T上
是协变的。
由于泛型T不能出现在in位置上,因此我们也就不能使用set()方法为data参数赋值了,所以这
里改成了使用构造函数的方式来赋值。你可能会说,构造函数中的泛型T不也是在in位置上的
吗?没错,但是由于这里我们使用了val关键字,所以构造函数中的泛型T仍然是只读的,因此
这样写是合法且安全的。另外,即使我们使用了var关键字,但只要给它加上private修饰
符,保证这个泛型T对于外部而言是不可修改的,那么就都是合法的写法。
经过了这样的修改之后,下面的代码就可以完美编译通过且没有任何安全隐患了:
fun main() { val student = Student("Tom", 19) val data = SimpleData<Student>(student) handleMyData(data) val studentData = data.get()
}
fun handleMyData(data: SimpleData<Person>) { val personData = data.get()
}
由于SimpleData类已经进行了协变声明,那么SimpleData自然就是
SimpleData的子类了,所以这里可以安全地向handleMyData()方法中传递参
数。
然后在handleMyData()方法中去获取SimpleData封装的数据,虽然这里泛型声明的是
Person类型,实际获得的会是一个Student的实例,但由于Person是Student的父类,向上
转型是完全安全的,所以这段代码没有任何问题。
学到这里,关于协变的内容你就掌握得差不多了,不过最后还有个例子需要回顾一下。前面我
们提到,如果某个方法接收一个List类型的参数,而传入的却是一个
List的实例, 在Java 中是不允许这么做的。注意这里我的用语,在Java 中是不允
许这么做的。
你没有猜错,在Kotlin 中这么做是合法的,因为Kotlin 已经默认给许多内置的API加上了协变声
明,其中就包括了各种集合的类与接口。还记得我们在第2章中学过的吗?Kotlin 中的List本身
就是只读的,如果你想要给List添加数据,需要使用MutableList 才行。既然List是只读的,也
就意味着它天然就是可以协变的,我们来看一下List简化版的源码:
public interface List<out E> : Collection<E> { override val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> public operator fun get(index: Int): E
}
List在泛型E的前面加上了out关键字,说明List在泛型E上是协变的。不过这里还有一点需要说
明,原则上在声明了协变之后,泛型E就只能出现在out 位置上,可是你会发现,在
contains()方法中,泛型E仍然出现在了in位置上。
这么写本身是不合法的,因为在in位置上出现了泛型E就意味着会有类型转换的安全隐患。但是
contains()方法的目的非常明确,它只是为了判断当前集合中是否包含参数中传入的这个元
素,而并不会修改当前集合中的内容,因此这种操作实质上又是安全的。那么为了让编译器能
够理解我们的这种操作是安全的,这里在泛型E的前面又加上了一个@UnsafeVariance注解,
这样编译器就会允许泛型E出现在in位置上了。但是如果你滥用这个功能,导致运行时出现了类
型转换异常,Kotlin 对此是不负责的。
泛型的逆变
理解了协变之后再来学习逆变,我觉得会相对比较容易一些,因为它们之间是有所关联的。
不过仅从定义上来看,逆变与协变却完全相反。那么这里先引出定义吧,假如定义了一个
MyClass < T >的泛型类,其中A是B的子类型,同时 MyClass< B > 又是 MyClass < A >的子类型,
那么我们就可以称MyClass在T这个泛型上是逆变的。协变和逆变的区别如图

协变与逆变的区别
从直观的角度上来思考,逆变的规则好像挺奇怪的,原本A是B的子类型,怎么MyClass能
反过来成为MyClass的子类型了呢?别担心,下面我们通过一个具体的例子来学习一下,
你就明白了。
这里先定义一个Transformer接口,用于执行一些转换操作,代码如下所示:
interface Transformer<T> { fun transform(t: T): String
}
可以看到,Transformer接口中声明了一个transform()方法,它接收一个T类型的参数,并
且返回一个String类型的数据,这意味着参数T在经过transform()方法的转换之后将会变成
一个字符串。至于具体的转换逻辑是什么样的,则由子类去实现,Transformer接口对此并不
关心。
那么现在我们就尝试对Transformer接口进行实现,代码如下所示:
fun main() { val trans = object : Transformer<Person> { override fun transform(t: Person): String { return "${t.name} ${t.age}" } } handleTransformer(trans) // 这行代码会报错
}
fun handleTransformer(trans: Transformer<Student>) { val student = Student("Tom", 19) val result = trans.transform(student)
}
首先我们在main()方法中编写了一个Transformer的匿名类实现,并通过
transform()方法将传入的Person对象转换成了一个“姓名+ 年龄”拼接的字符串。而
handleTransformer()方法接收的是一个Transformer类型的参数,这里在
handleTransformer()方法中创建了一个Student对象,并调用参数的transform()方法
将Student对象转换成一个字符串。
这段代码从安全的角度来分析是没有任何问题的,因为Student是Person的子类,使用
Transformer的匿名类实现将Student对象转换成一个字符串也是绝对安全的,并
不存在类型转换的安全隐患。但是实际上,在调用handleTransformer()方法的时候却会提
示语法错误,原因也很简单,Transformer并不是Transformer的子
类型。
那么这个时候逆变就可以派上用场了,它就是专门用于处理这种情况的。修改Transformer接
口中的代码,如下所示:
interface Transformer<in T> { fun transform(t: T): String
}
这里我们在泛型T的声明前面加上了一个in关键字。这就意味着现在T只能出现在in位置上,而
不能出现在out 位置上,同时也意味着Transformer在泛型T上是逆变的。
没错,只要做了这样一点修改,刚才的代码就可以编译通过且正常运行了,因为此时
Transformer已经成为了Transformer的子类型。
逆变的用法大概就是这样了,如果你还想再深入思考一下的话,可以想一想为什么逆变的时候
泛型T不能出现在out 位置上?为了解释这个问题,我们先假设逆变是允许让泛型T出现在out 位
置上的,然后看一看可能会产生什么样的安全隐患。
修改Transformer中的代码,如下所示:
interface Transformer<in T> { fun transform(name: String, age: Int): @UnsafeVariance T
}
可以看到,我们将transform()方法改成了接收name和age这两个参数,并把返回值类型改成
了泛型T。由于逆变是不允许泛型T出现在out 位置上的,这里为了能让编译器正常编译通过,所
以加上了@UnsafeVariance注解,这和List源码中使用的技巧是一样的。
那么,这个时候可能会产生什么样的安全隐患呢?我们来看一下如下代码就知道了:
fun main() { val trans = object : Transformer<Person> { override fun transform(name: String, age: Int): Person {return Teacher(name, age) } } handleTransformer(trans)
}
fun handleTransformer(trans: Transformer<Student>) { val result = trans.transform("Tom", 19)
}
上述代码就是一个典型的违反逆变规则而造成类型转换异常的例子。在
Transformer的匿名类实现中,我们使用transform()方法中传入的name和age
参数构建了一个Teacher对象,并把这个对象直接返回。由于transform()方法的返回值要求
是一个Person对象,而Teacher是Person的子类,因此这种写法肯定是合法的。
但在handleTransformer()方法当中,我们调用了Transformer的
transform()方法,并传入了name和age这两个参数,期望得到的是一个Student对象的返
回,然而实际上transform()方法返回的却是一个Teacher对象,因此这里必然会造成类型转
换异常。
由于这段代码是可以编译通过的,那么我们可以运行一下,打印出的异常信息如图

逆变使用不当造成的类型转换异常
可以看到,提示我们Teacher类型是无法转换成Student类型的。
也就是说,Kotlin 在提供协变和逆变功能时,就已经把各种潜在的类型转换安全隐患全部考虑进
去了。只要我们严格按照其语法规则,让泛型在协变时只出现在out 位置上,逆变时只出现在in
位置上,就不会存在类型转换异常的情况。虽然@UnsafeVariance注解可以打破这一语法规
则,但同时也会带来额外的风险,所以你在使用@UnsafeVariance注解时,必须很清楚自己
在干什么才行。
最后我们再来介绍一下逆变功能在Kotlin 内置API中的应用,比较典型的例子就是Comparable
的使用。Comparable是一个用于比较两个对象大小的接口,其源码定义如下:
interface Comparable<in T> { operator fun compareTo(other: T): Int
}
可以看到,Comparable在T这个泛型上就是逆变的,compareTo()方法则用于实现具体的比
较逻辑。那么这里为什么要让Comparable接口是逆变的呢?想象如下场景,如果我们使用
Comparable实现了让两个Person对象比较大小的逻辑,那么用这段逻辑去比较两
个Student对象的大小也一定是成立的,因此让Comparable成为
Comparable的子类合情合理,这也是逆变非常典型的应用。
相关文章:
kotlin 知识点 七 泛型的高级特性
对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的,因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前,Java 是没有泛型功能的,…...
Transformer LLaMA
一、Transformer Transformer:一种基于自注意力机制的神经网络结构,通过并行计算和多层特征抽取,有效解决了长序列依赖问题,实现了在自然语言处理等领域的突破。 Transformer 架构摆脱了RNNs,完全依靠 Attention的优…...
Qt学习 网络编程 TPC通信
一 基本网络端口 1 网络编程基本概念 通讯方式:信息的通讯时通过网络来进行,通讯方式有两种,TCP和UDP通信,TCP通讯是专用通道,指定某个信息只能走某个通道,UDP则是非专用通道,比如一个车队&am…...
ESP32-S3 实战指南:BOOT-KEY 按键驱动开发全解析
一、基础知识 本篇我们使用 BOOT 按键来学习一下 GPIO 功能,首先补充一下相关术语介绍。 1、GPIO(General Purpose Input/Output) GPIO 是微控制器上的通用引脚,既可以作为输入(读取外部信号)࿰…...
ssh配置 远程控制 远程协作 github本地配置
0.设备版本 windows11 ubuntu24.0.4 1.1 在 Linux 上启用 SSH 服务 首先,确保 Linux 计算机上安装并启用了 SSH 服务。 安装和启动 OpenSSH 服务(如果未安装) # 在终端安装 OpenSSH 服务(如果尚未安装) sudo apt …...
C++知识整理day9——继承(基类与派生类之间的转换、派生类的默认成员函数、多继承问题)
文章目录 1.继承的概念和定义2.基类与派生类之间的转换3.继承中的作用域4.派生类的默认成员函数5.实现一个不能被继承的类6.继承与友元7.继承与静态成员8.多继承和菱形继承问题8.1 继承分类及菱形继承8.2 虚继承 1.继承的概念和定义 概念: 继承(inheritance)机制是⾯…...
2024年国赛高教杯数学建模A题板凳龙闹元宵解题全过程文档及程序
2024年国赛高教杯数学建模 A题 板凳龙闹元宵 原题再现 “板凳龙”,又称“盘龙”,是浙闽地区的传统地方民俗文化活动。人们将少则几十条,多则上百条的板凳首尾相连,形成蜿蜒曲折的板凳龙。盘龙时,龙头在前领头&#x…...
华为认证考试证书下载步骤(纸质+电子版)
华为考试证书可以通过官方渠道下载相应的电子证书,部分高级认证如HCIE还支持申请纸质证书。 一、华为电子版证书申请步骤如下: ①访问华为培训与认证网站 打开浏览器,登录华为培训与认证官方网站 ②登录个人账号 在网站首页,点…...
[杂学笔记]工厂模式、多态、内存空间区域划分、cp指令破坏软连接问题、UDP如何实现可靠传输、滑动窗口的原理、进程与线程、线程之间的通信
目录 1.工厂模式 2.多态 3.内存空间区域划分 4.cp指令破坏软连接问题 5.UDP实现可靠传输 6.滑动窗口的原理 7.进程与线程 8.线程之间的通信 1.工厂模式 工厂模式是一种创建对象的设计模式。它提供了一种创建对象的方式,将对象的创建和使用分离,通…...
开源RAG主流框架有哪些?如何选型?
开源RAG主流框架有哪些?如何选型? 一、开源RAG框架全景图 (一)核心框架类型对比 类型典型工具技术特征适用场景传统RAGLangChain, Haystack线性流程(检索→生成)通用问答、知识库检索增强型RAGRAGFlow, AutoRAG支持重排序、多路召回优化高精度问答、复杂文档处理轻量级…...
【Android】用 chrome://inspect/#devices 调试H5页面
通常做Android开发的过程中,不可避免的需要遇到去与H5交互,甚至有时候需要去调试H5的信息。 这里分享一下Android工程里如何调试H5页面信息: 直接在浏览器地址栏输入 : chrome://inspect/#devices 直接连接手机usb,打开开发者模式…...
贪心算法精品题
1.找钱问题 本题的贪心策略在于我们希望就可能的保留作用大的5元 class Solution { public:bool lemonadeChange(vector<int>& bills) {std::map<int ,int> _map;for(auto ch:bills){if(ch 5) _map[ch];else if(ch 10){if(_map[5] 0) return false;else{_m…...
七、Spring Boot:初识与项目搭建
深入解析 Spring Boot:初识与项目搭建 Spring Boot 是基于 Spring Framework 的开源 Java 基础框架,旨在简化 Spring 应用的开发过程。它通过“约定优于配置”的理念,极大地减少了开发中的配置工作,同时提供了“开箱即用”的功能…...
WEB1~6通杀
##解题思路 这六道题,通杀了,只因为是PHP的特性 来,看web6,过滤最复杂的正则,而且不能解析成大于999的值,但是,php是弱类型的语言,我只要输入任意字符数字,最终值就为0&…...
孜然单授权系统V2.0PHP授权系统
孜然单授权V1.0系统,延续了2022年开发的孜然多应用授权系统V2.0 变更:多应用变单系统,去除没用的垃圾代码,从0开发,去除了一些没用的功能 完善了开发文档,之前那套是我写着玩的屎山代码,V1.0将展…...
Apache SeaTunnel 构建实时数据同步管道(最新版)
文章作者 王海林 白鲸开源 数据集成引擎研发 Apache SeaTunnel Committer & PMC Member,Apache SkyWalking Committer,多年平台研发经验,目前专注于数据集成领域。 导读 在当今数字化快速发展的时代,数据已然成为企业决策…...
数据保护API(DPAPI)深度剖析与安全实践
Windows DPAPI 安全机制解析 在当今数据泄露与网络攻击日益频繁的背景下,Windows 提供的 DPAPI(Data Protection API)成为开发者保护本地敏感数据的重要工具。本文将从 双层密钥体系、加密流程、跨上下文加密、已知攻击向量与防御措施、企业…...
服务器离线部署DeepSeek
目标 本次部署的目标是在本地服务器上部署DeepSeek。但是该服务不能连接外网,因此只能使用离线部署的方式。为了一次完成部署。现在云服务器上进行尝试。 云服务器部署尝试 云服务器配置 CentOS72080Ti 11GB 安装准备 1、上传iso并配置为本地yum源 安装前先将…...
ComfyUI:Stable Diffusion 及 LoRA、VAE 、ControlNet模型解析
目录 Stable Diffusion流程 扩散过程 去噪过程 checkpoints LoRA LoRA 位置与结构 LoRA 层与原层的关系 LoRA 层的参数拆解 VAE 训练特定 VAE 时更新的参数部分 ControlNet ControlNet 位置与结构 ControlNet 的训练过程 ControlNet 的参数处理与信息融合 Contr…...
微信小程序:多菜单栏设计效果
一、实现效果 二、代码 wxml 编辑前端界面,步骤 菜单逻辑: 逐步取出数组中的项,首先取出顶部菜单项,然后选中后取出选中的底部数据(左侧菜单+右侧内容),然后点击左侧菜单取出选中的左侧菜单对应的右侧内容 ①这里我的数据是全部封装到一个数组对象的,首先我的循环…...
【Linux Oracle】time命令+oracle exp压缩
Linux && Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.说明 Linux中的time命令:主要用于测量命令的执行时间,并显示该命令在执行过程中所使用的系统资源情况,如CPU时间、内存和…...
20分钟 Bash 上手指南
文章目录 bash 概念与学习目的第一个 bash 脚本bash 语法变量的使用位置参数管道符号(过滤条件)重定向符号条件测试命令条件语句case 条件分支Arrayfor 循环函数exit 关键字 bash 脚本记录历史命令查询文件分发内容 bash 概念与学习目的 bash࿰…...
v4l2子系统学习(三)编写虚拟摄像头驱动
文章目录 1、声明2、前言3、虚拟摄像头驱动编写3.1、编写硬件相关代码3.2、程序示例 1、声明 本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。 韦老师的《驱动大全》:商品详情 其对应的…...
【前端定位线上问题的多种方案(不依赖 Sentry)】
前端定位线上问题的多种方案(不依赖 Sentry) 🛠️ 一、构建时注入调试信息 🔧 1. 注入版本信息与 Git 提交哈希 Webpack 配置: // webpack.config.js const webpack require(webpack); const gitRevision require(…...
【虚拟仪器技术】labview操作指南和虚拟仪器技术习题答案(一)
今天是2025年2月24日,画的是fate/Grand Order里面的阿尔托莉雅.卡斯特,武内老师的画。 目录 第1章 第2章 第3章 第4章 第5章 关注作者了解更多 我的其他CSDN专栏 毕业设计 求职面试 大学英语 过程控制系统 工程测试技术 虚拟仪器技术 可编程…...
LabVIEW电能质量分析软件
随着电力系统的复杂性增加,电能质量问题日益突出,传统的电能质量检测装置多采用DSP技术,不仅开发周期长、功能单一,而且在多功能集成方面存在局限性。基于LabVIEW虚拟仪器开发平台的电能质量分析软件利用FFT、STFT、WT、HHT等多种…...
视频裂变加群推广分享引流源码
源码介绍 视频裂变加群推广分享引流源码 最近网上很火,很多人都在用,适合引流裂变推广 测试环境:PHP7.4(PHP版本不限制) 第一次访问送五次观看次数,用户达到观看次数后需要分享给好友或者群,好友必须点击推广链接后才会增加观看次…...
《深度剖析:AI与姿态估计技术在元宇宙VR交互中的应用困境》
在元宇宙的宏大版图里,虚拟现实(VR)交互是构建沉浸式体验的关键支柱,而人工智能(AI)与姿态估计技术的融合,本应成为提升交互体验的强大引擎。但在实际应用中,它们面临着诸多复杂且棘…...
项目一 - 任务3:搭建Java集成开发环境IntelliJ IDEA
通过本次实战,我们成功搭建了Java集成开发环境IntelliJ IDEA,并完成了多个任务。首先,安装了IntelliJ IDEA并进行了个性化设置,如选择主题、调整字体和编码等。接着,创建了Java项目、包和类,编写并运行了简…...
RoCBert:具有多模态对比预训练的健壮中文BERT
摘要 大规模预训练语言模型在自然语言处理(NLP)任务上取得了最新的最优结果(SOTA)。然而,这些模型容易受到对抗攻击的影响,尤其是对于表意文字语言(如中文)。 在本研究中࿰…...
