首页 > 其他分享 >09_接口

09_接口

时间:2023-10-08 13:23:49浏览次数:32  
标签:fmt 09 接口 reflect func Println type

参考:

接口深度解析

1. 接口是什么?

Go 语言中的接口实现了多态的思想,接口是一组方法的集合,但是这些方法不包含实现的代码。接口是一种类型!

通过以下格式来定义接口:

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

接口具有以下特点:

  • 类型不需要显式声明它实现了某个接口:接口被隐式地实现,多个类型可以实现同一个接口;
  • 实现某个接口的类型可以有其它方法;
  • 一个类型可以实现多个接口;
  • 接口类型可以包含一个实例的引用,该实例的类型实现了接口。
  • 接口分为值接收者接口和指针接收者接口,区别在于可以将值或者指针赋值给值类型接口变量;而不能将值赋值给指针类型接口变量。

Go接口与其它编程语言的差异:

  • 接口为非入侵性,不像Java语言需要用implements申请依赖;
  • 所有接口的定义可以包含在接口使用者包内,而不像Java需要定义在一个单独的包中;
type Shaper interface {
	Area() float32
}
type Square struct {
	side float32
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

type Rectangle struct {
	length, width float32
}

func (re *Rectangle) Area() float32 {
	return re.length * re.width
}

func Testinter01() {
	r := &Rectangle{5, 3}
	q := &Square{5}
	shapes := []Shaper{r, q}
	fmt.Println("Looping through shapes for area ...")
	for n, _ := range shapes {
		fmt.Println("Shape details: ", shapes[n])
		fmt.Println("Area of this shape is: ", shapes[n].Area())
	}
}

接口可以重复嵌套,比如 File 接口包含了 ReadWrite 接口和 Lock 接口的方法

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type Lock interface {
    Lock()
    Unlock()
}

type File interface {
    ReadWrite
    Lock
    Close()
}

2. 如何检测和转换接口变量的类型

一般我们通过 类型断言 来检测某个时刻变量 varI 是否包含类型 Tv, ok := varI.(T),注意这里的 varI 变量必须是一个接口变量。

如果转换合法,v 是 varI 转换到类型 T 的值,ok 会是 true; 否则 v 是类型 T 的零值,ok 是 false。

type Square2 struct {
	side float32
}

type Circle2 struct {
	radius float32
}

type Shaper2 interface {
	Area() float32
}

func (sq *Square2) Area() float32 {
	return sq.side * sq.side
}

func (ci *Circle2) Area() float32 {
	return ci.radius * ci.radius
}

func TestAssest() {
	var areaIntf Shaper2
	sq1 := new(Square2)
	sq1.side = 5
	areaIntf = sq1

	if t, ok := areaIntf.(*Square2); ok {
		fmt.Printf("The type of areaIntf is: %T\n", t)
	}
	if u, ok := areaIntf.(*Circle2); ok {
		fmt.Printf("The type of areaIntf is: %T\n", u)
	} else {
		fmt.Println("areaIntf does not contain a variable of type Circle")
	}
}

接口变量的类型也可以使用 type-switch 来进行类型检测,输出如下:

switch t := areaIntf.(type) {
case *Square:
    fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
    fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
    fmt.Printf("nil value: nothing to check?\n")
default:
    fmt.Printf("Unexpected type %T\n", t)
}

3. 类型断言解析

参考: Go的类型断言解析

go接口详解

通常我们对于一个接口值的动态类型是不确定的,比如方法的形参为接口类型时,此时就需要我们检验它是否符合我们需要的类型。类型断言是使用在一个接口值上的操作。

类型断言语法:x.(T),这里 x 表示一个接口的类型,T表示一个类型(可以是接口类型也可以是其它具体类型)。一个类型断言检查一个接口对象 x 的动态类型是否和断言类型 T 相匹配。

1. 接口类型值

在讲解前先明确一些概念,变量的类型在声明时指定而且不能改变,成为静态类型;接口的静态类型就是接口本身,接口没有静态值,它指向的是动态值,接口类型的变量存储的是实现接口类型的值。该值就是接口的动态值,实现接口的类型就是接口的动态类型。接口的动态类型又称为具体类型,当我们访问接口类型时,返回的是接口底层的动态值的类型。

type Iname interface {
	Mname()
}

type St1 struct {}
func (St1) Mname() {}
type St2 struct {}
func (St2) Mname() {}

func main() {
	var i Iname = St1{}
	fmt.Printf("type is %T\n",i)
	fmt.Printf("value is %v\n",i)
	i = St2{}
	fmt.Printf("type is %T\n",i)
	fmt.Printf("value is %v\n",i)
}
// 接口变量i的静态类型是 lname,在声明时就已经指定不能改变。动态类型不固定,第一次分配完毕后,动态类型为St1,第二次分配完后,为St2。
type Iname interface {
	Mname()
}
type St struct {}
func (St) Mname() {}
func main() {
	var t *St
	if t == nil {
		fmt.Println("t is nil")
	} else {
		fmt.Println("t is not nil")
	}
	var i Iname = t
	fmt.Printf("%T\n", i)
	if i == nil {
		fmt.Println("i is nil")
	} else {
		fmt.Println("i is not nil")
	}
	fmt.Printf("i is nil pointer:%v",i == (*St)(nil))
}
/* 输出
t is nil
*main.St
i is not nil
i is nil pointer:true */

i 的静态类型为 Iname,其动态类型为 *St,动态值为 nil,而一个接口当且仅当动态值和动态类型都为nil 时,接口类型才为nil。

2. 类型断言

由于类型断言 x.(T) 的 T 的类型不确定,所以分以下两种情况:

  • 如果断言类型T是一个具体的类型,类型断言 x.(T) 就检查 x 的动态类型是否和 T的类型相同

如果这个检查成功了,类型断言的结果就是一个类型为T的对象,该对象的值为接口变量x的动态值。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。

如果这个检查失败了,接下来会抛出 panic,除非用两个变量来接收检查结果,比如 f,ok := w.(*os.File)

  • 如果断言类型T是一个接口类型,类型断言 x.(T) 就检查 x 的动态类型是否满足 T接口

如果这个检查成功了,则检查结果的接口值的动态类型和动态值不变,但是该接口值的类型被替换为接口类型T。换句话说,对一个接口类型的断言改变了这个类型的表达方式,改变了可以获取方法的集合(变得更大),但是它保护了接口值内部的动态类型和动态值部分。

如果操作失败,也会抛出panic,也需要上述两个变量进行接收。

type Tester interface {
	getName() string
}
type Tester2 interface {
	printName()
}
type Person struct {
	name string
}

func (p Person) getName() string {
	return p.name
}
func (p Person) printName() {
	fmt.Println(p.name)
}

func check(t Tester) {
	if f, ok1 := t.(Person); ok1 {
		fmt.Printf("%T\n%s\n", f, f.getName())
	}
	if t, ok2 := t.(Tester2); ok2 {
		fmt.Printf("%T\n", t)
		t.printName()
	}
}

func Test() {
	var t Tester
	t = Person{"xiaohua"}
	fmt.Printf("%T\n", t)
	check(t)
}

/* 输出
interfacelearn.Person
interfacelearn.Person
xiaohua              
interfacelearn.Person
xiaohua  */

4. 使用方法集与接口

我们知道,在作用于变量上的方法是不区分变量到底是指针还是值的,因为 Go 可以自动进行解引用和取变量地址。

type List []int

type Appender interface {
	Append(int)
}

type Lener interface {
	Len() int
}

func (l *List) Append(val int) {      //需要接收一个指针变量,指针方法
	*l = append(*l, val)
}

func (l List) Len() int {             //需要接收一个值变量,值方法
	return len(l)
}

func CountInto(a Appender, start, end int) {
	for i := start; i <= end; i++ {
		a.Append(i)
	}
}

func LongEnough(l Lener) bool {
	return l.Len()*10 > 42
}

func Test111() {
	var lst List                     // 作为值出现
	if LongEnough(lst) {
		fmt.Printf("- lst is long enough\n")
	}

	pst := new(List)                 // 作为指针出现
	CountInto(pst, 1, 10)
	if LongEnough(pst) {
		fmt.Printf("- plst is long enough\n")
	}
}

上述例子,在 lst 上调用 CountInto 方法时会导致一个编译器错误,因为 CountInto 需要一个 Appender,而它的方法 Append 定义在指针上。在 lst 上调用 LongEnough 方法是可以的,因为 Len 定义在值上。

pst 作为指针,既可以用于值方法、也可以用于指针方法,值方法比如 LongEnough,这里指针会被自动解引用。

总结:

  • 指针方法可以通过指针调用
  • 值方法可以通过值调用
  • 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
  • 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址

5. 空接口

空接口或者最小接口不包含任何方法,对实现不做任何要求:type Any interface{},其它任何类型都实现了空接口。

1. 构建通用类型或者包含不同类型变量的数组

type Element interface{}    // 定义空接口

type Vector struct {        // 存储空接口的容器
    a []Element
}

func (p *Vector) At(i int) Element {
    return p.a[i]
}

func (p *Vector) Set(i int, e Element) {
    p.a[i] = e
}

Vector 中的所有数据类型都是 Element 类型,所以 Vector 可以存储任何的数据类型。

复制数据切片到空接口切片时需要采用 for-range 来逐个显式地复制:

var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for ix, d := range dataSlice {
    interfaceSlice[i] = d
}

2. 通用类型的节点数据结构

模拟二叉树的结构

type Node struct {
	le   *Node
	data interface{}
	ri   *Node
}

func NewNode(left, right *Node) *Node {
	return &Node{left, nil, right}
}

func (n *Node) SetData(data interface{}) {
	n.data = data
}

func TestTree() {
	root := NewNode(nil, nil)
	root.SetData("root Node")
	a := NewNode(nil, nil)
	a.SetData("left Node")
	b := NewNode(nil, nil)
	b.SetData("right Node")
	root.le = a
	root.ri = b
	fmt.Printf("%v\n", root)
}

6. 使用技巧

  • Go倾向于使用小的接口定义,很多接口只包含一个方法;
  • 较大的接口定义,可以由多个小接口组合而成;
  • 只依赖于必要功能的小接口;
image-20221006110904759

7. 反射

1. 通过反射查看变量类型和值

Go 中的 reflect 反射包常用来返回某个变量的类型和值。

reflect.TypeOf(x) 用来返回被检查对象的类型;reflect.ValueOf(x) 用来返回被检查对象的值;

    func TypeOf(i interface{}) Type
    func ValueOf(i interface{}) Value
func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
    v := reflect.ValueOf(x)
    fmt.Println("value:", v)
    fmt.Println("type:", v.Type())
    fmt.Println("kind:", v.Kind())
    fmt.Println("value:", v.Float())     // 这里由于已知变量为float类型,同理可以使用Int()、Bool()、String()
    fmt.Println(v.Interface())
    fmt.Printf("value is %5.2e\n", v.Interface())   // v.Interface()  返回对应变量的值
    y := v.Interface().(float64)
    fmt.Println(y)
}

