Go语言结构体、方法与接口

文章目录

  • 一、结构体构造函数
      • Go语言中的构造函数语法
  • 二、结构体方法和接收器
      • 无参数和返回值
      • 值类型接收者
      • 指针类型接收者
        • 方法继承
        • 方法重写
  • 三、结构体比较
      • 结构体比较要求
      • 结构体比较符号
  • 四、接口声明
      • 接口定义
      • 接口特点
      • 接口格式
      • 标准格式接口的实现:
      • 空接口
      • error接口
  • 五、接口与结构体
      • 接口与结构体的关系
      • 一个结构体类型实现多个接口
      • 一个接口被多个结构体实现
  • 六、接口的嵌套
      • 接口嵌套的定义
      • 接口嵌套的格式

一、结构体构造函数

Go语言中的构造函数:

  • Go 语言没有构造函数的功能
  • 使用结构体初始化的过程来模拟实现构造函数
  • 结构体可以像其他数据类型一样将结构体类型作为参数传递给函数

在这里插入图片描述

Go语言中的构造函数语法

type 类型名 struct {
    字段名1 字段类型1
    字段名2 字段类型2
    ......
}
func newfunc(变量名1 变量类型, 变量名2 变量类型 ……) *类型名{
    return &类型名{
        字段名1: 变量1,
        字段名2: 变量2......
    }
}

  • Go语言的类型或结构体没有构造函数的功能,但是我们可以使用结构体初始化的过程来模拟实现构造函数。
  • 构造函数传入的变量类型需要与return返回结构体指针字段名类型一致
  • 构造函数可以只返回结构体中某些字段名

示例1:

package main
import "fmt"
type person struct {
    name string
    age  int
}
//构造函数约定俗成用new开头,不是强制
//返回的是结构体还是结构体指针
//当结构体比较大的时候使用结构体指针,减少程序内存的开销。
func personOnlyName(name string) *person {
    return &person{
        name: name,
    }//仅返回部分字段
}

func newperson(name string, age int) *person {
    return &person{
        name: name,
        age:  age,
    }//返回所有字段
}
func main()  {
    p1 := personOnlyName("Go语言")
    fmt.Println(p1)
    p2 := newperson("golang", 20)
    fmt.Println(p2)
}



//运行结果:
//&{Go语言 0}
//&{golang 20}

示例2:

package main
import "fmt"
type person struct {
    name string
    age  int
}
func print1(p person){
    p.name="值传递"
    fmt.Println("print1",p)
}
func print2(p *person){
    p.name="指针传递"
    fmt.Println("print2",p)
}

func main()  {
    p1:=person{"Go语言",20}
    print1(p1)	      //值传递,形参无法改实参
    fmt.Println(p1)
    print2(&p1)  //指针传递,形参可以改实参
    fmt.Println(p1)
}



//运行结果:
//print1 {值传递 20}
//{Go语言 20}
//print2 &{指针传递 20}
//{指针传递 20}

二、结构体方法和接收器

Go语言同时有函数和方法,方法的本质是函数,但是方法和函数又具有不同点。
在这里插入图片描述在这里插入图片描述

无参数和返回值

func (结构体变量 结构体类型) 方法名() {
    函数体
}

值类型接收者

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

指针类型接收者

