参考:
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
是否包含类型 T
:v, 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倾向于使用小的接口定义,很多接口只包含一个方法;
- 较大的接口定义,可以由多个小接口组合而成;
- 只依赖于必要功能的小接口;
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
}
4. 讲的很清楚的反射文章
8. 通过 golang.unsafe 获取/更改字段值
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 读操作:
- 直接读取指针地址
func TestReadMemo(t *testing.T) {
x := int64(-10086)
ptr := unsafe.Pointer(&x)
y := *(*int64)(ptr) // 注意类型为 int64,你可以试一试将类型改为 uint64 会出现什么情况
fmt.Println(y) // -10086
}
- 结合反射和 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 写操作:
- 通过指针直接修改
func TestWri(t *testing.T) {
x := int64(10086)
ptr := unsafe.Pointer(&x)
*(*int64)(ptr) = 123
fmt.Println(x)
}
- 利用反射
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