Kotlin基础——类、对象和接口

文章目录

  • 1 定义类继承结构
    • 1.1 接口
      • 1.1.1 接口概述
      • 1.1.2 接口中的默认方法
      • 1.1.3 接口方法重复
      • 1.1.4 Kotlin接口中静态方法实现原理
    • 1.2 修饰符
      • 1.2.1 类继承修饰
      • 1.2.2 方法重写修饰
      • 1.2.3 抽象类
      • 1.2.4 接口的修饰符
    • 1.3 可见性修饰符
      • 1.3.1 Kotlin中的可见性修饰符
      • 1.3.2 Kotlin中的可见性修饰符和Java的对应关系
    • 1.4 内部类和嵌套类
    • 1.5 密封类
  • 2 声明一个带非默认构造方法或属性的类
    • 2.1 主构造方法和初始化语句块
      • 2.1.1 主构造方法的简化流程
      • 2.1.2 默认构造方法
      • 2.1.3 主构造私有
    • 2.2 构造方法:初始化父类
    • 2.3 实现在接口中声明的属性
    • 2.4 通过getter或setter访问支持字段
    • 2.5 修改访问器的可见性
  • 3 数据类和类委托
    • 3.1 通用对象方法
    • 3.2 数据类
    • 3.3 类委托
  • 4 objet关键字
    • 4.1 对象声明
    • 4.2 伴生对象
    • 4.3 作为普通对象使用的伴生对象
    • 4.4 对象表达式:改变写法的匿名内部类

1 定义类继承结构

1.1 接口

1.1.1 接口概述

kotlin中同样使用interface关键字来定义接口,接口的含义和Java中类似,用于定义抽象

fun main() {Button().click()
}interface Clickable {fun click()
}class Button : Clickable {override fun click() {println("Button clicked")}
}

使用interface关键字定义接口。实现一个接口跟Java中不同,Kotlin中继承类和实现接口都是使用冒号。
重写接口中的方法,使用override关键字,与Java中不同的是,override是必须写的,如果不写则会报错。这会避免先写出实现方法再添加抽象方法造成的意外重写。
和Java一样,Kotlin中的类只能继承一个类,但可以实现多个方法。

1.1.2 接口中的默认方法

Kotlin接口中的默认方法也是与Java中有区别,Java中需要使用default关键字修饰,而Kotlin中不用。Kotlin接口中,如果方法有实现,则是一个默认方法,如果没有实现,则是纯接口。

fun main() {Button().click()
}interface Clickable {fun click() {println("default click")}
}class Button : Clickable

对于拥有默认实现的接口方法,可以使用默认实现,也可以重写

1.1.3 接口方法重复

由于可以实现多个接口,如果两个接口出现同样的方法,那么实现类应该怎么处理?在Java中,处理方式是必须重写其中的方法,Kotlin中也是一样的要求。

fun main() {Button().click()
}interface Clickable1 {fun click() {println("default click 1")}
}interface Clickable2 {fun click() {println("default click 2")}
}class Button : Clickable1, Clickable2 {override fun click() {super<Clickable1>.click()}
}

必须显示的重写重复的方法,否则会报错。这里就是重写click方法,具体实现则调用父类Clickable1的click方法。
注意这里super的用法与Java中有区别,Kotlin中,super后面通过尖括号限定是哪个父接口,而Java中则是如下使用

class Button2 implements Clickable1, Clickable2 {@Overridepublic void click() {Clickable1.super.click();}
}

1.1.4 Kotlin接口中静态方法实现原理

Kotlin以jdk1.6为目标进行涉及,当时还没有默认方法。Kotlin会把每个带默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类的结合体

interface Clickable1 {fun click() {println("default click 1")}
}

对于以上包含默认实现方法的Kotlin接口,使用kotlinc编译后,会产生两个class文件
在这里插入图片描述
内容分别为:
Clickable1.class

public interface Clickable1 {void click();
}