/* output:
type: float64
value: <float64 Value>
type: float64
kind: float64
value: 3.4
3.4
value is 3.40e+00
3.4
*/

2. 通过反射修改变量的值

可以通过 v := reflect.ValueOf(x) 获取反射Value 类型v,然后对于 v 再采取 v.setFloat(xxx) 去修改其值。

但并不是所有属性都可以对其值进行修改,可以使用 v.CanSet()来测试该属性字段是否可以设置。

如果我们要去修改某个属性字段的值(这个字段必须首字母大写),那么不能传递 x ,而需要传递 x 的地址 &x,使用 v := reflect.Valueof(&x),另外还需要通过 v = v.Elem() 来设置属性字段可修改。

func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    // setting a value:
    // v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
    fmt.Println("settability of v:", v.CanSet())
    v = reflect.ValueOf(&x) // Note: take the address of x.
    fmt.Println("type of v:", v.Type())
    fmt.Println("settability of v:", v.CanSet())
    v = v.Elem()
    fmt.Println("The Elem of v is: ", v)
    fmt.Println("settability of v:", v.CanSet())
    v.SetFloat(3.1415) // this works!
    fmt.Println(v.Interface())
    fmt.Println(v)
}

/* Output:
settability of v: false
type of v: *float64
settability of v: false
The Elem of v is:  <float64 Value>
settability of v: true
3.1415
<float64 Value>
*/
type T struct {
    A int
    B string
}