func (接收者变量 *接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

示例:

package main
import "fmt"
type person struct {
    name string
    age  int
}
func (p person)print(){  //无参数和返回值
    fmt.Println(p)
}
func (p person)change1(s string){
    p.name=s  //值类型接收者
}
func (p *person)change2(s string){
    p.name=s   //指针类型接收者
}

func main()  {
    p := person{"Go",20}
    p.print()
    p.change1("Golang")
    p.print()
    p.change2("Go语言")
    p.print()
}




//运行结果为:
//{Go 20}
//{Go 20}
//{Go语言 20}

若方法的接受者不是指针,实际只是获取了一个copy,而不能真正改变接受者中原来的数据。

使用方法的原因:

  • 既然可以用函数来写相同的程序,却还要使用方法,主要有以下两个原因
  • 如Go不是一种纯粹面向对象的编程语言,它不支持类。因此其方法是一种实现类似于类的行为的方法。
  • 如果相同名称的方法可以在不同的类型上定义,而具有相同名称的函数是不允许的。假设有一个正方形和圆形的结构。可以在正方形和圆形上定义一个名为Area的求取面积的方法。
  • 下面通过一个示例来观察不同的结构体中方法名。

示例:

package main
import (
    "math"
    "fmt"
)
type Rectangle struct {
    width, height float64
}
type Circle struct {
    radius float64
}
func main() {
    r1 := Rectangle{10, 4}
    r2 := Rectangle{12, 5}
    c1 := Circle{1}
    c2 := Circle{10}    

    fmt.Println("r1 的面积", r1.Area())
    fmt.Println("r2 的面积", r2.Area())
    fmt.Println("c1 的面积", c1.Area())
    fmt.Println("c2 的面积", c2.Area())
}
func (r Rectangle) Area() float64 {
    return r.width * r.height
}   // 定义 Rectangle 的方法
func (c Circle) Area() float64 {
    return c.radius * c.radius * math.Pi
}   // 定义 Circle 的方法




//运行结果为:r1 的面积 40
//r2 的面积 60
//c1 的面积 3.141592653589793
//c2 的面积 314.1592653589793

方法继承
  • 方法是可以继承的,如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该匿名结构体中的方法。
方法重写
  • 方法是可以继承和重写的。

示例:

package main
import "fmt"
type Human struct { 
    name  string
    phone string
    age   int
}
type Student struct { // 继承Human
    Human  // 嵌入Human,匿名字段
    school string
} 
type Employee struct {
    Human   // 匿名字段
    company string
}  

// Human的SayHi方法
func (h Human) SayHi() {
    fmt.Printf("大家好! 我是%s,%d岁,联系方式是: %s\n", h.name, h.age, h.phone)
}  
func main() {
    s1 := Student{Human
{"Daniel", "15012345678", 13}, "十一中学"}
    e1 := Employee{Human
{"Steven", "17812345678", 35}, "1000phone"}
    s1.SayHi()
    e1.SayHi()
}

package main
import "fmt"
type Human struct {
	name  string
	phone string
	age   int
}
type Student struct {
	Human  //继承Human
	school string
}
type Employee struct {
	Human  //继承Human
	company string
}
func (h Human) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,联系方式是: %s\n", h.name, h.age, h.phone)
}
func (s Student) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,我在%s上学,联系方式是: %s\n", s.name, s.age, s.school, s.phone)
}
func (e Employee) SayHi() {
fmt.Printf("大家好! 我是%s,%d岁,我在%s工作,联系方式是: %s\n", e.name, e.age, e.company, e.phone)  }
func main() {
    s1 := Student{Human{"Daniel", "15012345678", 13}, "十一中学"}
    e1 := Employee{Human{"Steven", "17812345678", 35}, "1000phone"}
    s1.SayHi() // 调用各自的SayHi方法
    e1.SayHi()
}

三、结构体比较

结构体比较要求

  • 结构体可以比较,也不可以比较
  • 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的
  • 如果包含slice、map、function不可比较类型时,那么结构体是不可以比较的

结构体比较符号

  • 可以比较的情况下:两个结构体将可以使用**==或!=运算符进行比较,但不支持>或<**
  • 同类型的两个结构体变量可以相互赋值

示例1:

package main
import "fmt"
type person struct {
    name string
    age  int
}
func main()  {
    p1 :=person{"Go语言",20}
    p2 :=person{"Go语言",20}
    if p1==p2{
        fmt.Println("p1=p2")
    }else {
        fmt.Println("p1!=p2")
    }
}