Clickable1$DefaultImpls.class

public final class Clickable1$DefaultImpls {public static void click(@NotNull Clickable1 $this) {System.out.println((Object) "default click 1");}
}

接口中只包含了声明,类中包含了一个静态方法,该方法有实现

1.2 修饰符

Kotlin中常用的修饰符有open、final、abstract
Java中有关键字final用于修饰方法,被修饰的方法在其子类中不能够被重写。Kotlin中的final也是有此用途,Kotlin中的open与final对应,表示可以被重写。
Kotlin与Java设计理念不同,Java中,默认是open的,子类可以重写父类中的方法,这会导致脆弱的基类问题。对基类的修改可能会导致子类无法正常运行。在Java中,也建议将子类不需要重写的方法在基类中设置成final。
Kotlin依据此,从语言级别上,将默认的修饰符设置成final,只有需要重写的方法,才需要显示的使用open修饰,允许子类进行重写。
open和final的修饰不止应用于方法级别,对于类的继承也是一样。

修饰符相关成员评注
final不能被重写类中成员默认使用
open可以被重写需要明确地表示
abstract必须被重写只能在抽象类中使用,抽象成员不能有实现
override重写父类或接口中的成员如果没有使用final表明,重写的成员默认是open的

1.2.1 类继承修饰

在这里插入图片描述
类默认是final的,无法继承,只能将基类设置成open的才能够继承

open class Father
class Son : Father()

继承父类,并调用父类的构造方法

1.2.2 方法重写修饰

在这里插入图片描述
方法默认是final修饰的,需要定义成open才能够重写

open class Father {open fun getName() {println("Father")}
}
class Son : Father() {override fun getName() {println("Son")}
}

定义成open后可以被子类重写

open class Son : Father() {override fun getName() {println("Son")}
}class Grandson : Son() {override fun getName() {println("GrandSon")}
}

子类重写后子类的方法也是open的,如果不需要让当前子类的子类重写,则需要重新显示的定义final修饰

1.2.3 抽象类

一个类如果被abstract修饰,那么就是抽象类。

abstract class Animated {abstract fun animate();open fun stopAnimating() {}fun animateTwice() {}
}

抽象类中的抽象方法也需要使用abstract修饰,没有使用abstract修饰的方法是普通方法。抽象方法是默认open的,而普通方法则是默认final的。

1.2.4 接口的修饰符

接口中不使用open、final、abstract修饰符。
其中open是默认的,加不加都不影响。
不能使用final修饰,因为接口中的方法就是用来重写的。
如果接口中的方法没有实现,则默认是abstract的,有实现则不是。

1.3 可见性修饰符

1.3.1 Kotlin中的可见性修饰符

Kotlin中的可见性修饰符有private、protected和public,与Java相比,少了default。
Kotlin中的默认可见性修饰符是public的。这与Java不同,Java中是包私有的。
Kotlin中的包和Java中的包含义不同,Kotlin中的包只是代码组织的一种形式,没有用作可见性控制。
Kotlin引入了一个新的可见性修饰符,internal,表示模块内可见。internal可见性的优势在于它提供了对模块实现细节的真正封装。
Kotlin允许在顶层声明中使用private可见性,包括类、函数和属性。这些声明就会只在声明它们的文件中可见。

修饰符类成员顶层声明
public所有地方可见所有地方可见
internal模块中可见模块中可见
protected子类中可见
private类中可见文件中可见

在这里插入图片描述
类的基础类型和类型参数列表中用到的所有类,或者函数的签名都有与这个类或者函数本身相同的可见性

