十一、快速入门go语言之接口和反射

文章目录

    • 接口
      • [:one: 接口基础](#:one: 接口基础)
      • [:two: 接口类型断言和空接口](#:two: 接口类型断言和空接口)
        • [:star2: 空接口实现存储不同数据类型的切片/数组](#:star2: 空接口实现存储不同数据类型的切片/数组)
        • [:star2: 复制切片到空接口切片](#:star2: 复制切片到空接口切片)
        • [:star2: 类型断言](#:star2: 类型断言)
    • 反射

📅 2024年5月9日

📦 使用版本为1.21.5

接口

十、Java类的封装和继承、多态 - 七点半的菜市场 (tanc.fun) Java的接口

ps: 我感觉Go的接口和类方法两个就很模糊

1️⃣ 接口基础

⭐️ 接口就是一些未实现功能的集合(我是这样理解的),为了实现多态(就是多状态)

go 复制代码
type PersonIF interface {
	Status() //人类的状态
	Skills() string //会的技能
}

⭐️ 在Go语言中接口可以有值,它的值默认是nil,接口本质上是一个指针,一个类型如果实现了接口中的所有方法,那么这个类实现的方法就会使用指针自动指向接口的方法

⚠️ 注意是所有抽象方法,如果有一个没实现都会报错

⭐️ 类型不需要使用什么关键字来表面实现了这个接口,多个类型可以同时实现一个接口,一个类型也可以实现多个接口

go 复制代码
type PersonIF interface {
	Status()
	Skills()
}

type WorkIF interface {
	Class()
	Income()
}

type Person struct { //人的基本
	Name string
	age  int
	sex  string
}

/*
	Person实现PersonIF接口
*/
func (this *Person) Status() {
	println("Status")
}

func (this *Person) Skills() {
	println("Skills")
}

/*
	Person实现WorkIF接口
 */
func (this *Person) Class() {
	println("Class")
}	
func (this *Person) Income() {
	println("Income")
}

⭐️ 调用则和Java差不多,这里可以直接使用Interfer来创建一个变量调用,然后就可以调用实现interfer内的方法

go 复制代码
type PersonIF interface {
	Status()
	Skills()
}

type WorkIF interface {
	Class()
	Income()
}

type Person struct { //人的基本
	Name string
	age  int
	sex  string
}

/*
Person实现PersonIF接口
*/
func (this *Person) Status() {
	println("Status")
}

func (this *Person) Skills() {
	println("Skills")
}

/*
Person实现WorkIF接口
*/
func (this *Person) Class() {
	println("Class")
}
func (this *Person) Income() {
	println("Income")
}



//Person类自己的方法
func (this *Person) GetTest() {
	println("GetTest")
}


func main() {
	p1 := Person{"张三", 18, "男"} //创建一个对象
	pIn := PersonIF(&p1) //实现接口
	pIn.Status() //调用接口的方法,接口无法调用类自己的方法,也就是无法调用GetTest
	pIn.Skills()
    p1.GetTest()
    fmt.Println(pIn)  //接口变量包含了接受实列的值和指向对应方法表的指针,这里输出的是实列的值
}

⭐️ 接口也可以内嵌接口

GO 复制代码
type PersonIF interface {
	Status()
	Skills()
	WorkIF
}

type WorkIF interface {
	Class()
	Income()
}
func main() {
	p1 := Person{"张三", 18, "男"} //创建一个对象
	pIn := PersonIF(&p1)        //实现接口
	pIn.Class()			//可以调用嵌套接口中的实现
	fmt.Println(pIn)
}

2️⃣ 接口类型断言和空接口

⭐️ Go语言有个万能的类型通配符,是一个空接口interfer{},因为在Go中任何接口类型都实现了空接口,类似于Java中的Object

⭐️ 每个空接口变量在内存中占据两个字长,一个是用来存储包含的类型,一个是存储数据或者指向数据的指针

GO 复制代码
func main() {
	Test(1)
	Test("abc")
	Test(1.111)
}

func Test(i interface{}) { //空类型
	fmt.Println(i)
}

//输出:
1
abc  
1.111
🌟 空接口实现存储不同数据类型的切片/数组

⭐️ 直接使用type给空接口一个别名类型,然后创建这个接口别名类型的切片/数组(真的太聪明了这个办法)

go 复制代码
package main

import "fmt"

type Empty interface{}

type Vector struct {
	e []Empty
}

func (this *Vector) NewInit() { //初始化切片
	this.e = make([]Empty, 5)
}

func (this *Vector) Set(i int, a interface{}) { //将元素插入指定索引
	this.e[i] = a
}
func (this *Vector) Get(i int) { //获取指定索引元素并输出
	fmt.Println(this.e[i])
}

func main() {
	var v Vector
	v.NewInit() //初始化
	v.Set(0, 22) //整数
	v.Set(1, "222") //string
	v.Set(2, 13.14) //float32
	v.Get(0)
	v.Get(1)
	v.Get(2)
}

//输出:
22
222
13.14
🌟 复制切片到空接口切片

⭐️ 如果你需要将切片复制到一个空接口切片中需要通过for-range,不能直接传递

go 复制代码
func main() {
	dataSlice := make([]int, 0)
	dataSlice = append(dataSlice, 1, 2, 3, 4, 5, 6)
	emptySlice := make([]interface{}, 10)
	for i, j := range dataSlice {
		emptySlice[i] = j
	}
	fmt.Println(emptySlice)
}

⭐️ 一个接口的值是可以赋值给另外一个接口变量,只要底层类型实现了必要的方法

🌟 类型断言

⭐️ 如果变量是一个接口变量,则直接可以使用断言机制,直接就是接口变量.(类型)即可,注意一定要是接口变量

go 复制代码
func main() {
	Test(1)
	Test("abc")
}

func Test(i interface{}) { //空类型

	if value, ok := i.(string); ok {
		fmt.Printf("值: %v 是string\n", value)
	} else {
		fmt.Printf("值: %v 不是string\n", i)
	}
}

⭐️ 还有一种是type-switch

go 复制代码
func main() {
	Test(1)
	Test("abc")
}

func Test(i interface{}) { //空类型
	switch v := i.(type) {
	case string:
		fmt.Printf("值 %v 是string\n", v)
	case int:
		fmt.Printf("值 %v 是int\n", v)
	case float32:
		fmt.Printf("值 %v 是float32\n", v)
	default:
		fmt.Printf("我不想学习\n")
	}
}

反射

Go 语言反射的实现原理 | Go 语言设计与实现 (draveness.me) 可以好好看看

⭐️ Go语言的反射机制也是通过空接口来实现的,在reflect包中

⭐️ 反射提供了两个简单的函数实现一个是TypeOf(获取类型信息),一个是ValueOf(获取数据的运行时表示)

它们两个对应了两个不同的接口分别是TypeValue

⭐️ TypeOf返回的是一个Type接口对象,所以说它可以使用Type方法

它接受一个any类型的值

type any = interface{} //空接口
go 复制代码
// TypeOf函数返回给定参数i的类型。
// 
// 参数:
// i - 任意类型的值。
// 
// 返回值:
// Type - 表示参数i类型的类型值。
func TypeOf(i any) Type {
	// 将i转换为emptyInterface类型,以获取其动态类型信息。
	eface := *(*emptyInterface)(unsafe.Pointer(&i))

	// 使用noescape函数确保i的指针不会逃逸,这样做是为了避免i的生命周期被延长。
	// 这是基于Value.typ字段的注释中提到的安全理由。
	return toType((*abi.Type)(noescape(unsafe.Pointer(eface.typ))))
}

⭐️ 在Type接口中内定义了很多方法

go 复制代码
type Type interface {
	// 基本属性
	Align() int           // 返回类型值对齐所需的字节数。
	FieldAlign() int      // 返回类型作为结构体字段时的对齐要求。

	// 方法相关
	Method(int) Method    // 获取方法集合中的第i个方法。
	NumMethod() int       // 返回方法总数。
	MethodByName(string) (Method, bool) // 通过名称查找方法。

	// 类型基本信息
	Name() string        // 返回类型名。
	PkgPath() string     // 返回类型所在的包路径。
	Size() uintptr       // 返回类型的大小。
	String() string      // 返回类型表示的字符串。
	Kind() Kind          // 返回类型的基本类别。

	// 类型兼容性
	Implements(Type) bool    // 检查是否实现某个接口。
	AssignableTo(Type) bool // 检查值是否可直接赋值给另一类型。
	ConvertibleTo(Type) bool // 检查值是否可通过类型转换匹配另一类型。
	Comparable() bool       // 指示类型值是否可比较。

	// 特定类型特有操作
	Elem() Type             // 对数组/切片/指针/映射/通道,返回元素类型。
	ChanDir() ChanDir        // 对通道类型,返回通信方向。
	IsVariadic() bool       // 对函数类型,检查是否可变参数。
	Key() Type              // 对映射类型,返回键的类型。
	Len() int               // 对数组类型,返回其长度。

	// 结构体操作
	Field(int) StructField  // 获取结构体的第i个字段信息。
	NumField() int         // 返回结构体字段数量。
	FieldByIndex([]int) StructField // 通过嵌套索引获取字段信息。
	FieldByName(string) (StructField, bool) // 通过字段名获取字段信息。
	FieldByNameFunc(func(string) bool) (StructField, bool) // 通过匹配函数查找字段。

	// 函数类型操作
	In(int) Type           // 获取函数的第i个输入参数类型。
	NumIn() int            // 返回函数输入参数数量。
	Out(int) Type          // 获取函数的第i个输出参数类型。
	NumOut() int           // 返回函数输出参数数量。

	// 内部方法,用户通常无需直接调用
	common(), uncommon() *internal // 返回底层实现相关的数据结构。
}

⭐️ ValueOf 放回一个Value对象,接受的也是一个任意类型的值

go 复制代码
// ValueOf 是一个将任意类型转换为 Value 类型的函数。
// 参数 i 为任意类型的值,表示需要转换的值。
// 返回值为 Value 类型,表示转换后的值。
// 如果输入值 i 为 nil,则返回一个空的 Value。
func ValueOf(i any) Value {
	// 检查输入值是否为 nil,如果是则返回空的 Value
	if i == nil {
		return Value{}
	}

	// 如果满足特定条件(go121noForceValueEscape 为 false),则标记 i 为逃逸变量
	if !go121noForceValueEscape {
		escapes(i)
	}

	// 将输入值 i 解包为 Value 类型并返回
	return unpackEface(i)
}

⭐️ 在Value中也定义了一些方法,反射包中 reflect.Value 的类型与 reflect.Type 不同,它被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法

go 复制代码
type Value struct {
	// 内部结构未公开,实际包含值和类型等信息
}

// 实例化Value的方法通常通过reflect.ValueOf或reflect零值的间接操作获得

// Kind 返回此Value所持有的值的类型种类。
func (v Value) Kind() Kind

// Type 返回此Value所持有的值的具体类型信息。
func (v Value) Type() Type

// Bool 获取bool类型的值,如果Value不是bool类型则会panic。
func (v Value) Bool() bool

// Int 获取整数值,类型必须是整数类型,否则会panic。
func (v Value) Int() int64

// Float 获取浮点数值,类型必须是浮点数类型,否则会panic。
func (v Value) Float() float64

// String 获取字符串值,类型必须是string,否则会panic。
func (v Value) String() string

// Interface 转换Value为interface{}类型,几乎所有类型都可以通过此方法获取。
func (v Value) Interface() interface{}

// Set 设置Value的值,新值必须是可设置的并且类型匹配。
func (v Value) Set(x Value) 

// SetBool 设置bool类型的值,Value必须是可设置的bool类型。
func (v Value) SetBool(x bool)

// SetInt 设置整数值,Value必须是可设置的整数类型。
func (v Value) SetInt(x int64)

// SetFloat 设置浮点数值,Value必须是可设置的浮点数类型。
func (v Value) SetFloat(x float64)

// SetString 设置字符串值,Value必须是可设置的string类型。
func (v Value) SetString(x string)

// Call 对于函数或方法Value,执行调用并返回结果。
func (v Value) Call(in []Value) []Value

// Elem 如果Value是一个指针,返回它指向的值的Value;否则返回自身。
func (v Value) Elem() Value

// Field 获取结构体Value的第i个字段的Value。
func (v Value) Field(i int) Value

// FieldByName 获取结构体Value的名为name的字段的Value。
func (v Value) FieldByName(name string) Value

// Index 对于数组、slice或map的Value,返回索引i处的元素Value。
func (v Value) Index(i int) Value

// MapIndex 对于map的Value,返回key对应的元素Value。
func (v Value) MapIndex(key Value) Value

// CanSet 返回此Value是否可被设置,即是否可以更改其底层值。
func (v Value) CanSet() bool

// IsZero 判断Value的底层值是否为零值。
func (v Value) IsZero() bool

⭐️ Type方法实列

go 复制代码
type Person struct {
	Name string
	Age  int
	Sex  string
}

func (this *Person) toString() string {
	return fmt.Sprintf("Nmae: %v,Age: %v,Sex: %v", this.Name, this.Age, this.Sex)
}

func main() {
	p1 := Person{"张三", 18, "男"}
	p1_demo := reflect.TypeOf(p1)             //获取类型信息
	fmt.Println(p1_demo.Name())               //输出类名
	fmt.Println(p1_demo.Size())               //输出类型大小
	for i := 0; i < p1_demo.NumField(); i++ { //放回结构体字段的个数,然后输出遍历
		field := p1_demo.Field(i) //获取字段
		fmt.Println(field.Name, field.Type, field.Offset)
	}
	for i := 0; i < p1_demo.NumMethod(); i++ { //放回结构体方法的个数,然后输出遍历
		method := p1_demo.Method(i) //获取方法
		fmt.Println(method.Name, method.Type)
	}
	//数组
	p2 := [1]Person{Person{"张三", 18, "男"}}
	p2_demo := reflect.TypeOf(p2)
	fmt.Println(p2_demo.Len()) //输出数组长度

}

⭐️ Value方法实现,可以使用ValueOf方法来修改值

go 复制代码
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age  int
	Sex  string
}

func main() {
	// 使用指针来确保可以通过反射修改值
	p1 := Person{"张三", 18, "男"}
	fmt.Println(p1)

	// 获取p1的反射值
	p1_demo := reflect.ValueOf(&p1)
	// 由于p1是指针,我们先获取其指向的值的反射值
	p1_val := p1_demo.Elem()

	// 检查是否可以设置值
	if p1_val.CanSet() {
		// 创建一个新的Person值并通过反射设置
		newPerson := Person{"李四", 20, "女"}
		p1_val.Set(reflect.ValueOf(newPerson))
	} else {
		fmt.Println("无法设置值")
	}

	// 打印修改后的值
	fmt.Printf("%#v\n", p1)     // 使用%#v格式化输出结构体细节
	fmt.Println(p1_demo.Kind()) // 输出类型信息
}

欢迎关注我,继续探讨技术,如果觉得写的不错动动小手点个小赞,如果觉得我还有哪些不足可以私信哦

相关推荐
数据小小爬虫1 分钟前
如何利用Python爬虫获取商品历史价格信息
开发语言·爬虫·python
Gao_xu_sheng3 分钟前
Java程序打包成exe,无Java环境也能运行
java·开发语言
NiNg_1_2348 分钟前
Python的sklearn中的RandomForestRegressor使用详解
开发语言·python·sklearn
谢家小布柔12 分钟前
java中的继承
java·开发语言
黑色叉腰丶大魔王12 分钟前
《基于 Python 的网页爬虫详细教程》
开发语言·爬虫·python
l1384942745118 分钟前
Java每日一题(2)
java·开发语言·游戏
晓纪同学25 分钟前
QT创建一个模板槽和信号刷新UI
开发语言·qt·ui
WANGWUSAN6631 分钟前
Python高频写法总结!
java·linux·开发语言·数据库·经验分享·python·编程
forNoWhat40 分钟前
java小知识点:比较器
java·开发语言
坐井观老天1 小时前
在C#中使用资源保存图像和文本和其他数据并在运行时加载
开发语言·c#