//运行结果为:
//p1=p2

示例2:

package main
import "fmt"
type person struct {
    name string
    age  []int
}
func main()  {
    p1 :=person{"Go语言",[]int{20}}
    p2 :=person{"Go语言",[]int{20}}
    if p1==p2{
        fmt.Println("p1=p2")
    }else {
        fmt.Println("p1!=p2")
    }
}



//报错提示:
//invalid operation: p1 == p2 (struct containing []int cannot be compared)

!! 不能比较包含[]int的结构体

示例3:

package main
import "fmt"
type person1 struct {
    name string
    age  int
}
type person2 struct {
    name string
    age  int
}
func main()  {
    p1 :=person1{"Go语言",20}
    p2 :=person2{"Go语言",20}
    if p1==p2{
        fmt.Println("p1=p2")
    }else {
        fmt.Println("p1!=p2")
    }
}




//报错提示:
//invalid operation: p1 == p2 (mismatched types person1 and person2)

!! 结构体不同则不能比较

示例4:

package main
import "fmt"
type person1 struct {
    name string
    age  int
}
type person2 struct {
    name string
    age  int
}
func main()  {
    p1 :=person1{"Go语言",20}
    p2 :=person2{"Go语言",20}
    if p1==person1(p2){
        fmt.Println("p1=p2")
    }else {
        fmt.Println("p1!=p2")
    }
}






//运行结果:
//p1=p2

结构体内部相同时,可以使用强制转换来实现比较

四、接口声明

接口

  • 面向对象语言中,接口用于定义对象的行为。接口只指定对象应该做什么,实现这种行为的方法(实现细节)是由对象来决定。
  • 接口定义了一组方法,如果某个对象实现了该接口的所有方法,则此对象就实现了该接口。

Go语言是一种“鸭子类型”的语言

  • Go没有 implements, extends 关键字,其实这种编程语言叫做duck typing编程语言。
  • duck typing是描述事物的外部行为而非内部结构。在“鸭子类型”中,关注的不是对象的类型本身,而是它是如何使用的。

接口定义

  • 接口是对类型行为的约定
  • 接口是一系列方法的集合
  • 接口是一种高度抽象的数据类型
  • 接口中的方法不包含代码
  • 接口中的方法是抽象的
  • 接口中不包含变量
  • 本质是一种关于对象功能的约定

接口特点

  • 接口是双方约定的一种合作协议,接口实现者不需要关心接口会怎样被使用,只需要实现接口里面所有的方法即可
  • 接口不支持直接实例化,只能通过具体的类来实现声明的所有方法,同时函数的函数名、函数参数和函数返回值必须完全一样
  • Go语言中的接口支持赋值操作,从而快速实现接口与实现类的映射

接口格式

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    ......
}

接口要点:

  • 接口类型名:使用 type 将接口定义为自定义的类型名,接口在命名时,一般会在单词后面添加er;
  • 方法名:当方法名首字母是大写时,同时接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问;
  • 参数列表:表示传入到方法中的值;
  • 返回值列表:方法返回值。
    示例:
type Phone interface {
    call()
    SendMsg(msg string) bool
}

标准格式接口的实现:

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    ......
}
type (变量名 类型) 方法名1( 参数列表1 ) 返回值列表1{
}
type (变量名 类型) 方法名2( 参数列表2 ) 返回值列表2{
}

示例:

//标准格式接口的实现案列
type AndroidPhone struct {
}  //实现接口的结构体
 
func (a AndroidPhone) call() {
    fmt.Println("AndroidPhone calling")
}
 
func (a AndroidPhone) SendMsg(msg string) bool {
    fmt.Println("AndroidPhone sending msg")
    return true
}

空接口

  • 是接口类型的特殊形式
  • 空接口没有任何方法,因此任何类型都无须实现空接口
  • 从实现的角度看,任何值都满足这个接口的需求
  • 空接口类型可以保存任何值,也可以从空接口中取出原值