1.3.2 Kotlin中的可见性修饰符和Java的对应关系

  • Kotlin中的public、protected和private修饰符在编译成Java字节码时会被保留。
  • 唯一的例外是private类:在这种情况下它会被编译成包私有声明(在Java中你不能把类声明为private。
  • internal修饰符在字节码中会变成public。

1.4 内部类和嵌套类

Kotlin中也是可以在一个类中定义类,但是与Java不同的是,Kotlin中定义的类默认不是成员内部类,并没有持有外部类的引用,而是类似于Java中的static修饰的静态内部类。如果要定义类似Java中的成员内部类,需要使用inner修饰。
在这里插入图片描述
嵌套类不持有父类的引用,无法直接访问父类的成员。

class Outer {val num = 10inner class Inner {fun printTest() {println(num)}}
}

使用inner修饰,嵌套类会持有父类的引用,可以直接访问父类的成员,包括私有成员,类似于成员内部类。

class Outer {val num = 10inner class Inner {fun getOuterInstance() : Outer{return this@Outer}}
}

访问外部类对象,使用this@访问。
Kotlin对于嵌套类的设计可以防止Java中的以下问题:当内部类可序列化而外部类不可序列化时,如果序列化内部类对象,会报异常,因为内部类对象存储了外部类的引用,而外部类是不可序列化的。

1.5 密封类

密封类使用sealed关键字修饰,对其可能创建的子类做出严格的限制,所有的直接子类必须嵌套在父类中。

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Exprfun eval(e: Expr): Int =when (e) {is Num -> e.valueis Sum -> eval(e.left) + eval(e.right)else-> throw IllegalArgumentException("Unknown expression")}

考虑上述场景,当使用when结构来执行表达式的时候,Kotlin编译器会强制检查默认选项。如果新增一个子类,就得新增分支,如果忘了新增分支,则会走默认分支,可能导致潜在的bug。
如果使用密封类,就可以解决上述问题。

sealed class Expr {class Num(val value: Int) : Expr()class Sum(val left: Expr, val right: Expr) : Expr()
}fun eval(e: Expr): Int =when (e) {is Expr.Num -> e.valueis Expr.Sum -> eval(e.left) + eval(e.right)}

外部类无法继承Expr类,所有继承了Expr类的子类都在其内部。

2 声明一个带非默认构造方法或属性的类

Java中,一个类可以声明多个构造方法,Kotlin中也可以,Kotlin中只是做了一些区分,区分主构造方法和从构造方法。
主构造方法:通常是简洁的类初始化方法,声明在类定义的时候
从构造方法:在类内部声明

2.1 主构造方法和初始化语句块

2.1.1 主构造方法的简化流程

class User(val nickName: String)

定义了一个类User,并声明了主构造方法,其中nickName是主构造方法的参数。这里是一种简化后的写法,下面是简化流程

//以下就是完整的定义,使用constructor声明一个构造方法,init关键字用来引入一个初始化语句块
//初始化语句块会在类创建的时候执行,并与主构造方法一起使用。由于主构造方法有语法限制,不能包含初始化代码,所以用初始化语句块
//属性的nickName和构造方法参数中的可以定义为相同的,通过this进行区分
class User constructor(_nickName: String) {val nickName: Stringinit {nickName = _nickName}
}//属性声明与初始化语句块中的初始化代码相结合,所以可以省略初始化语句块
class User constructor(_nickName: String) {val nickName: String
}//如果主构造方法没有注解或者可见性修饰符,可以去掉constructor关键字
class User(_nickName: String) {val nickName: String
}//如果属性用相应的构造方法参数来初始化,可以将val关键字放到参数前,并省略属性定义来简化
//这就简化成了最终的定义
class User(val nickName: String)

2.1.2 默认构造方法

如果一个类没有声明任何构造法方法,在继承的时候,必须显示调用父类构造

open class Button
class RadioButton: Button()

Button类会生成一个不带任何参数的默认构造方法,继承必须调用父类的构造方法。

2.1.3 主构造私有

如果想确保类不被其他代码实例化,必须把构造方法标记为private

class Secretive private constructor(){}

Java中,通常将构造方法私有来实现静态工具类或者是单例类。这在Kotlin中有替代方案,可以使用顶层函数来代替静态工具类,使用对象声明来代替单例类。

2.2 构造方法:初始化父类

Kotlin中由于区分了主构造方法和从构造方法,对于主构造方法来说,一个类中只能有一个,而可以拥有多个从构造方法。
继承时,主构造方法在继承后面调用父类的构造方法。从构造方法使用super调用。

//拥有主构造方法的类继承父类
open class Person
class Student : Person()//只有从构造方法的类继承父类
open class View {constructor(ctx: Context) {}constructor(ctx: Context, attr: AttributeSet) {}
}class MyButton : View {//constructor(ctx: Context) : super(ctx)constructor(ctx: Context) : this(ctx, MY_STYKLE)constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr)
}

可以直接使用super调用父类构造,也可以通过自身调用了父类构造的其他构造方法来调用父类构造

2.3 实现在接口中声明的属性

Kotlin接口与Java接口对于属性有所区别。Java,可以在接口中定义常量,而Kotlin中的常量使用const修饰,只能放在顶层或者object修饰的域中。
Kotlin中,接口可以包含抽象属性说明,让实现类来实现属性声明。

interface User {val nickName: String
}class PrivateUser(override val nickName: String) : User
class SubscribingUser(val email: String) : User {override val nickName: Stringget() = email.substringBefore('@')
}class FacebookUser(val accountId: Int) : User {override val nickName = getFacebookName(accountId)private fun getFacebookName(accountId: Int): String {return "test"}
}

接口中定义抽象属性说明,在实现类中用字段去存储,可以表达不同的含义。
第一个PrivateUser使用主构造方法中定义的属性字段进行存储。第二个SubscribingUser通过主构造方法中的属性email来提供nickName这个属性的一个get方法。第三个通过一个函数返回。

除了抽象属性声明外,接口还可以包含具有getter和setter的属性,只要它们没有引用一个存储字段

interface User {val email: Stringval nickName: Stringget() = email.substringBefore('@')
}

2.4 通过getter或setter访问支持字段

目前有两种属性的例子:一种是存储值的属性,一种是具有自定义访问器在每次访问时计算值的属性。
如何在自定义访问其中访问支持字段呢?

class User(val name: String) {var address: String = "Beijing"set(value) {println("""Address was changed for $name: "$field" -> "$value".""".trimIndent())}
}fun main() {val user = User("Alice")user.address = "Shanghai"
}

这里有一个关键字field用于访问支持字段

2.5 修改访问器的可见性

访问器的可见性默认与属性的可见性相同,但是可以修改其可见性

class LengthCounter {var counter: Int = 0private setfun addWord(word: String) {counter += word.length}
}

默认属性的可见性是public的,将其修改为private,在外部无法修改counter的值。

3 数据类和类委托

3.1 通用对象方法

在编写Java类时,有些方法是常常要重写的,比如toString、equals和hashCode这三个方法。
toString方法用来打印对象实例,如果想输出具体属性值,需要重写该方法。
equals方法用于比较两个对象是否相等,重写之后可用于比较对象中属性是否相等。这里有双等号和equals的区别,双等号在Java中对于基本类型,比较值,对于引用,比较对象地址。在Java的实践中,通常总是调用equals比较对象内容。在Kotlin中,双等号是比较两个对象的默认方式:本质上说是通过equals比较两个值的,所以重写equals后,可以直接使用双等号比较。
hashCode方法用于实现hash运算,便于使用hash算法相关的容器。hash算法的约定是:如果两个对象相等,它们必须有相同的hash值。所以重写equals,必须重写hashCode,以维护这个约定。

3.2 数据类

Kotlin提供了一个新的关键字——data,用于定义数据类。数据类自动重写了equals、hashCode和toString方法。

data class Client(val name: String, val postalCode: Int)fun main() {val client = Client("test", 32)println(client)
}
输出:
Client(name=test, postalCode=32)

数据类的属性并没有要求一定是val,但是建议使用val,让数据类的实例不可变。这样做有很多好处,一是容器中作为键使用,不用担心被修改。二是在多线程环境下不担心被修改。
由于建议数据类尽量不被修改,所以提供了copy()方法,可以用于copy类的实例,并在copy的同时修改某些属性的值,这个是编译器自动生成的。

val client2 = client.copy("hello", 23)
println(client2)
输出:
Client(name=hello, postalCode=23)

3.3 类委托

考虑Java中实现装饰器模式,创建一个新类,实现与原始类一样的接口并将原来的类的实例作为一个字段保存,与原始类拥有同样行为的接口不用修改,直接转发到原始类

class DelegatingCollection<T> : Collection<T> {private val innerList = arrayListOf<T>();override val size: Intget() = innerList.sizeoverride fun isEmpty(): Boolean = innerList.isEmpty()override fun iterator(): Iterator<T> = innerList.iterator()override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)override fun contains(element: T): Boolean = innerList.contains(element)
}

