This article describes the reflection usage of Go learning notes. Share it for your reference, as follows:
1. Type (Type)
Reflect allows us to detect the type information and memory structure of objects during runtime, which to a certain extent makes up for the lack of dynamic behavior of static languages. At the same time, reflection is also an important means to implement metaprogramming.
Like the C data structure, the Go object header does not have a type pointer, and it is impossible to obtain any type-related information during the runtime. All information required for reflection operation comes fromInterface variables. In addition to storing its own type, interface variables will also save the type data of the actual object.
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
These two reflective entry functions will convert any incoming object to interface type.
When facing types, you need to distinguishType
andKind
. The former represents the real type (static type), and the latter represents its infrastructure (understanding type) category -- base type。
func main() {
var a X = 100
t := (a)
(t)
((), ())
}
Output:
X int
Therefore, in terms of type judgment, you must choose the correct way
type Y int
func main() {
var a, b X = 100, 200
var c Y = 300
ta, tb, tc := (a), (b), (c)
(ta == tb, ta == tc)
(() == ())
}
In addition to obtaining types through actual objects, some basic composite types can also be directly constructed.
a := (10, (byte(0)))
m := ((""), (0))
(a, m)
}
Output:
[10]uint8 map[string]int
Incoming objects should distinguish between base type and pointer type because they do not belong to the same type.
x := 100
tx, tp := (x), (&x)
(tx, tp, tx == tp)
((), ())
(tx == ())
}
Output:
int *int false int ptr true
Method Elem() Returns the base type of a pointer, array, slice, dictionary (value), or channel.
((map[string]int{}).Elem())
(([]int32{}).Elem())
}
Output:
int int32
Only after obtaining the base type of the structure pointer can iterate over its fields.
type user struct {
name string
age int
}
type manager struct {
user
title string
}
func main() {
var m manager
t := (&m)
if () == {
t = ()
}
for i := 0; i < (); i++ {
f := (i)
(, , )
if { // Output anonymous field structure
for x := 0; x < (); x++ {
af := (x)
(" ", , )
}
}
}
}
Output:
user 0 name string age int title string 24
For anonymous fields, they can be accessed directly with multi-level indexes (in the order of definition).
name string
age int
}
type manager struct {
user
title string
}
func main() {
var m manager
t := (m)
name, _ := ("name") // Find by name
(, )
age := ([]int{0, 1}) // Find by multi-level index
(, )
}
Output:
name string age int
FieldByName() does not support multi-level names. If there is occlusion of the same name, it must be obtained twice through the anonymous field.
Similarly, when outputting the method set, the same distinction isBase typeandPointer type。
type B struct {
A
}
func (A) av() {}
func (*A) ap() {}
func (B) bv() {}
func (*B) bp() {}
func main() {
var b B
t := (&b)
s := []{t, ()}
for _, t2 := range s {
(t2, ":")
for i := 0; i < (); i++ {
(" ", (i))
}
}
}
Output:
* : {ap main func(*) <func(*) Value> 0} {av main func(*) <func(*) Value> 1} {bp main func(*) <func(*) Value> 2} {bv main func(*) <func(*) Value> 3} : {av main func(*) <func(*) Value> 0} {bv main func(*) <func(*) Value> 1}
A little different from the imagination, reflection can detect the non-exported structure members of the current package or outsourcing.
"net/http"
"reflect"
"fmt"
)
func main() {
var s
t := (s)
for i := 0; i < (); i++ {
((i).Name)
}
}
Output:
Addr Handler ReadTimeout WriteTimeout TLSConfig MaxHeaderBytes TLSNextProto ConnState ErrorLog disableKeepAlives nextProtoOnce nextProtoErr
Compared with reflect, both packages and outsourcing are "outsourcing".
The struct tag can be extracted using reflection, and it can also be automatically decomposed. It is commonly used in ORM mapping, or data format verification.
type user struct {
name string `field:"name" type:"varchar(50)"`
age int `field:"age" type:"int"`
}
func main() {
var u user
t := (u)
for i := 0; i < (); i++ {
f := (i)
("%s: %s %s\n", , ("field"), ("type"))
}
}
Output:
name: name varchar(50) age: age int
Auxiliary judgment methods Implements(), ConvertibleTo, and AssignableTo() are all performed during the runtimeDynamic callandAssignmentRequired.
func (X) String() string {
return ""
}
func main() {
var a X
t := (a)
// Implements cannot use type directly as a parameter, which makes this usage very awkward
st := ((*)(nil)).Elem()
((st))
it := (0)
((it))
((st), (it))
}
Output:
true true true false
2. Value (Value)
Unlike Type to obtain type information, Value focuses on reading and writing object instance data.
As mentioned in the previous chapter, interface variables will copy objects and are unaddressable, so if you want to modify the target object, you must use a pointer.
a := 100
va, vp := (a), (&a).Elem()
((), ())
((), ())
}
Output:
false false true true
Even if the pointer is passed, it still needs to be passedElem()
Get the target object. Because the pointer stored by the interface cannot be addressed and set up operations.
Notice, no direct setting operations for non-exported fields, whether it is the current package or outsourcing.
Name string
code int
}
func main() {
p := new(User)
v := (p).Elem()
name := ("Name")
code := ("code")
("name: canaddr = %v, canset = %v\n", (), ())
("code: canaddr = %v, canset = %v\n", (), ())
if () {
("Tom")
}
if () {
*(*int)((())) = 100
}
("%+v\n", *p)
}
Output:
name: canaddr = true, canset = true code: canaddr = true, canset = false {Name:Tom code:100}
Method types such as and convert the stored data into a pointer, and the target must be a pointer type. UnsafeAddr returns any CanAddr address (equivalent to & address fetching operation), such as the Value after Elem() and the field member address.
Taking the pointer type field in the structure as an example, Pointer returns the address saved by the field, and UnsafeAddr returns the address of the field itself (structural object address + offset).
Type recommendations and conversions can be performed through the Interface method.
type user struct {
Name string
Age int
}
u := user{
"",
60,
}
v := (&u)
if !() {
println("CanInterface: fail.")
return
}
p, ok := ().(*user)
if !ok {
println("Interface: fail.")
return
}
++
("%+v\n", u)
}
Output:
{Name: Age:61}
You can also use , Bool and other methods to perform type conversion directly, but pani will be triggered when it fails, and ok-iiom is not supported.
Example of compound type object settings:
c := make(chan int, 4)
v := (c)
if ((100)) {
(())
}
}
Output:
100 true
The interface has two nil states, which has always been a potential trouble. The solution is to use IsNil() to determine whether the value is nil.
var a interface{} = nil
var b interface{} = (*int)(nil)
(a == nil)
(b == nil, (b).IsNil())
}
Output:
true false true
You can also directly determine whether it is a zero value after conversion by unsafe.
var b interface{} = (*int)(nil)
iface := (*[2]uintptr)((&b))
(iface, iface[1] == 0)
}
Output:
&[712160 0] true
What makes people feel helpless is that some methods in Value do not implement ok-idom or return error, so you have to judge whether the returned Zero Value is.
v := (struct {name string}{})
println(("name").IsValid())
println(("xxx").IsValid())
}
Output:
true false
3. Method
Dynamically calling methods is not too troublesome. Just press the In list to prepare the required parameters.
func (X) Test(x, y int) (int, error) {
return x + y, ("err: %d", x + y)
}
func main() {
var a X
v := (&a)
m := ("Test")
in := []{
(1),
(2),
}
out := (in)
for _, v := range out {
(v)
}
}
Output:
3 err: 3
For variable parameters, using CallSlice() is more convenient.
func (X) Format(s string, a ...interface{}) string {
return (s, a...)
}
func main() {
var a X
v := (&a)
m := ("Format")
out := ([]{
("%s = %d"), // All parameters must be processed
("x"),
(100),
})
(out)
out = ([]{
("%s = %d"),
([]interface{}{"x", 100}),
})
(out)
}
Output:
[x = 100] [x = 100]
The non-export method cannot be called, and even the valid address cannot be obtained.
4. Construction
The reflection library provides built-in functionsmake()
andnew()
The most interesting one isMakeFunc()
. It can be used to implement a common template to adapt to different data types.
func add(args []) (results []) {
if len(args) == 0 {
return nil
}
var ret
switch args[0].Kind() {
case :
n := 0
for _, a := range args {
n += int(())
}
ret = (n)
case :
ss := make([]string, 0, len(args))
for _, s := range args {
ss = append(ss, ())
}
ret = ((ss, ""))
}
results = append(results, ret)
return
}
// Point the function pointer parameter to a general algorithm function
func makeAdd(fptr interface{}) {
fn := (fptr).Elem()
v := ((), add) // This is the key
(v)
}
func main() {
var intAdd func(x, y int) int
var strAdd func(a, b string) string
makeAdd(&intAdd)
makeAdd(&strAdd)
println(intAdd(100, 200))
println(strAdd("hello, ", "world!"))
}
Output:
300 hello, world!
If the language supports generics, it is natural that there is no need to worry about it like this
I hope this article will be helpful to everyone's Go language programming.