func main() {
    t := T{23, "skidoo"}
    s := reflect.ValueOf(&t).Elem()
    typeOfT := s.Type()
    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
    }
    s.Field(0).SetInt(77)
    s.Field(1).SetString("Sunset Strip")
    fmt.Println("t is now", t)
}

/* Output:
0: A int = 23
1: B string = skidoo
t is now {77 Sunset Strip}
*/

反射中常用的方法:

  • v := reflect.ValueOf() 获取类型对象v后,如果要获取属性字段的字段名称:v.Type().Field(xxx).Name;获取字段值:v.Field().interface()

  • 更改某个属性字段值:v.Field(xxx).Setxxx(xxx)

  • 通过反射调用对象方法:v.Method(xxx).Call(args)

  • 判断一个类型是否实现了某个接口:

    通过申明一个指向 nil 的指针来判断,实现如下:

type ItemType struct {
	a string
}

type ItemType2 struct {
	b string
}

type A interface {
	FuncA() bool
}

func (obj *ItemType) FuncA() bool {
	fmt.Println(obj.a)
	return true
}

func main() {
	objA := &ItemType2{b: "aaa"}
	elemValueType := reflect.TypeOf(objA)
	tableNameAbleType := reflect.TypeOf((*A)(nil)).Elem()
	if elemValueType.Implements(tableNameAbleType) {
		fmt.Println("don't implement")
	}
}

3. reflect 包方法总结

