go反射----1类型

声明:文章内容取自雨痕老师《Go语言学习笔记》

反射( reflect )让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥补了静态语言在动态行为上的不足。同时,反射还是元编程的重要手段。

和C数据结构一样,Go对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的。反射操作所需的全部信息都源自接口变量。接口变量除储存自身类型外,还会保存实际对象的类型数据。

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

 这两个反射入口函数,会将任何传入的对象转换为接口类型。

在面对类型时,需要区分Type和Kind。前者表示真实类型( 静态类型 ),后者表示其基础结构( 底层类型 )类别。

package main

import (
	"fmt"
	"reflect"
)

type X int

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

输出:

X int

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

package main

import (
	"fmt"
	"reflect"
)

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, ta == tc)
	fmt.Println(ta.Kind() == tc.Kind())
}

输出:

true false
true

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

package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
	m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
	fmt.Println(a, m)
}

输出:

[10]uint8 map[string]int

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

package main

import (
	"fmt"
	"reflect"
)

func main() {
	x := 100
	tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
	fmt.Println(tx, tp, tx == tp)
	fmt.Println(tx.Kind(), tp.Kind())
	fmt.Println(tx == tp.Elem())
}

输出:

int *int false
int ptr
true

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

package main

import (
	"fmt"
	"reflect"
)

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

输出:

int
int32

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

package main

import (
	"fmt"
	"reflect"
)

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

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

package main

import (
	"fmt"
	"reflect"
)

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 不支持多级名称,如有同名遮蔽,须通过匿名字段的二次获取
同样地,输出方法集时,一样区分基类型和指针类型。 ```golang package main

import (
"fmt"
"reflect"
)

type A int
type B struct {
A
}

func (a A) Av() {}
func (a *A) Ap() {}

func (b B) Bv() {}
func (b *B) Bp() {}

func main() {
var b B
t := reflect.TypeOf(&b)
s := []reflect.Type{t, t.Elem()}
for _, v := range s {
fmt.Println(v, "😊
//注意,方法名为小写时,反射不出来
//通过NumMethod()反射出方法的个数
for i := 0; i < v.NumMethod(); i++ {
fmt.Println(" ", v.Method(i))
}
}
}

输出:

main.B :
{Ap func(
main.B) <func(main.B) Value> 0}
{Av func(
main.B) <func(main.B) Value> 1}
{Bp func(
main.B) <func(main.B) Value> 2}
{Bv func(
main.B) <func(*main.B) Value> 3}
main.B :
{Av func(main.B) <func(main.B) Value> 0}

有一点和想象的不同,反射能探知当前包或外包的非导出结构成员
```golang
package main

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

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
ReadTimeout
WriteTimeout
TLSConfig
MaxHeaderBytes
TLSNextProto
ConnState
ErrorLog
disableKeepAlives
nextProtoOnce
nextProtoErr

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

import (
"fmt"
"reflect"
)

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

辅助判断方法 Implements,ConvertibleTo,AssignableTo都是运行期进行动态调用和赋值所必需的。
```golang
package main

import (
	"fmt"
	"reflect"
)

type X int

func (X) String() string {
	return ""
}
func main() {
	var a X
	t := reflect.TypeOf(a)

	//Implements 不能直接使用类型作为参数,导致这种用法非常别扭
	st := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
	fmt.Println(t.Implements(st))
	it := reflect.TypeOf(0)
	fmt.Println(t.ConvertibleTo(it))
	fmt.Println(t.AssignableTo(st), t.AssignableTo(it))
}

输出:

true
true
true false
posted @ 2016-11-13 21:48  郑闯  阅读(989)  评论(0编辑  收藏  举报