这里就有很多都是模板代码。Kotlin中可以通过委托来实现上述功能,使用by关键字将接口的实现委托到另一个对象。

class DelegatingCollection<T>(innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList {
}

这里使用类委托,上述的模板代码都消失了

class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet {var objectsAdded = 0;override fun add(element: T): Boolean {objectsAdded++;return innerSet.add(element)}override fun addAll(elements: Collection<T>): Boolean {objectsAdded += elements.sizereturn innerSet.addAll(elements)}
}

可以只重写关注的方法,其他的方法委托给父类对象。

4 objet关键字

objet关键字的实质是定义一个类并同时创建一个对象,常使用的场景有:

  • 对象声明是定义单例的一种方式
  • 伴生对象可以持有工厂方法和其他与这个类相关,但在调用时并不依赖类实例的方法。它们的成员可以通过类名来访问
  • 对象表达式可以用来替代Java的匿名内部类

4.1 对象声明

对象声明通过object关键字引入

object CaseInsensitiveFileComparator : Comparator<File> {override fun compare(file1: File, file2: File): Int {return file1.path.compareTo(file2.path, ignoreCase = true)}
}

对象声明,声明一个类并创建一个对象,是天生的单例类。
与类一样,一个对象声明可以包含属性、方法、初始化语句块等声明,唯一不允许的是构造方法(包括主构造和从构造)
对象声明的对象,在对象声明定义时就创建了,不需要在代码其他地方调用构造方法。
但是在大型系统中,由于对象声明不提供构造,无法对对象实例化进行控制,并且不能通过构造方法指定特定的参数,所以大型系统中还是需要将依赖注入的框架与普通的Kotlin类一起使用。
Kotlin中的对象声明被编译成了通过静态字段来持有它的单一实例的类,这个字段的名字始终是INSTANCE,Java中可以通过如果下调用

CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);

