Golang 反射机制 reflect.TypeOf reflect.ValueOf字符串处理

Posted 知其黑、受其白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang 反射机制 reflect.TypeOf reflect.ValueOf字符串处理相关的知识,希望对你有一定的参考价值。

阅读目录

前言

众所周知,go语言是一门静态编程语言,变量的类型在进行程序的编写时均是写死的。

没有办法在运行时进行改变,您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。

一、反射基本概念

go语言反射为何而生?

  • 对数据进行动态赋值,提高代码的灵活性与复用性。
  • 使程序更灵活,使用反射的方式将数据的类型变为动态的如果使用switch语句进行操作的话会使得代码十分臃肿,并且有用户自定义的数据类型,很难将类型写全。

反射弊端

使用反射涉及到很多包,需要调用很对函数,效率会有所下降但是使用一丢丢效率换取很大的方便我们非常乐意。

怎样使用反射机制?

有官方写好的第三方包,包内有接口 TypeOfValueOf,从接口中获取目标对象的信息。

TypeOf:动态的获取从函数接口中传进去的变量的类型,如果为空则返回值为 nil(获取类型对象)

可以从该方法获取的对象中拿到字段的所属类型,字段名,以及该字段是否是匿名字段等信息。

还可以获取到与该字段进行绑定的 tag。

ValueOf:获取从函数入口传进去变量的值(获取值对象)该方法获取到的对象是对应的数据信息区域(具体的值可以通过 Filed(index) 直接拿到对应位置的值)FieldByName(字段名)

可以通过该函数拿到成员属性的详细信息(返回值与ValueOf一样)

注:指针类型经过以上函数拿到的还是指针类型,结构体还是结构体类型。

Elem()方法:该方法只接受指针类型与接口类型,如果不是这两种类型就会抛出异常(相当于对指针进行取元素)

用到的库

import (
	"errors"
	"fmt"
	
	//读取配置文件用到的库(文件部分后期还会进行详细介绍)
	"io/ioutil"
	
	//反射用到的东西一般都在这里面
	"reflect"
	
	//处理字符串用到的库
	"strconv"
	"strings"
)

常用的字符串处理函数

1 字符串加载为固定类型 strconv.ParseBool()

package main

import (
	"fmt"
	"strconv"
)

func main() 
	//这里的 ParseBool 可以换成ParseInt等,你需要的函数
	boolV, err := strconv.ParseBool("true")
	if err != nil 
		fmt.Println(err)
	
	fmt.Println(boolV)

2 去除字符串首尾空格 strings.TrimSpace()

package main

import (
	"fmt"
	"strings"
)

func main() 
	str := " 1234=56789 "
	str = strings.TrimSpace(str)
	fmt.Println(str)

3 获取特定子串的下标

package main

import (
	"fmt"
	"strings"
)

func main() 
	str := " 1234=56789 "
	index := strings.Index(str, "4=")
	fmt.Println(index) //4

4 将字符串转换为数值 strconv.Atoi()

package main

import (
	"fmt"
	"strconv"
)

func main() 
	//array to number
	i, _ := strconv.Atoi("1433223")
	fmt.Println(i) // 1433223

5 将数值转换为字符串 strconv.Itoa()

package main

import (
	"fmt"
	"strconv"
)

func main() 
	s := strconv.Itoa(10)
	fmt.Println(s) // 10

reflect 包下的常用函数

1 reflect.TypeOf

参数是要进行类型获取的变量,可以是任意类型。

返回值是一个 reflect.Type 接口,这个接口实现了一系列方法,可以获取到传入对象的详细类型信息。

2 reflect.ValueOf

参数是要进行类型获取的变量,可以是任意类型。

返回值是一个 reflect.Value接口,这个接口实现了一系列方法,可以获取到传入对象的详细值信息。

3 示例

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


func main() 
	//对结构体进行初始化
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)
	sTructValue := reflect.ValueOf(stu1)

	// 结构体值
	fmt.Println("值是:", sTructValue)
	//类型
	fmt.Println(sTructType)

	//打印结果
	//值是: hahaha 小明 20
	//main.stu

反射结构体类型变量

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


// 方法
func (st stu) Speak() 


func (st stu) Run(x int) 
	fmt.Println("跑起来,润润润!")

func (st stu) Haha() bool 
	fmt.Println("你是成功的!")
	return true


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)
	sTructValue := reflect.ValueOf(stu1)
	fmt.Println(sTructType, sTructValue)

PS E:\\TEXT\\test_go\\test> go run .\\main.go
main.stu hahaha 小明 20
PS E:\\TEXT\\test_go\\test> 

反射对象可调用的方法

1 NumField() 获取属性个数

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)

	//获取属性个数
	fmt.Println(sTructType.NumField())
	//打印结果3