package main
import (
	"fmt"
	"reflect"
	"time"
)
type OutType struct { // tags
	field1 bool   "This is field1's tag"
	Field2 string "This is field2's tag"
	Field3 int    "This is field3's tag"
	field4 *InType "This is field4's tag"
}
type InType struct {
	a int "this is tag of a"
	b int "this is tag of b"
}
func (o OutType)Function1(){
	fmt.Println("this is function1,it's unexported.")
}
func (o OutType)Function2(arg1 string,arg2 *int)(res1 int,res *string){
	fmt.Println("this is function2,it's exported.")
	return 0,nil
}
func (o *OutType)Function3(a,b int)(InType){
	fmt.Println("this is function2,it's exported.")
	return InType{a,b}
}
func main() {
	var obj interface{}=OutType{true,"hello",1,&InType{1,2}}
	var objPtr interface{}=&OutType{true,"hello",1,&InType{1,2}}
 
	/
	//1、Type类型:包含了一个结构的类型信息(各数据成员和方法信息),不包含实际值,接口中包含了大量类型信息读取方法
	fmt.Println(reflect.TypeOf(obj))   //main.OutType
	fmt.Println(reflect.TypeOf(objPtr)) //*main.OutType
 
	objType:=reflect.TypeOf(obj)
	fmt.Println(objType.Size())   //40
	fmt.Println(objType.Kind())   //struct    kind()总显示底层类型
	fmt.Println(objType.Name())   //OutType
	//用指针初始化Type实例
	objPtrType:=reflect.TypeOf(objPtr)
	fmt.Println(objPtrType.Size())  //8
	fmt.Println(objPtrType.Kind())  //ptr
	fmt.Println(objPtrType.Name())  //空值
	/
	//2、Value类型:包含了Type类型和对应的实际值,是reflect包中代表一个实例的类型
	//   ValueOf returns a new Value initialized to the concrete value stored in the interface
	//   源码注释:返回一个根据输入的接口中存储的值初始化的新Value类型,其包含的Type与输入接口相同
	//   可以理解为将输入的接口初始化为reflect包中的实例
	objPtrValue:=reflect.ValueOf(objPtr)
	objPtr.(*OutType).field1=false   //对指针指向属性的修改,会同时修改之前生成的Value对象
	fmt.Println(objPtr)       //&{false hello 1 0xc00000a0e0}
	fmt.Println(objPtrValue)   //&{false hello 1 0xc00000a0e0}
	objValue:=reflect.ValueOf(obj)    //用值对象初始化Value实例,只复制了一份值,相当于按值传递
	obj=OutType{false,"hello",1,&InType{1,2}}
	fmt.Println(obj)   //{false hello 1 0xc00000a140}
	fmt.Println(objValue)  //{true hello 1 0xc00000a0d0}
	//同样可以从Value实例中获得对应的Type信息
	typeFromobjValue:=objValue.Type()
	fmt.Println(typeFromobjValue.Name())  //OutType
	/
	//3、对于使用包含指针型值的接口初始化的Value和Type,无法直接获得指向类型的信息
	//   可以通过一下方式,提取valuel类型指针指向的值,到一个新的value类型中,此时就获得了非指针类型
	//   在输入不确定是否为指针型的情况下,可以使用这种方法确保操作的是非指针型实例
	fmt.Println(objPtrType.Name())   //空
	elem1:=reflect.ValueOf(objPtr).Elem()
    elem2:=reflect.Indirect(reflect.ValueOf(objPtr))
    fmt.Println(elem1.Type().Name())   //OutType
	fmt.Println(elem2.Type().Name())   //OutType
	fmt.Println(reflect.ValueOf(objPtr).Elem().Type().Name())   //OutType
	fmt.Println(reflect.Indirect(reflect.ValueOf(objPtr)).Type().Name())   //OutType
	/
	//4、同样的,reflect包 支持将非指针类型转化为指针类型进行处理
	ptrValue:=reflect.PtrTo(objType)
	fmt.Println(ptrValue.Kind())   //ptr
	fmt.Println(ptrValue)          //*main.OutType
	/
	//5、使用type类型:获得类型内各字段信息Field实例,其结构如下:
	//type StructField struct {
	//	Name string   字段的名称
	//	PkgPath string  标志着一个非导出(首字母小写)字段的包路径,如果是导出量,该值为空(因此可以用于判断是否为导出量)
	//	Type      Type      字段类型,与外部的的实例类型没什么不同,因此可以做域中数据的进一步判断
	//	Tag       StructTag  字段后标签的字符串
	//	Offset    uintptr   // offset within struct, in bytes
	//	Index     []int     // index sequence for Type.FieldByIndex
	//	Anonymous bool     是否是一个嵌入的(匿名的)域
	//}
	for i:=1;i<=objType.NumField();i++{
		field:=objType.Field(i-1)
		fType:=field.Type
		fName:=field.Name
		fPkgPath:=field.PkgPath
		fTag:=field.Tag
		fmt.Printf("Field%v  type:%v  name:%v  PkgPath:%v  tag:%v \n",i,fType,fName,fPkgPath,fTag)
		//Field1  type:bool  name:field1  PkgPath:main  tag:This is field1's tag
		//Field2  type:string  name:Field2  PkgPath:  tag:This is field2's tag
		//Field3  type:int  name:Field3  PkgPath:  tag:This is field3's tag
		//Field4  type:*main.InType  name:field4  PkgPath:main  tag:This is field4's tag
	}
	//得到Field的Type字段,就可以查看类型内类型的具体信息
	intype:=objType.Field(3).Type
	fmt.Println(intype)   //*main.InType
	inObj:=intype.Elem()
	fmt.Println(inObj.Field(0).Name)  //a
	//可以通过函数FieldByName(name string) (StructField, bool),用字段名称得到字段类型
	field,_:=objType.FieldByName("field1");
	fmt.Println(field.Tag)   //This is field1's tag
	/
	//6、使用Type类型:获得实例下的方法信息
	//type Method struct {
	//	Name    string  方法名
	//	PkgPath string   包路径名,同样可以被用来判断是否是被导出函数
	//	Type  Type  方法类型
	//	Func  Value 以接收者为第一参数的函数体
	//	Index int   方法下标
	//}
	fmt.Println(objType.NumMethod())   //2
	for i:=1;i<=objType.NumMethod();i++{
		mtd:=objType.Method(i-1)
		mName:=mtd.Name
		mType:=mtd.Type
		mPkgPath:=mtd.PkgPath
		fmt.Printf("Method%v  %v %v %v \n",i,mName,mType,mPkgPath)
		//Method1  Function1 func(main.OutType)
		//Method2  Function2 func(main.OutType, string, *int) (int, *string)
	}
	//与正常方法调用要求相同,以指针作为接收体的方法必需以指针实例调用
	//因此,只有指针化的结构才能反射到第三个函数
	fmt.Println(reflect.PtrTo(objType).Method(2).Name)    //Function3
	/
	//7、对方法类型的处理
	//   方法对应的类型同样实现了Type接口,可以获得其中的字段信息
	mtdType:=objType.Method(1).Type
	fmt.Println(mtdType)   //func(main.OutType, string, *int) (int, *string)
	fmt.Println(mtdType.Kind()) //func
	//同样提供了按方法名获得方法类型的函数
	fun,_:=objType.MethodByName("Function1")
	fmt.Println(fun.Type)//func(main.OutType)
	//在方法中最关键的当然是参数和返回值返回值信息,使用Out和In函数,同样可以得到对应的Type类型
	//值得注意的是,方法的接收者类型被作为第一个参数进行处理
	for i:=0;i<mtdType.NumIn();i++{
		argType:=mtdType.In(i)
		if argType.Kind()==reflect.Ptr{   //对于有可能是指针的量,可以与relect.Ptr进行对比做出判断
			fmt.Println(argType.Elem().Name())
		}else{
			fmt.Println(argType.Name())
		}
	}
	for i:=0;i<mtdType.NumOut();i++{
		resType:=mtdType.Out(i)
		if resType.Kind()==reflect.Ptr{
			fmt.Println(resType.Elem().Name())
		}else{
			fmt.Println(resType.Name())
		}
	}
	/
	//8、除解析一个接口的结构字段和方法外,还可以对注册在结构上的方法进行调用
	//   调用过程中需要注意接收者是否为指针型,被调用函数应当是被导出类型
	//   当然,这个调用过程只能在包含了结构实例值的Value类型上使用,Type类型无法嗲用
	//    func (v Value) Call(in []Value) []Value
	//   参数和返回值都是reflect包中Value型的切片,需要经过转换
	objValue.Method(0).Call(nil)   //函数打印:this is function1,it's unexported.
	res:=objPtrValue.Method(2).Call(
		[]reflect.Value{reflect.ValueOf(1),reflect.ValueOf(2)})//this is function2,it's exported.
	fmt.Println(res[0])  //{1 2}
	//对于Type类型包含的方法,因为没有实际值作为接收者,需要吧传入的第一个参数作为接收者
	function:=objType.Method(0).Func
	function.Call([]reflect.Value{reflect.ValueOf(obj)})//this is function1,it's unexported.
	/
	//9、reflect包提供了方法对Value实例中的字段值做出修改
	//   To modify a reflection object, the value must be settable.
	//   值得注意的是,根据官方描述,为了改变一个反射对象,其值必需是可修改的
	//   这里的可修改值得注意,我们知道,通过输入的接口初始化的Value实例
	//   返回一个根据输入的接口中存储的值初始化的新Value类型(跟按值传递参数一致)
	//   因此,对Value中的值做出修改并没有改变外部接口值,因此并不支持这样做
	//   objValue.Field(1).SetString("hello,world!")
	//   panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
	//   可行的用法是使用Elem()函数将用指针量初始化的Value实例中实际存储的值(相当于引用传递,这里的Elem()相当于一个解引用)
	fmt.Println(objPtrValue.Elem().Field(1))   //hello
	objPtrValue.Elem().Field(1).SetString("hello,world!")
	fmt.Println(objPtrValue.Elem().Field(1))    //hello,world!
	//可以使用CanSet()函数判断是否能够修改值
	fmt.Println(objValue.Field(1).CanSet())   //false
	fmt.Println(objPtrValue.Elem().Field(1).CanSet())  //true
 	///
 	//11、对切片进行的一些操作
 	slice:=[]int{}
 	slice=append(slice,1,2,3,4,5,6,7,8,9)
 	sliceValue:=reflect.ValueOf(&slice).Elem()
	fmt.Println(sliceValue.Len())       //9
	fmt.Println(sliceValue.Cap())       //10
	sliceValue.SetLen(10)
	fmt.Println(sliceValue.Len())       //10
 	fmt.Println(sliceValue.Index(3)) //4
	///
	//11、补充
	//对于kind()返回的底层类型的判断举例
	fmt.Println(objPtrType.Kind()==reflect.Ptr)   //true
	fmt.Println(objType.Kind()==reflect.Struct)   //true
	var m=make(map[int]int)
	fmt.Println(reflect.TypeOf(m).Kind()==reflect.Map) //true
	//reflect包提供func New(typ Type) Value函数,根据输入类型默认初始化一个Value
	// 该Value量包含的是一个指针型实例,也就是New函数和包外的New一样,申请好了内存空间,其中值可修改
	newType:=reflect.New(objType)
	fmt.Println(newType)   //&{false  0 <nil>}
	fmt.Println(newType.Elem().Field(0).CanSet())  //true
	//可以使用Interface()函数将Value值转化回对应的interface
	fmt.Println(objValue.Interface().(OutType).Field2)//hello
	//对于管道值的操作,reflect提供了send函数
	ch:=make(chan int)
	go reflect.ValueOf(ch).Send(reflect.ValueOf(1))
	fmt.Println(<-ch)  //1
	time.Sleep(1e9)
}	