4.2 伴生对象

Kotlin中的类不能拥有静态成员,Java中的static关键字并不是Kotlin语言的一部分。作为替代,Kotlin依赖包级别函数和对象声明。
在大多数情况下,使用顶层函数可以替代,但是顶层函数不能访问类中的private成员。
如果需要写一个可以在没有类实例的情况下调用但是需要访问类内部的函数,可以将其写成类中的对象声明的成员。
在类中定义的对象之一可以使用一个特殊的关键字来标记:companion。如果这样做,就获得了直接通过容器类名称来访问这个对象的方法和属性的能力,不在需要显式地指明对象的名称。

class A {companion object {val a = 120fun bar() {println("Companion object called")println("c is ${A().c}")}}
}fun main() {A.bar()println(A.a)
}
输出:
Companion object called
c is 20
120

这里companion object就是声明了一个伴生对象,伴生对象可以访问类中的所有private成员,包括private构造方法,它是实现工厂模式的理想选择。

class User {val nickName: Stringconstructor(email: String) {nickName = email.substringBefore('@')}constructor(facebookAccountId: Int) {nickName = getFacebookName(facebookAccountId)}private fun getFacebookName(accountId: Int): String {return "test"}
}

之前的User类可以通过伴生对象来实现成工厂模式

class User private constructor(val nickName: String) {companion object {fun newSubscribingUser(email: String) = User(email.substringBefore('@'))fun newFacebookUser(accountId: Int) = User(getFacebookName(accountId))private fun getFacebookName(accountId: Int): String {return "test"}}
}