示例:

package main
import "fmt"
func main() {
    var any interface{}
    any = 1
    fmt.Println(any)
    any = "hello"
    fmt.Println(any)
    any = false
    fmt.Println(any)
}




//运行结果为:
//1
//hello
//false

error接口

  • 作为错误处理的标准模式
  • 如果函数要返回错误,则返回值类型列表中肯定包含 error
  • error 处理过程类似于C语言中的错误码,可逐层返回,直到被处理

示例:

package main
import (
    "errors"
    "fmt"
    "math"
)
func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return -1, errors.New("开平方根的数字不能小于0")
    }
    return math.Sqrt(f), nil
}
func main() {
    result, err := Sqrt(-13)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(result)
    }
}






//运行结果为:
//开平方根的数字不能小于0

五、接口与结构体

接口与结构体的关系

在这里插入图片描述

一个结构体类型实现多个接口

格式:

type 接口类型名1 interface{
    方法名1( 参数列表1 ) 返回值列表1
}
type 接口类型名2 interface{
    方法名2( 参数列表2 ) 返回值列表2
}


type 结构体 struct{
    字段名1  字段类型1
    字段名2  字段类型2
}
type (结构体名 结构体) 方法名1( 参数列表1 ) 返回值列表1{
}
type (结构体名 结构体) 方法名2( 参数列表2 ) 返回值列表2{
}

示例:

package main
import "fmt"
type run interface{
    running(time int)int
}
type eat interface{
    eating(food string)int
}
type person struct {
    weight int
}
func (p *person)running(time int)int{
    p.weight -= time/5
    return p.weight
}
func (p *person)eating(food string)int{
    p.weight += 1
    return p.weight
}
func main() {
    p1 :=&person{
        weight: 50,
    }
    fmt.Println(p1.weight)
    p1.running(20)
    fmt.Println(p1.weight)
    p1.eating("面包")
    fmt.Println(p1.weight)






//运行结果为:
//50
//46
//47

一个接口被多个结构体实现

格式:

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
}
type 结构体1 struct{
    字段名1  字段类型1
}
type 结构体2 struct{
    字段名2  字段类型2
}
type 结构体3 struct{
    字段名3 字段类型3
}
type (结构体名1 结构体1) 方法名1( 参数列表1 ) 返回值列表1{
}
type (结构体名2 结构体2) 方法名2( 参数列表2 ) 返回值列表2{
}
type (结构体名3 结构体3) 方法名2( 参数列表2 ) 返回值列表2{
}

示例:

package main
import "fmt"
type who interface{
    who(position string)string
}
type tearcher struct {
    position string
    name string
}
type student struct {
    position string
    name string
}
func (t *tearcher)who(name string)string{
    t.position="教师"
    t.name=name
    return t.position
}
func (s *student)who(name string)string{
    s.position="学生"
    s.name=name
    return s.position
}
func main() {
    var t who=&tearcher{
        position: "未知",
        name: "未知",
    }
    t.who("Go")
    fmt.Println(t)
    var s who=&student{
        position: "未知",
        name: "未知",
    }
    s.who("go")
    fmt.Println(s)
}








//运行结果为:
//&{教师 Go}
//&{学生 go}

六、接口的嵌套

接口嵌套的定义

在Go 语言中,不仅仅结构体与结构体之间可以嵌套,接口与接口之间也可以嵌套,被包含的接口中的所有方法都会被包含到新的接口中。

接口嵌套的格式

type 接口类型名1 interface{
    方法名1()
}
type 接口类型名2 interface{
    方法名2()
}
type 接口类型名3 interface{
    接口类型名1
    接口类型名2
    方法名3()
}

一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。

//接口未嵌套
type queue interface {
    height()
}
type run interface {
    weight()
}
type BMI interface {
    height()
    weight()
    age()
}