​ 实例:编写一个通用的方法,实现根据 map 结构填充给定对象 obj 的键值对

type Employee struct {
	EmployeeID string
	Name       string `format:"normal"`
	Age        int
}

func (e *Employee) UpdateAge(newVal int) {
	e.Age = newVal
}

type Customer struct {
	CookieID string
	Name     string
	Age      int
}

func TestFillNameAndAge(t *testing.T) {
	settings := map[string]interface{}{"Name": "Mike", "Age": 40}
	e := Employee{}
	if err := fillBySettings(&e, settings); err != nil {
		t.Fatal(err)
	}
	t.Log(e)
	c := Customer{}
	if err := fillBySettings(&c, settings); err != nil {
		t.Fatal(err)
	}
	t.Log(c)
}

// 利用反射为任意obj对象填充setting集合数据
func fillBySettings(obj interface{}, settings map[string]interface{}) error {
	//1.判断obj为指针类型,否则转换为指针类型
	if reflect.TypeOf(obj).Kind() != reflect.Ptr {
		if reflect.TypeOf(obj).Elem().Kind() != reflect.Struct {
			return errors.New("obj type is not a pointer")
		}
	}
	if settings == nil {
		return errors.New("setting is nil.")
	}
	var (
		field reflect.StructField
		ok    bool
	)
	//2.判断settings键值对是否为obj的Field
	for k, v := range settings {
		if field, ok = (reflect.TypeOf(obj)).Elem().FieldByName(k); !ok {  //判断是否存在对应名称的Field
			continue
		}
		if field.Type == reflect.TypeOf(v) {                               //判断Field对象和给定v是否相同
			vstr := reflect.ValueOf(obj)
			vstr = vstr.Elem()
			vstr.FieldByName(k).Set(reflect.ValueOf(v))
		}
	}
	return nil
}
image-20221008210753100