工厂方法很有用,可以通过用途来命名来创建对应的子类对象。

4.3 作为普通对象使用的伴生对象

伴生对象是一个声明在类中的普通对象,可以有名字,实现一个接口或者有扩展函数或属性。

class Person(val name: String) {companion object Loader {fun fromJSON(jsonText: String): Person {return Person("test")}}
}

伴生对象可以有自己的名字,如果没有提供名字,则会使用默认的名字Companion

interface JSONFactory<T> {fun fromJSON(jsonText: String): T
}
class Person(val name: String) {companion object : JSONFactory<Person> {override fun fromJSON(jsonText: String): Person {return Person("test")}}
}

伴生对象可以实现接口
类的伴生对象会同样被编译成常规对象:类中的一个引用了它的实例的静态字段,Java中访问伴生对象的方法如下

public static void main(String[] args) {Person.Companion.fromJSON("");
}

如果伴生对象有名字,则Companion这个默认名字会被替换。
可以在对应的成员上使用@JvmStatic注解来将类中成员设置成静态的,如果想声明一个static的字段,可以再在一个顶层属性或者声明在object中的属性上使用@JvmField注解。

class Person(val name: String) {companion object : JSONFactory<Person> {@JvmStaticvar test: String = "hello"@JvmStaticoverride fun fromJSON(jsonText: String): Person {return Person("test")}}
}

对于属性来说,会生成静态的getter和setter方法。对于方法来说,会生成静态的方法。
Java中调用如下:

ystem.out.println(Person.getTest());
System.out.println(Person.fromJSON(""));

对于@JvmField注解

class Person(val name: String) {companion object : JSONFactory<Person> {@JvmFieldvar test: String = "hello"override fun fromJSON(jsonText: String): Person {return Person("test")}}
}

会生成静态的字段,Java中访问如下:

System.out.println(Person.test);

伴生对象可以定义扩展函数,调用就像是该函数定义在伴生对象中定义的函数一样。

class Person(val name: String) {companion object {}
}fun Person.Companion.fromJSON(jsonText: String): Person {return Person("test")
}fun main() {val p = Person.fromJSON("")
}

4.4 对象表达式:改变写法的匿名内部类

object关键字不仅仅能用来声明单例式的对象,还能用来声明匿名对象。匿名对象替代了Java中匿名内部类的用法。

open class Person(val name: String) {open fun testFun() {println("Person testFun")}
}fun main() {object : Person("test") {override fun testFun() {println("object testFun")}}.testFun()
}

使用object创建了一个匿名内部类
与Java匿名内部类只能扩展一个类或实现一个接口不同,Kotlin的匿名对象可以实现多个接口或者不识闲接口。
注意与对象声明不同,匿名对象不是单例的。每次对象表达式被执行都会创建一个新的对象实例。
与Java的匿名类一样,可以访问创建它的函数中的变量,但是不同的是Java只能访问final类型的变量,而Kotlin中没有限制,还可以在对象表达式中修改变量的值。

fun main() {var num = 20object : Person("test") {override fun testFun() {println("object testFun")num++}}.testFun()
}

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

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

相关文章

蓝桥杯真题:扑克牌移动

