go语言学习(第十章,反射)(go 语言学习笔记)

10.1 类型

反射(reflect)让我们再运行期间探知对象的类型信息和内存结构,这从一定程度上弥补了静态语言在动态行为上的不足。同时,反射还是实现元编程的重要手段。
和C数据结构一样,Go对象头部并没有类型指针,通过其自身是无法在运行期间获知任何类型相关信息的。反射操作所需要的全部信息都源自接口变量。接口变量除存储自身类型外,还会保存实际对象的类型数据。

func Typeof(i interface{}) Type
func Valueof(i interface{}) Value 

这两个反射入口函数,会将任何传入的对象转换为接口类型
在面对类型时,需要区分Type和Kind。前者表示真实类型(静态类型),后者表示其基础(底层类型)类别。

type X int

func main() {
	var a X = 100
	t := reflect.TypeOf(a)
	fmt.Println(t.Name(), t.Kind())	//X int
}

所以在类型判断上,须选择正确的方式。

type X int
type Y int

func main() {
	var a, b X = 100, 200
	var c Y = 300
	ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
	fmt.Println(ta == tb, tb == tc)
	fmt.Println(ta.Kind() == tc.Kind())
}
result
true false
true

除通过实际对象获取类型外,也可直接构造一些符合类型。

func main() {
	a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
	m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
	fmt.Println(a, m)
}
result
[10]uint8 map[string]int

传入对象应区分基类型和指针类型,因为他们并不属于同一类型。

type X int

func main() {
	var x X= 100
	tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
	fmt.Println(tx == tp)
	fmt.Println(tx.Kind(),reflect.TypeOf(x), tp.Kind())
	fmt.Println(tx == tp.Elem())
}
结果
false
int main.X ptr
true

方法Elem返回指针、数组、切片、字典(值)或通道的基础类型

func main() {
	fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
	fmt.Println(reflect.TypeOf([]int32{}).Elem())
}
结果
int
int32

只有在获取结构体指针的基础类型之后,才能遍历它的字段。

type user struct{
	name string
	age int
}
type manager struct{
	user
	title string 
}
func main() {
	var m manager
	t := reflect.TypeOf(&m)
	if t.Kind() == reflect.Ptr{
		t = t.Elem()
	}
	for i:=0;i<t.NumField();i++{
		f := t.Field(i)
		fmt.Println(f.Name,f.Type,f.Offset)
		if f.Anonymous{
			for x:=0;x<f.Type.NumField();x++{
				af := f.Type.Field(x)
				fmt.Println(" ",af.Name,af.Type)
			}
		}
	}
	
}
结果
user main.user 0
  name string
  age int
title string 24

对于匿名字段,可用多级索引(按定义顺序)直接访问

type user struct{
	name string
	age int
}
type manager struct{
	user
	title string 
}
func main() {
	var m manager
	t := reflect.TypeOf(m)
	name,_ := t.FieldByName("name")
	fmt.Println(name.Name,name.Type)

	age := t.FieldByIndex([]int{0,1})
	fmt.Println(age.Name,age.Type)
	
}
结果
name string
age int

FieldByname 不支持多级名称,如有同名遮蔽,须通过匿名字段进行二次获取
有一点和想象中不同,反射能探知当前包或外包的非导出结构成员;

import (
	"fmt"
	"reflect"
	"net/http"
)


func main() {
	var s http.Server
	t := reflect.TypeOf(s)
	for i:=0;i<t.NumField();i++{
		fmt.Println(t.Field(i).Name)
	}
}
结果
Addr
Handler
TLSConfig
ReadTimeout
ReadHeaderTimeout
WriteTimeout
IdleTimeout
MaxHeaderBytes
TLSNextProto
ConnState
ErrorLog
disableKeepAlives
inShutdown
nextProtoOnce
nextProtoErr
mu
listeners
activeConn
doneChan
onShutdown

相对于reflect而言,当前包和外包都是“外包”
可用反射提取struct tag,还能自动分解。其常用于ORM映射,或数据格式验证;

type user struct {
	name string `field:"name" type:"varchar(50)"`
	age  int    `field:"age" type:"int"`
}

func main() {
	var u user
	t := reflect.TypeOf(u)
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		fmt.Printf("%s: %s %s\n", f.Name, f.Tag.Get("field"), f.Tag.Get("type"))
	}

}
结果
name: name varchar(50)
age: age int

10.2 值

和Type获取类型信息不同,Value专注于对象实例数据读写。
在前面章节提到过,接口变量会复制对象,而且是unaddressable的,所以要想修改目标对象,就必须使用指针