4. 讲的很清楚的反射文章

https://zhuanlan.zhihu.com/p/411313885

8. 通过 golang.unsafe 获取/更改字段值

参考;https://www.jianshu.com/p/2f0f2083376e

unsafe 会直接操作内存,这是一种非常底层的能力,同时由于你可以基于内存做很多事,这也导致它非常不安全。

要想操作内存,我们必须知道你操作的变量(通常就是类的实例)的起始地址,以及各个字段的偏移量,知晓了这些,我们才可以在正确的位置上进行操作。
我们可以通过一些操作获取到我们想要的值。

  • unsafe.Pointer(&x):获取对象 x 的起始地址;
  • unsafe.Offsetof(x.f):获取对象 x 的属性 f 的偏移量;
  • reflect.ValueOf(&x).UnsafePointer():结合反射Value,获取对象 x 的起始地址;
  • reflect.ValueOf(&x.f).UnsafePointer():结合反射Value,获取对象 x 的属性 f 的起始地址;
type Student struct {
    Name   string
    Gender string
    Age    int64
}

func TestOffset(t *testing.T) {
    s := Student{}
    s.Name = "Peter"
    s.Age = 33

    // 获取起始地址,注意使用指针
    startAddr := reflect.ValueOf(&s).UnsafePointer()
    fmt.Println(startAddr)

    // 获取地址
    nameAddr := reflect.ValueOf(&s.Name).UnsafePointer()
    fmt.Println(nameAddr)

    // 获取地址
    genderAddr := reflect.ValueOf(&s.Gender).UnsafePointer()
    fmt.Println(genderAddr)

    // 获取偏移量
    fmt.Println(unsafe.Offsetof(s.Age))
    // 获取地址
    ageAddr := reflect.ValueOf(&s.Age).UnsafePointer()
    fmt.Println(ageAddr)
}