2 Field()

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)

	//获取指定位置属性的详细类型
	fmt.Println(sTructType.Field(0))
	//打印结果
	//id main string  0 [0] false
	//以上结果含义分别为:
	//Type      Type      // field type字段名
	//Tag       StructTag // field tag string所在包
	//Offset    uintptr   // offset within struct, in bytes//基类型名称
	//Index     []int     // index sequence for Type.FieldByIndex//下标
	//Anonymous bool      // is an embedded field//是不是匿名字段


3 Name() 直接获取到结构体名

想要获取包名+结构体名时直接使用sTructType。

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)

	//类型是: stu
	fmt.Println("类型是:", sTructType.Name())
	// main.stu
	fmt.Println(sTructType)


4 Kind() 获取该类型最初的基本

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)

	// struct
	fmt.Println(sTructType.Kind())


5 Elem() 该方法只接受指针类型与接口类型,如果不是这两种类型就会抛出异常(相当于对指针进行取元素)

package main

import (
	"fmt"
	"reflect"
)

func main() 
	var x int = 10
	v := reflect.ValueOf(&x)
	// elem: 10
	fmt.Println("elem:", v.Elem())

6 NumMethod() 获取方法个数,也就是结构体实现了哪些接口

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


// 方法
func (st stu) Speak() 


func (st stu) Run(x int) 
	fmt.Println("跑起来,润润润!")

func (st stu) Haha() bool 
	fmt.Println("你是成功的!")
	return true


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	// sTructType := reflect.TypeOf(stu1)
	sTructValue := reflect.ValueOf(stu1)
	// NumMethod: 3
	fmt.Println("NumMethod:", sTructValue.NumMethod())

7 Method() 获取结构体对应的方法

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type stu struct 
	id   string
	name string
	age  int


// 方法
func (st stu) Speak() 


func (st stu) Run(x int) 
	fmt.Println("跑起来,润润润!")

func (st stu) Haha() bool 
	fmt.Println("你是成功的!")
	return true


func main() 
	//进行反射
	var stu1 stu
	stu1.age = 20
	stu1.id = "hahaha"
	stu1.name = "小明"
	sTructType := reflect.TypeOf(stu1)
	// sTructValue := reflect.ValueOf(stu1)

	//	获取结构体对应的方法
	fmt.Printf("该结构体有方法%d个\\n", sTructType.NumMethod())
	for i := 0; i < sTructType.NumMethod(); i++ 
		tempMethod := sTructType.Method(i)
		// 获取方法的属性,方法的函数参数以及返回值(仅针对公有方法有效,无法统计私有方法)
		fmt.Println("第", i, "个字段对应的信息为:", tempMethod.Name, tempMethod.Type)
	


打印结果

该结构体有方法3个
第 0 个字段对应的信息为: Haha func(main.stu) bool
第 1 个字段对应的信息为: Run func(main.stu, int)
第 2 个字段对应的信息为: Speak func(main.stu)

二、使用反射实现配置文件的读取

配置文件 myconfig.ini

1 配置文件构成

  • 配置文件可以进行注释,通过# 或者 ; 实现。
  • 配置文件进行分块处理,每一块的模块名用 [] 包裹起来。
  • 配置文件每一个模块有许多配置,每一个配置信息由键值对组成,中间通过 = 或者 : 隔开。

2 配置文件中包含的信息

; mysql配置信息
[mysql]
address=127.0.0.1
port=2206
username=root
userpasswd=123456

; []
;  [
#redies配置信息
[redies]
host=localhost
port=6379
password=root
database=100
test=false
go=123
; =false
; test=

配置文件需求分析

1 配置文件作用

  • 配置文件记录服务器的参数配置,比如数据库的密码端口号,用户名密码等。
  • 如果不存进配置文件的话后期维护起来十分麻烦,比如数据库密码泄露,需要更新数据库密码,所以需要将容易变更的应用配置写进配置文件通过反射机制对其进行解析,以得到更加灵活的程序。

2 配置文件实现步骤

解析配置文件需要的步骤:

1 需要对参数进行校验,因为经过反射需要将文件内的内容进行解析,并读取到结构体对象内,所以需要改原对象中对应的数据,就要传入的是指针类型,否则无法修改原来的值检验结果需要传入的参数是结构体指针,否则返回报错。

2 读取文件得到字节类型的数据,并将数据按行划分,去除注释行与空行。

1 得到的结果进行迭代。

① 去除行首尾的空格,判断本行是否是模块行,是的话进去通过模块名拿到结构体,并记录下来。

② 如果是非模块名将键值进行分割(分割为key,value)结构体属性中没有该对应字段名进行忽略。

只有键没有值的忽略,只有值没有键的抛异常。

迭代①中记录下来的结构体中的字段,并将字段与本行的key进行比对,相同的话将对应的value给该字段(key找不到匹配字段的话忽略)

代码实现(带详细注释)

打印结果

结构体 MysqlConfig 正在解析!
结构体 RedisConfig 正在解析!
所有配置项解析完毕!
成员结构体属性如下: MysqlConfig
属性类型: string 属性名: Address 属性值: 127.0.0.1
属性类型: int 属性名: Port 属性值: 2206
属性类型: string 属性名: Username 属性值: root
属性类型: string 属性名: Userpasswd 属性值: 123456

成员结构体属性如下: RedisConfig
属性类型: string 属性名: Host 属性值: localhost
属性类型: int 属性名: Port 属性值: 6379
属性类型: string 属性名: Password 属性值: root
属性类型: int 属性名: Database 属性值: 100
属性类型: bool 属性名: Test 属性值: false

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"strconv"
	"strings"
)