//接口嵌套
type queue interface {
    	height()
}
type run interface {
    	weight()
}
type BMI interface {
     	queue
	run
    	age()
}

示例:嵌套接口的实现

package main
import "fmt"
type queue interface {
    height() int
}
type run interface {
    weight() int
}
type BMI interface {
    queue //嵌套接口
    run
    age() int
}
type Person struct {  //结构体
    h, w, a int
}
func (p Person) height() int {
    return p.h 
}     // 实现queue接口的height方法
func (p Person) weight() int {
    return p.w
}   
func (p Person) age() int {
    return p.a
} 
func main() {
    p := Person{h: 170, w: 65, a: 25}
    fmt.Println("Height:", p.height())
    fmt.Println("Weight:", p.weight())
    fmt.Println("Age:", p.age())
}






//运行结果为:
//Height: 170
//Weight: 65
//Age: 25

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用Puppeteer点击与数据爬取:实现动态网页交互

用Puppeteer与代理IP抓取51job招聘信息&#xff1a;动态网页交互与数据分析 引言 在数据采集领域&#xff0c;传统的静态网页爬虫方式难以应对动态加载的网页内容。动态网页通常依赖JavaScript加载数据&#xff0c;用户需要与页面交互才能触发内容显示。因此&#xff0c;我们…

Sophos | 网络安全

在 SophosLabs 和 SophosAI 的威胁情报、人工智能和机器学习的支持下&#xff0c;Sophos 提供广泛的高级产品和服务组合&#xff0c;以保护用户、网络和端点免受勒索软件、恶意软件、漏洞利用、网络钓鱼和各种其他网络攻击。Sophos 提供单一的集成式基于云的管理控制台 Sophos …

盘点RPA在政务领域落地应用

数字政府是数字经济的中坚力量&#xff0c;以强有力的“抓手”带动着各行各业的数字化转型以及新技术的应用与普及。近两年&#xff0c;以RPA为代表的数字技术在政务实践中的表现受到了很高的关注&#xff0c;RPA数字员工在各地相关政务部门悄然上岗&#xff0c;有效助力政府信…

mysql5.7安装SSL报错解决(2),总结

Caused by: java.io.EOFException: SSL peer shut down incorrectly 在java里面连接mysql5.7.17数据库&#xff0c;报以上错误&#xff0c; 将数据库升级到mysql5.7.44就可以了。 这两天处理java连接mysql的问题&#xff0c;报了各种错误&#xff0c;总结一下就是openssl和mysq…

前端基础-html-注册界面

&#xff08;200粉啦&#xff0c;感谢大家的关注~ 一起加油吧~&#xff09; 浅浅分享下作业&#xff0c;大佬轻喷~ 网页最终效果&#xff1a; 详细代码&#xff1a; ​ <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"…

论文 | Teaching Algorithmic Reasoning via In-context Learning

这篇论文《通过上下文学习教授算法推理》探讨了如何通过上下文学习&#xff08;In-context Learning, ICL&#xff09;有效训练大型语言模型&#xff08;LLMs&#xff09;以进行算法推理。以下是从多个角度对这项工作的详细解读&#xff1a; 1. 问题陈述与研究动机 算法推理的…

Json 类型与多值索引 — OceanBase 4.3.2 AP 功能体验

本文来自 2024年OceanBase技术征文大赛——“让技术被看见 | OceanBase 布道师计划”的用户征文。也欢迎更多的技术爱好者参与征文&#xff0c;赢取万元大奖。和我们一起&#xff0c;用文字让代码跳动起来&#xff01; 参与2024年OceanBase技术征文大赛>> MySQL在5.7.8…

FPAGA学习~问题记录

1.Error: concurrent assignmentto a non-netstart is not permitted&#xff08;错误&#xff1a;不允许并发分配到非网络‘start’&#xff09; 原因&#xff1a;wire 或reg 类型不匹配引起的&#xff0c;assign与wrie搭配使用&#xff0c;而reg一般在always、initial语句块中…