Unsafe 读操作:

  1. 直接读取指针地址
func TestReadMemo(t *testing.T) {
    x := int64(-10086)
    ptr := unsafe.Pointer(&x)

    y := *(*int64)(ptr)  // 注意类型为 int64,你可以试一试将类型改为 uint64 会出现什么情况
    fmt.Println(y)  // -10086
}
  1. 结合反射和 Unsafe 读取

reflect.NewAt(Type, Unsafe.Pointer):获取指定地址,指定类型的 Value值

func TestReadMemoByRef(t *testing.T) {
    x := int64(10086)
    typ := reflect.TypeOf(x)

    y := reflect.NewAt(typ, unsafe.Pointer(&x)).Elem()
    fmt.Println(y.Interface()) // 10086

    x = 123
    fmt.Println(y.Interface()) // 123
}

Unsafe 写操作:

  1. 通过指针直接修改
func TestWri(t *testing.T) {
    x := int64(10086)
    ptr := unsafe.Pointer(&x)

    *(*int64)(ptr) = 123
    fmt.Println(x)
}
  1. 利用反射
func TestWriRef(t *testing.T) {
    x := int64(10086)
    y := int64(666)

    newX := reflect.NewAt(reflect.TypeOf(x), unsafe.Pointer(&x)).Elem()
    if newX.CanSet() {
        fmt.Println("ok")  // ok
        newX.Set(reflect.ValueOf(y))
    }

    fmt.Println(x)  // 666
}

Unsafe.Pointer 和 uintptr 的区别:

  • unsafe.Pointer:代表指针,并且 Pointer 会被 GC 管理
  • uintptr:也可以代表指针,但是它的底层是一个 uint,不会被 GC 管理
func TestPtr(t *testing.T) {
    s := make([]int, 0, 1)
    p1 := unsafe.Pointer(&s)
    p2 := uintptr(p1)

    s = append(s, 1)
    fmt.Println(*(*[]int)(p1))
    fmt.Println(*(*[]int)(unsafe.Pointer(p2)))

    s = append(s, 2)
    fmt.Println(*(*[]int)(p1))
    fmt.Println(*(*[]int)(unsafe.Pointer(p2)))
}

//[1]
//[1]
//[1 2]
//[1]

利用反射和 Unsafe 封装一个工具类,实现获取或修改一个不知名对象的字段信息:

package test9

import (
	"errors"
	"reflect"
	"unsafe"
)

//反射类型获取/设置接口
type FieldAccessor interface {
	Field(field string) (int, error)
	FieldAny(field string) (interface{}, error)
	SetField(field string, val int) error
	SetFieldAny(field string, val interface{}) error
}

//对象操作类型
type UnsafeAccessor struct {
	entityAddr unsafe.Pointer		//地址
	fields map[string]FieldMeta		//字段元信息Map集合
}

//字段元信息
type FieldMeta struct {
	offset uintptr		//偏移量
	typ reflect.Type	//类型
}

func (u *UnsafeAccessor)Field(field string) (int, error) {
	f, has := u.fields[field]
	if !has {
		return 0, errors.New("字段不存在")
	}
	res := *(*int)(unsafe.Pointer(uintptr(u.entityAddr) + f.offset))
	return res, nil
}

func (u *UnsafeAccessor)FieldAny(field string) (interface{}, error) {
	f, has := u.fields[field]
	if !has {
		return nil, errors.New("字段不存在")
	}
	res := reflect.NewAt(f.typ, unsafe.Pointer(uintptr(u.entityAddr) + f.offset))
	return res.Interface(), nil
}

func (u *UnsafeAccessor)SetField(field string, val int) error {
	f, has := u.fields[field]
	if !has {
		return errors.New("字段不存在")
	}
	*(*int)(unsafe.Pointer(uintptr(u.entityAddr) + f.offset)) = val
	return nil
}

func (u *UnsafeAccessor)SetFieldAny(field string, val interface{}) error {
	f, has := u.fields[field]
	if !has {
		return errors.New("字段不存在")
	}
	res := reflect.NewAt(f.typ, unsafe.Pointer(uintptr(u.entityAddr) + f.offset))
	if res.CanSet() {
		res.Set(reflect.ValueOf(val))
	}
	return nil
}