//以下的``中包含的是一种解析数据的格式,是一一对应的映射关系
//比如:Address 对应ini文件内的address字段
//在进行反射的时候将address对应的数据解析到Address字段中。

type MysqlConfig struct 
	Address    string `ini:"address"`
	Port       int    `ini:"port"`
	Username   string `ini:"username"`
	Userpasswd string `ini:"userpasswd"`

type RedisConfig struct 
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	Password string `ini:"password"`
	Database int    `ini:"database"`
	Test     bool   `ini:"test"`


type Config struct 
	MysqlConfig `ini:"mysql"`
	RedisConfig `ini:"redies"`
	// int         `ini:"mysql"`


func (c Config) Println() 
	mType := reflect.TypeOf(c)
	mValue := reflect.ValueOf(c)
	for i := 0; i < mType.NumField(); i++ 
		fmt.Println("成员结构体属性如下:", mType.Field(i).Name)
		tempValue := mValue.FieldByName(mType.Field(i).Name)
		// tempValue := mValue.FieldByName(mType.Field(i).Name)
		// 获取到的是值域,可以通过该对象进行每个字段的值进行获取
		tempType := tempValue.Type()
		// tempType := tempValue.Type()
		//获取到的是值域的具体类型信息

		// 再次迭代取值
		for j := 0; j < tempType.NumField(); j++ 
			fmt.Println("属性类型:", tempType.Field(j).Type, "属性名:", tempType.Field(j).Name, "属性值:", tempValue.Field(j))
		
		fmt.Println()
	


func Loading_INI(filename string, data interface) error 

	// 获取data的类型
	dataType := reflect.TypeOf(data)

	// 获取data对应的值
	dataValue := reflect.ValueOf(data)

	// 判断传进来的参数是不是指针类型,并且判断一下这个指针类型指向的是不是结构体
	if dataType.Kind() != reflect.Ptr || dataType.Elem().Kind() != reflect.Struct 
		return errors.New("the 2 args must be struct ptr")
	

	// 读取配置文件
	bytes, err := ioutil.ReadFile("myconfig.ini")
	if err != nil 
		return err
	
	iniLines := strings.Split(string(bytes), "\\r\\n")

	// 用于暂时存储ini配置文件内的模块名对应的结构体名
	var ComfigModelStructName string

	// 迭代每一行数据,进行对应的操作
	for index, line := range iniLines 

		// 去除行前面的空格
		line = strings.TrimSpace(line)
		// 判断是不是注释行与空行,是的话直接跳过本次循环
		if len(line) == 0 || (line[0] == '#' || line[0] == ';')  //排除掉空行,以及注释
			continue
		 else if line[0] == '[' 
			// 如果该行没有]
			if line[len(line)-1] != ']' 
				return fmt.Errorf("the '[' has nomatch ']' in %d line", index)
				//如果该行里面有[]却没有字段
			 else if len(strings.TrimSpace(line[1:len(line)-1])) == 0 
				return fmt.Errorf("the [ ] has none in %d line", index)
				//字段书写规范
			 else 
				// 将配置文件内的模块名拿出来
				for i := 0; i < dataType.Elem().NumField(); i++ 
					if line[1:len(line)-1] == string(dataType.Elem().Field(i).Tag.Get("ini")) 
						ComfigModelStructName = dataType.Elem().Field(i).Name
					
				
				fmt.Println("结构体", ComfigModelStructName, "正在解析!")
			
		 else 
			// 将数据以等号为分隔符进行分割
			tempIndex := strings.Index(line, "=")
			Mkey := line[0:tempIndex]
			Mvalue := line[tempIndex+1:]
			// 如果只有值没有键,那么抛出异常,只有键没有值,那么数据为默认值直跳过本次循环
			if len(strings.TrimSpace(Mkey)) == 0 
				return fmt.Errorf("the Key is null in %d line", index+1)
			 else if len(strings.TrimSpace(Mvalue)) == 0 
				continue
			 else 以上是关于Golang 反射机制 reflect.TypeOf reflect.ValueOf字符串处理的主要内容,如果未能解决你的问题,请参考以下文章

Golang 反射机制 reflect.TypeOf reflect.ValueOf字符串处理

Golang 反射机制 reflect.TypeOf reflect.ValueOf字符串处理

Golang反射机制的实现分析——reflect.Type类型名称

Golang反射机制的实现分析——reflect.Type类型名称

Golang的反射reflect深入理解和示例

Golang的反射reflect深入理解和示例