func main() {
	a:= 100
	va,vp := reflect.ValueOf(a),reflect.ValueOf(&a).Elem()
	fmt.Println(va.CanAddr(),va.CanSet())
	fmt.Println(vp.CanAddr(),vp.CanSet())
}
结果
false false
true true

就算传入指针,一样需要通过elem获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的
注意,不能对非导出字段直接进行设置操作,无论是当前包还是外包。

type User struct{
	Name string
	code int 
}
func main() {
	p := new(User)
	v := reflect.ValueOf(p).Elem()
	name := v.FieldByName("Name")
	code := v.FieldByName("code")
	fmt.Printf("name:canaddr=%v,canset=%v\n",name.CanAddr(),name.CanSet())
	fmt.Printf("code:canaddr=%v,canset=%v\n",code.CanAddr(),code.CanSet())
	// if name.CanSet(){
	// 	name.SetString("Tom")
	// }
	if name.CanAddr(){
		*(*string)(unsafe.Pointer(name.UnsafeAddr())) = "lin"
	}
	if code.CanSet(){
		code.SetInt(122)
	}else if code.CanAddr(){
		*(*int)(unsafe.Pointer(code.UnsafeAddr())) = 100
	}
	fmt.Printf("%+v\n",p)
}
结果
name:canaddr=true,canset=true
code:canaddr=true,canset=false
&{Name:lin code:100}

Value.Pointer和Value.int等方法类似,将Value.data存储的数据转换为指针,目标必须是指针类型。而UnsafeAddr 返回任何CanAddr Value.data地址(相当于&取地址操作),比如Elem后的Value,以及字段成员地址。
以结构体力的指针类型字段为例,Pointer返回该字段所保存的地址,而UnsafeAddr返回字段自身的地址(结构体对象地址+偏移量)
可通过interface方法进行类型推断和转换。

type User struct {
	Name string
	code int
}

func main() {
	u := User{"q.yuhen", 60}
	v := reflect.ValueOf(&u)
	if !v.CanInterface() {
		println("caninterface:fail.")
		return
	}
	p, ok := v.Interface().(*User)
	if !ok {
		println("caninterface:fail.")
		return
	}
	p.code++
	fmt.Printf("%+v\n", u)
}
{Name:q.yuhen code:61}

复合类型对象设置:

func main() {
	c := make(chan int,4)
	v := reflect.ValueOf(c)

	if v.TrySend(reflect.ValueOf(100)){
		fmt.Println(v.TryRecv())
	}
}
结果
100 true

接口有两种nil状态,这一直是个潜在麻烦。解决方法是用IsNil判断值是否为nil

func main() {
	var a interface{} = nil
	var b interface{} = (*int)(nil)

	fmt.Println(a == nil)
	fmt.Println(b == nil,reflect.ValueOf(b).IsNil())
}
result
true
false true

也可用unsafe转换后直接判断iface.data是否为零值。

func main() {
	var b interface{} = (*int)(nil)
	iface := (*[2]uintptr)(unsafe.Pointer(&b))
	fmt.Println(iface, iface[1] == 0)	//&[4825024 0] true
}

Value里的某些方法并未实现ok-idom或返回error,所以得自行判断返回的是否为Zero Value。

func main() {
	v := reflect.ValueOf(struct{ name string }{})
	println(v.FieldByName("name").IsValid())
	println(v.FieldByName("xxx").IsValid())
}
true
false

10.3 方法

动态调用方法,谈不上有多麻烦。只需按In列表准备好所需参数即可。

type X struct{}

func (X) Test(x, y int) (int, error) {
	return (x + y), fmt.Errorf("err:%d", x+y)
}
func main() {
	var a X
	v := reflect.ValueOf(a)
	m := v.MethodByName("Test")
	in := []reflect.Value{
		reflect.ValueOf(1),
		reflect.ValueOf(2),
	}
	out := m.Call(in)
	for _, v = range out {
		fmt.Println(v)
	}
}
result
3
err:3

对于变参来说,用CallSlice要更方便一些

type X struct{}

func (X) Format(s string,a...interface{}) string{
	return fmt.Sprintf(s,a...)
}

func main() {
	var a X
	v := reflect.ValueOf(a)
	m := v.MethodByName("Format")
	out := m.Call([]reflect.Value{
		reflect.ValueOf("%s=%d"),
		reflect.ValueOf("x"),
		reflect.ValueOf(100),
	})
	fmt.Println(out)
	out = m.CallSlice([]reflect.Value{
		reflect.ValueOf("%s=%d"),
		reflect.ValueOf([]interface{}{"x",100}),
	})
	fmt.Println(out)
}
result
[x=100]
[x=100]

你可能感兴趣的:(go)