func NewFieldAccessor(entity interface{}) (FieldAccessor, error) {
	if entity == nil {
		return nil, errors.New("invalid Entity")
	}
	typ := reflect.TypeOf(entity)
	if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
		return nil, errors.New("invalid entity")
	}
	typ = typ.Elem()	//获取到对象子元素信息
	fields := make(map[string]FieldMeta, typ.NumField())
	for i := 0; i < len(fields); i++ {
		field := typ.Field(i)
		fields[field.Name] = FieldMeta{
			offset: field.Offset,
			typ: field.Type,
		}
	}
	val := reflect.ValueOf(entity)
	return &UnsafeAccessor{
		entityAddr: val.UnsafePointer(),
		fields: fields,
	}, nil
}

标签:fmt,09,接口,reflect,func,Println,type
From: https://www.cnblogs.com/istitches/p/17748645.html

相关文章

  • 2023-2024-20231409佟伟铭第二周学习总结
    学期2023-2024-120231409《计算机基础与程序设计》第二周学习总结作业信息这个作业属于哪个课程2023-2024-1-计算机基础与程序设计这个作业要求在哪里2023-2024-1计算机基础与程序设计第二周作业这个作业的目标自学教材:计算机科学概论第1章并完成云班课......
  • Python入门示例系列09 Python数学运算
     Python中的各种进制一、二进制,八进制,十进制,十六进制的表示方法在python的IDLE中输入的不同进制的数值,直接转化为十进制>>>0b10#以0b开头表示的是二进制(b-Binary)/ˈbaɪnəri/2>>>0o10#以0o开头表示的是八进制(o-字母欧Octal)/ˈɒktl/8>>>0x10#......
  • JavaSE基础05(方法,重载,调用,类和对象,构造器,封装,继承,方法重写,抽象类,接口,异常)
    面向对象以类的方式组织代码,以对象的组织封装数据;一个Java文件只能有一个public类,必须和文件名一样;java文件里也可以没有public类; 方法的定义方法的使用,修饰符返回值类型方法名(参数类型参数名){方法体return返回值};参数类型包括:基本数据类型和引用数据类......
  • 20230930
    //adjust,attractive,bid,binding,carriage,comment,confirmation,consideration,final,firm,worthwhile,floorpriceadjust-调整Toadjustmeanstomakesmallchangesormodificationstosomethinginordertoachieveadesiredresultorfitaparticu......
  • 09_石头剪刀布
    1.数组root@bk:~#arr=(aabbcc)root@bk:~#echo${arr[@]}aabbccroot@bk:~#echo${arr[0]}aaroot@bk:~#echo${arr[2]}cc#遍历序号root@bk:~#foriin${!arr[@]};doecho$i;done012#通过序号遍历元素root@bk:~#foriin${!arr[@]};doecho${arr[$i......
  • [20230922]dc命令复杂学习3.txt
    [20230922]dc命令复杂学习3.txt1.问题提出:--//前一段时间简单学习了dc,累加的例子:$cata.txt1111222233334444$cata.txt|dc-f--e"[+z1<r]srz1<rp"11110$dc-fa.txt-e"[+z1<r]srz1<rp"11110--//实际上如果累加数据量很大,这样的执行效率很低的,因为每次都要判断堆......
  • 多线程,实现Callable接口
    这里改变了之前Thread和Runnable接口的下载网络图片的代码是要下载器类的,下面并没有写出来一、实现Callable接口,重写call()方法  是需要返回值的      好处:可以设置返回值和可以抛出异常 二、与Thread和Runnable接口不一样的地方,是需要四部来开启线程的, Exe......
  • 学习Runnable接口来实现多线程
    1、先创建一个线程类来实现Runable接口 2、跟Thread类的一样照样调用FileUtils文件工具类创建下载器 3、对下载器的形参在线程类中创建属性,用构造方法对属性赋值,并且重写run方法,run方法中实例化下载器 4、实例化Runnable接口并且调用start方法 这里Runnable接口和T......
  • 腾讯TDSQL接口未授权访问信息泄露(CVE-2023-42387)
    腾讯TDSQL接口未授权访问信息泄露CVE-2023-42387漏洞地址:http://tdsql-xxxxxxx.com/tdsqlpcloud/index.php/api/install/get_db_info漏洞描述:tdsql赤兔管理平台,api接口存在未授权返回数据库明文配置信息。漏洞详情:代码审计1,访问上述接口。2,得到明文账号密码,登录数据库。漏洞版本......
  • 【接口自动化】安装环境-python
    1.下载Python访问Python官网: www.python.org/点击downloads按钮,在下拉框中选择系统类型(windows/MacOS/Linux等)选择下载最新版本的Python没有版本要求的话,尽量使用最新版本前几个版本。避免新版本的不稳定 2. 安装Python双击下载好的Python安装包勾选左下角Add......