import java.util.*; public class Main {public static List moveCard(List src){if(srcnull) return null;List dst new Vector();for(;;){if(src.size()0) break; // 填空src.add(src.remove(0));//remove(0) 是一个 List 接口中的方法调用&#xff0c;它表示移除列表中指…

【VSCode】使用笔记

目录 快捷键系列 相关插件 相关文档链接 快捷键系列 调出终端 ctrl 或者是ctrlJ 结束进程 ctrlc 注释 ctrlkc 取消注释 ctrlku 上下移动代码 alt方向键 多行光标ctrlalt方向键 快速跳过某个单词 ctrl方向键 相关插件 1.每次修改后&#xff0c;自动保存启动项目 相…

selenium定位元素报错:‘WebDriver‘ object has no attribute ‘find_element_by_id‘

Selenium更新到 4.x版本后&#xff0c;以前的一些常用的代码的语法发生了改变 from selenium import webdriver browser webdriver.Chrome() browser.get(https://www.baidu.com) input browser.find_element_by_id(By.ID,kw) input.send_keys(Python)目标&#xff1a;希望通…

解决vitepress首次加载慢(从40秒到1秒的倔强)

前言&#xff1a;在艰难的博客系统升级之路 这篇博客中我有提到vitepress首次加载非常耗时的问题&#xff0c;之前也在网上搜索时发现也有很多人说这个“问题”&#xff0c;但是在折腾了这么一段时间后&#xff0c;发现这也许本身不是vitepress的问题&#xff0c;而是我的启动方…

娅奴服饰:行至云深处,问计新零售

编辑&#xff1a;阿冒 设计&#xff1a;沐由 大浪壮美&#xff0c;时尚前行。 作为广东省首批特色小镇创建示范点&#xff0c;以及粤港澳大湾区唯一的特色时尚小镇&#xff0c;大浪时尚小镇云集了700余家服装及配套企业&#xff0c;涌动着蓬勃的生机与无尽的活力。 国内知名的“…

基于Java (spring-boot)的房屋租赁管理系统

一、项目介绍 基于Java (spring-boot)的房屋租赁管理系统功能&#xff1a;登录、管理员、租客、公告信息管理、房屋信息管理、用户信息管理、租金信息管理、故障信息管理、房屋出租信息详情、个人信息、修改密码、等等等。 适用人群&#xff1a;适合小白、大学生、毕业设计、课…

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G题解

题目 在一个果园里&#xff0c;多多已经将所有的果子打了下来&#xff0c;而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。 每一次合并&#xff0c;多多可以把两堆果子合并到一起&#xff0c;消耗的体力等于两堆果子的重量之和。可以看出&#xff0c;所…

Springboot的it职业生涯规划系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; Springboot的it职业生涯规划系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

【Linux】初步使用makefile

makefile 1 快速使用1.1 认识makefile1.2 使用makefile 2 深入理解理解 **依赖关系 与 依赖方法**如何实现源代码修改了才会重新编译 3 内置符号理解Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff01;&a…

家人们,比赛打完了

啊&#xff0c;终于打完一场比赛了&#xff0c;但还有三场…… 先看看我的战绩&#xff1a; 共八题&#xff0c;AC6题&#xff0c;总共3902分&#xff0c;3.7k人参加&#xff0c;第980名 来看看第一&#xff1a; A8题&#xff0c;我只有2题没做出&#xff0c;相差4000多分&am…

交通管理|交通管理在线服务系统|基于Springboot的交通管理系统设计与实现(源码+数据库+文档)

交通管理在线服务系统目录 目录 基于Springboot的交通管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、驾驶证业务管理 3、机动车业务管理 4、机动车业务类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计…

五、OpenAi之函数调用(Function Calling)(一)

1.函数调用(Function Calling) 学习如何使大语言模型连接到外部工具 1.1介绍 在API调用中&#xff0c;您可以描述函数规范&#xff0c;让模型智能地选择输出包含参数的JSON对象&#xff0c;过程中你以调用一个或多个函数。聊天补全API自身不能调用函数&#xff1b;而是&…