go语言之行--接口(interface)、反射(reflect)详解
一、interface简介
interface(接口)是golang最重要的特性之一,Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
简单的说:
- interface是方法的集合
- interface是一种类型,并且是指针类型
- interface的更重要的作用在于多态实现
interface定义
- type 接口名称 interface {
- method1 (参数列表) 返回值列表
- method2 (参数列表) 返回值列表
- ...
- }
interface使用
- 接口的使用不仅仅针对结构体,自定义类型、变量等等都可以实现接口。
- 如果一个接口没有任何方法,我们称为空接口,由于空接口没有方法,所以任何类型都实现了空接口。
- 要实现一个接口,必须实现该接口里面的所有方法。
- package main
- import "fmt"
- //定义接口
- type Skills interface {
- Running()
- Getname() string
- }
- type Student struct {
- Name string
- Age int
- }
- // 实现接口
- func (p Student) Getname() string{ //实现Getname方法
- fmt.Println(p.Name )
- return p.Name
- }
- func (p Student) Running() { // 实现 Running方法
- fmt.Printf("%s running",p.Name)
- }
- func main() {
- var skill Skills
- var stu1 Student
- stu1.Name = "wd"
- stu1.Age =
- skill = stu1
- skill.Running() //调用接口
- }
- //wd running
多态
上面提到了,go语言中interface是实现多态的一种形式,所谓多态,就是一种事物的多种形态,与python中类的多态是一致的。
同一个interface,不同的类型实现,都可以进行调用,它们都按照统一接口进行操作。
在上面的示例中,我们增加一个Teacher结构体,同样实现接口进行说明:
- package main
- import "fmt"
- type Skills interface {
- Running()
- Getname() string
- }
- type Student struct {
- Name string
- Age int
- }
- type Teacher struct {
- Name string
- Salary int
- }
- func (p Student) Getname() string{ //实现Getname方法
- fmt.Println(p.Name )
- return p.Name
- }
- func (p Student) Running() { // 实现 Running方法
- fmt.Printf("%s running",p.Name)
- }
- func (p Teacher) Getname() string{ //实现Getname方法
- fmt.Println(p.Name )
- return p.Name
- }
- func (p Teacher) Running() { // 实现 Running方法
- fmt.Printf("\n%s running",p.Name)
- }
- func main() {
- var skill Skills
- var stu1 Student
- var t1 Teacher
- t1.Name = "wang"
- stu1.Name = "wd"
- stu1.Age =
- skill = stu1
- skill.Running()
- skill = t1
- t1.Running()
- }
- //wd running
- //wang running
接口嵌套
go语言中的接口可以嵌套,可以理解我继承,子接口拥有父接口的所有方法,并且想要使用该子接口的话,必须将父接口和子接口的所有方法都实现。
- type Skills interface {
- Running()
- Getname() string
- }
- type Test interface {
- sleeping()
- Skills //继承Skills
- }
类型转换
由于接口是一般类型,当我们使用接口时候可能不知道它是那个类型实现的,基本数据类型我们有对应的方法进行类型转换,当然接口类型也有类型转换。
当然我们也可以用这个方式来进行类型的判断。
转换方式:
- var s int
- var x interface
- x = s
- y , ok := x.(int) //将interface 转为int,ok可省略 但是省略以后转换失败会报错,true转换成功,false转换失败, 并采用默认值
示例:
- package main
- import "fmt"
- func main() {
- var x interface{}
- s := "WD"
- x = s
- y,ok := x.(int)
- z,ok1 := x.(string)
- fmt.Println(y,ok)
- fmt.Println(z,ok1)
- }
- //0 false
- //WD true
判断类型示例:
- package main
- import "fmt"
- type Student struct {
- Name string
- }
- func TestType(items ...interface{}) {
- for k, v := range items {
- switch v.(type) {
- case string:
- fmt.Printf("type is string, %d[%v]\n", k, v)
- case bool:
- fmt.Printf("type is bool, %d[%v]\n", k, v)
- case int:
- fmt.Printf("type is int, %d[%v]\n", k, v)
- case float32, float64:
- fmt.Printf("type is float, %d[%v]\n", k, v)
- case Student:
- fmt.Printf("type is Student, %d[%v]\n", k, v)
- case *Student:
- fmt.Printf("type is Student, %d[%p]\n", k, v)
- }
- }
- }
- func main() {
- var stu Student
- TestType("WD", , stu,3.3)
- }
- //type is string, 0[WD]
- //type is int, 1[100]
- //type is Student, 2[{}]
- //type is float, 3[3.3]
二、反射reflect
反射是程序执行时检查其所拥有的结构。尤其是类型的一种能力。这是元编程的一种形式。它同一时候也是造成混淆的重要来源。
每一个语言的反射模型都不同(同一时候很多语言根本不支持反射,python通过hasattr方法实现)
go语言中的反射通过refect包实现,reflect包实现了运行时反射,允许程序操作任意类型的对象。
在介绍反射之前先说明下reflect包中的两个数据类Type和Value。
Type
Type:Type类型用来表示一个go类型。
不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。
获取Type对象的方法:
- func TypeOf(i interface{}) Type
示例:
- package main
- import (
- "reflect"
- "fmt"
- )
- func main() {
- str := "wd"
- res_type := reflect.TypeOf(str)
- fmt.Println(res_type) //string
- }
reflect.Type中方法
通用方法:
- // 通用方法
- func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
- func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
- func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
- func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
- func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
- func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
- func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
- func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
- func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
- // 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
- // 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
- func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
- func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
- func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
- func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
- func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
- ####注意对于:数组、切片、映射、通道、指针、接口
- func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型
示例:
- package main
- import (
- "fmt"
- "reflect"
- )
- type Skills interface {
- reading()
- running()
- }
- type Student struct {
- Name string
- Age int
- }
- func (self Student) runing(){
- fmt.Printf("%s is running\n",self.Name)
- }
- func (self Student) reading(){
- fmt.Printf("%s is reading\n" ,self.Name)
- }
- func main() {
- stu1 := Student{Name:"wd",Age:}
- inf := new(Skills)
- stu_type := reflect.TypeOf(stu1)
- inf_type := reflect.TypeOf(inf).Elem() // 特别说明,引用类型需要用Elem()获取指针所指的对象类型
- fmt.Println(stu_type.String()) //main.Student
- fmt.Println(stu_type.Name()) //Student
- fmt.Println(stu_type.PkgPath()) //main
- fmt.Println(stu_type.Kind()) //struct
- fmt.Println(stu_type.Size()) //
- fmt.Println(inf_type.NumMethod()) //
- fmt.Println(inf_type.Method(),inf_type.Method().Name) // {reading main func() <invalid Value> 0} reading
- fmt.Println(inf_type.MethodByName("reading")) //{reading main func() <invalid Value> 0} true
- }
其他方法:
- // 数值
- func (t *rtype) Bits() int // 获取数值类型的位宽,t 必须是整型、浮点型、复数型
- ------------------------------
- // 数组
- func (t *rtype) Len() int // 获取数组的元素个数
- ------------------------------
- // 映射
- func (t *rtype) Key() reflect.Type // 获取映射的键类型
- ------------------------------
- // 通道
- func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向
- ------------------------------
- // 结构体
- func (t *rtype) NumField() int // 获取字段数量
- func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段
- func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段
- func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段
- func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段
- ------------------------------
- // 函数
- func (t *rtype) NumIn() int // 获取函数的参数数量
- func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息
- func (t *rtype) NumOut() int // 获取函数的返回值数量
- func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息
- func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数。
- // 如果有可变参数,则 t.In(t.NumIn()-1) 将返回一个切片。
示例:
- package main
- import (
- "fmt"
- "reflect"
- )
- type Skills interface {
- reading()
- running()
- }
- type Student struct {
- Name string
- Age int
- }
- func (self Student) runing(){
- fmt.Printf("%s is running\n",self.Name)
- }
- func (self Student) reading(){
- fmt.Printf("%s is reading\n" ,self.Name)
- }
- func main() {
- stu1 := Student{Name:"wd",Age:}
- stu_type := reflect.TypeOf(stu1)
- fmt.Println(stu_type.NumField()) //
- fmt.Println(stu_type.Field()) //{Name string 0 [0] false}
- fmt.Println(stu_type.FieldByName("Age")) //{{Age int 16 [1] false} true
- }
Value
不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。
Value为go值提供了反射接口,获取Value对象方法:
- func ValueOf(i interface{}) Value
示例:
- str := "wd"
- val := reflect.ValueOf(str)
- //wd
reflect.Value方法
注意:以下所有方法中的v是reflect.Value返回的值。
reflect.Value.Kind():获取变量类别,返回常量
- const (
- Invalid Kind = iota
- Bool
- Int
- Int8
- Int16
- Int32
- Int64
- Uint
- Uint8
- Uint16
- Uint32
- Uint64
- Uintptr
- Float32
- Float64
- Complex64
- Complex128
- Array
- Chan
- Func
- Interface
- Map
- Ptr
- Slice
- String
- Struct
- UnsafePointer
- )
常量类型
- package main
- import (
- "reflect"
- "fmt"
- )
- func main() {
- str := "wd"
- val := reflect.ValueOf(str).Kind()
- fmt.Println(val)//string
- }
用于获取值方法:
- func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。
- func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。
- func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。
- func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。
- func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。
- func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。
- func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。
- func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。
- func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。
- func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
- // v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
- func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
- // i、j、k 不能超出 v 的容量。i <= j <= k。
- // v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
- func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
- // 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))
- func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
- // 如果 v 值是未初始化的映射,则返回空列表。
- func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。
- func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。
- func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。
- func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
设置值方法:
- func (v Value) SetInt(x int64) //设置int类型的值
- func (v Value) SetUint(x uint64) // 设置无符号整型的值
- func (v Value) SetFloat(x float64) // 设置浮点类型的值
- func (v Value) SetComplex(x complex128) //设置复数类型的值
- func (v Value) SetBool(x bool) //设置布尔类型的值
- func (v Value) SetString(x string) //设置字符串类型的值
- func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。
- func (v Value) SetCap(n int) //设置切片的容量
- func (v Value) SetBytes(x []byte) //设置字节类型的值
- func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加
其他方法:
- ##########结构体相关:
- func (v Value) NumField() int // 获取结构体字段(成员)数量
- func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段
- func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段
- func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)
- func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回零值(reflect.ValueOf(nil))
- ########通道相关:
- func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。
- func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。
- func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。
- func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。
- func (v Value) Close() // 关闭通道
- ########函数相关
- func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
- // 要传入多少参数就在 in 中存入多少元素。
- // Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。
- func (v Value) CallSlice(in []Value) []Value // 调用变参函数
示例一:获取和设置普通类型的值
- package main
- import (
- "reflect"
- "fmt"
- )
- func main() {
- str := "wd"
- age :=
- fmt.Println(reflect.ValueOf(str).String()) //获取str的值,结果wd
- fmt.Println(reflect.ValueOf(age).Int()) //获取age的值,结果age
- str2 := reflect.ValueOf(&str) //获取Value类型
- str2.Elem().SetString("jack") //设置值
- fmt.Println(str2.Elem(),age) //jack 11
- }
示例二:简单结构体操作
- package main
- import (
- "fmt"
- "reflect"
- )
- type Skills interface {
- reading()
- running()
- }
- type Student struct {
- Name string
- Age int
- }
- func (self Student) runing(){
- fmt.Printf("%s is running\n",self.Name)
- }
- func (self Student) reading(){
- fmt.Printf("%s is reading\n" ,self.Name)
- }
- func main() {
- stu1 := Student{Name:"wd",Age:}
- stu_val := reflect.ValueOf(stu1) //获取Value类型
- fmt.Println(stu_val.NumField()) //
- fmt.Println(stu_val.Field(),stu_val.Field()) //wd 22
- fmt.Println(stu_val.FieldByName("Age")) //
- stu_val2 := reflect.ValueOf(&stu1).Elem()
- stu_val2.FieldByName("Age").SetInt() //设置字段值 ,结果33
- fmt.Println(stu1.Age)
- }
示例三:通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()或者reflect.Value.MethodByName(name string).Call()实现
- package main
- import (
- "fmt"
- "reflect"
- )
- type Student struct {
- Name string
- Age int
- }
- func (this *Student) SetName(name string) {
- this.Name = name
- fmt.Printf("set name %s\n",this.Name )
- }
- func (this *Student) SetAge(age int) {
- this.Age = age
- fmt.Printf("set age %d\n",age )
- }
- func (this *Student) String() string {
- fmt.Printf("this is %s\n",this.Name)
- return this.Name
- }
- func main() {
- stu1 := &Student{Name:"wd",Age:}
- val := reflect.ValueOf(stu1) //获取Value类型,也可以使用reflect.ValueOf(&stu1).Elem()
- val.MethodByName("String").Call(nil) //调用String方法
- params := make([]reflect.Value, )
- params[] = reflect.ValueOf()
- val.MethodByName("SetAge").Call(params) //通过名称调用方法
- params[] = reflect.ValueOf("jack")
- val.Method().Call(params) //通过方法索引调用
- fmt.Println(stu1.Name,stu1.Age)
- }
- //this is wd
- //set age 18
- //set name jack
- //jack 18
go语言之行--接口(interface)、反射(reflect)详解的更多相关文章
- Go语言学习笔记(四)结构体struct & 接口Interface & 反射reflect
加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struc ...
- golang中的反射reflect详解
先重复一遍反射三定律: 1.反射可以将"接口类型变量"转换为"反射类型对象". 2.反射可以将"反射类型对象"转换为"接口类型变量 ...
- 【python3+request】python3+requests接口自动化测试框架实例详解教程
转自:https://my.oschina.net/u/3041656/blog/820023 [python3+request]python3+requests接口自动化测试框架实例详解教程 前段时 ...
- python+requests接口自动化测试框架实例详解
python+requests接口自动化测试框架实例详解 转自https://my.oschina.net/u/3041656/blog/820023 摘要: python + requests实 ...
- Java 反射机制详解(上)
一.什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...
- Java 反射机制详解(下)
续:Java 反射机制详解(上) 三.怎么使用反射 想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属 ...
- ubuntu 下命令行播放器mplayer 使用详解
ubuntu 下命令行播放器mplayer 使用详解 2011-01-02 21:00:42| 分类: Linux/Unix | 标签: |字号大中小 订阅 使用 MPlayer 播放媒体文件最简 ...
- C语言对文件的操作函数用法详解1
在ANSIC中,对文件的操作分为两种方式,即: 流式文件操作 I/O文件操作 一.流式文件操作 这种方式的文件操作有一个重要的结构FILE,FILE在stdio.h中定义如下: typedef str ...
- “全栈2019”Java第六十五章:接口与默认方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
随机推荐
- Git免输入密码提交
1.首先你需要下载 http://files.cnblogs.com/files/zhanqun/putty.7z 文件夹内包含下面这些文件:
- 如何通过活字格实现Excel 汇总
企业中,经常面临需要汇总大批量的Excel 数据表的需求.如果仅通过手工汇总,则会有非常大的工作量,而且还很容易出现错误.此时,如果可以借助活字格这样的工具,来帮助实现汇总Excel 的功能,则会使工 ...
- XQuery使用sum求和,提示char不能转换为money解决方法
select axml.value('sum(/root/pro/price)','money') 以上代码提示‘char不能转换为money’的错误,发现值为'0.0E0'.改为: select a ...
- Swagger使用教程 SwashbuckleEx
一.前言 自从之前写了一篇<Webapi文档描述-swagger优化>这篇文章后,欠了大家一篇使用文档的说明,现在给大家补上哈. 二.环境 .Net Framework 4.5 WebAp ...
- windows下搭建Consul分布式系统和集群
随着大数据时代的到来,分布式是解决大数据问题的一个主要手段,随着越来越多的分布式的服务,如何在分布式的系统中对这些服务做协调变成了一个很棘手的问题.我们在一个项目上注册了很多服务,在进行运维时,需要时 ...
- python爬虫_入门_翻页
写出来的爬虫,肯定不能只在一个页面爬,只要要爬几个页面,甚至一个网站,这时候就需要用到翻页了 其实翻页很简单,还是这个页面http://bbs.fengniao.com/forum/10384633. ...
- UNIX高级环境编程(15)进程和内存分配 < 故宫角楼 >
故宫角楼是很多摄影爱好者常去的地方,夕阳余辉下的故宫角楼平静而安详. 首先,了解一下进程的基本概念,进程在内存中布局和内容. 此外,还需要知道运行时是如何为动态数据结构(如链表和二叉树)分配额外内 ...
- EF5.0中的跨数据库操作
以前在用MVC + EF 的项目中,都是一个数据库,一个DbContext,因此一直没有考虑过在MVC+EF的环境下对于多个数据库的操作问题.等到要使用时,才发现这个问题也不小(关键是有个坑).直接说 ...
- SVN 远程访问
第一种方法 https://www.cnblogs.com/Leo_wl/p/3475167.html#_label0 默认协议为:https 端口号:443 服务器地址:https://主机名/sv ...
- Pre标签 自动换行
<pre> 元素可定义预格式化的文本.被包围在 pre 元素中的文本通常会保留空格和换行符.而文本也会呈现为等宽字体. <pre> 标签的一个常见应用就是用来表示计算机的源代码 ...