微服务系列四:热更新措施与配置共享

目录 前言 一、基于Nacos的管理中心整体方案 二、配置共享动态维护 2.1 分析哪些配置可拆&#xff0c;需要动态提供哪些参数 2.2 在nacos 分别创建共享配置 创建jdbc相关配置文件 创建日志相关配置文件 创建接口文档配置文件 2.3 拉取本地合并配置文件 2.3.1 拉取出现…

003-Kotlin界面开发之声明式编程范式

概念本源 在界面程序开发中&#xff0c;有两个非常典型的编程范式&#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑&#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中&#xff0c;程序员需要关心程…

Python作业记录

复制过来的代码的换行有问题&#xff0c;但是也不是什么大问题。 后续我会进行补充和修改。 请将如下英文短句根据单词切分成列表&#xff1a; The continent of Antarctica is rising. It is due to a geological phenomenon called post-glacial uplift 并在切分好的列表…

pdmaner连接sqlexpress

别以为sqlserver默认的端口总是1433 案例 有台sqlserver2008 express服务器&#xff0c;刚安装&#xff0c;支持混合模式登录&#xff0c;其它什么配置也没改。 先看用ADO连接 这说明&#xff1a; 案例中sqlserver端口不是1433 &#xff01;&#xff01;&#xff01;ADO连接…

轻型民用无人驾驶航空器安全操控------理论考试多旋翼部分笔记

官网&#xff1a;民用无人驾驶航空器综合管理平台 (caac.gov.cn) 说明&#xff1a;一是法规部分&#xff1b;二是多旋翼部分 本笔记全部来源于轻型民用无人驾驶航空器安全操控视频讲解平台 目录 官网&#xff1a;民用无人驾驶航空器综合管理平台 (caac.gov.cn) 一、轻型民用无人…

二叉树相关习题

题目&#xff1a;100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; …

阅读笔记记录

论文作者将对话建模成一个seq2seq的映射问题&#xff0c;该seq2seq框架以对话历史数据&#xff08;通过belief tracker建模&#xff09;和数据库查询结果&#xff08;通过Database Operator得到结果&#xff09;作为支撑。 Abstract 教会机器完成与人自然交流的任务是充满挑战…

测试分层:减少对全链路回归依赖的探索!

引言&#xff1a;测试分层与全链路回归的挑战 在软件开发和测试过程中&#xff0c;全链路回归测试往往是一个复杂且耗费资源的环节&#xff0c;尤其在系统庞大且模块众多的场景下&#xff0c;全链路测试的集成难度显著提高。而“测试分层”作为一种结构化的测试方法&#xff0…

融合虚拟化与容器技术,打造灵活又安全的AI算力服务

随着人工智能技术的不断进步&#xff0c;AI企业在迅速推进大模型业务时&#xff0c;往往会倾向于采用容器化的轻量部署方案。相较于传统的虚拟机部署&#xff0c;容器化在快速部署、资源利用、环境一致性和自动化编排等方面具备显著优势。 然而&#xff0c;容器技术所固有的隔…

协程3 --- golang的协程调度

文章目录 单进程时代多进程/线程时代协程时代内核级线程模型&#xff08;1&#xff1a;1&#xff09;用户级线程模型&#xff08;N&#xff1a;1&#xff09;两级线程模型CMP&#xff08;M&#xff1a;N&#xff09;GM模型 GMP模型 单进程时代 描述&#xff1a;每一个程序就是一…

微服务透传日志traceId

问题 在微服务架构中&#xff0c;一次业务执行完可能需要跨多个服务&#xff0c;这个时候&#xff0c;我们想看到业务完整的日志信息&#xff0c;就要从各个服务中获取&#xff0c;即便是使用了ELK把日志收集到一起&#xff0c;但如果不做处理&#xff0c;也是无法完整把一次业…

【原创】java+ssm+mysql收纳培训网系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…