Golang 接口与反射知识要点
目录
Golang 接口与反射知识要点
这篇文章以 Go 官方经典博客 The Laws of Reflection 为基础,详细介绍文中涉及的知识点,并有所扩展。
1. 接口类型变量
首先,我们谈谈接口类型的内存布局(memory layout),其他基础类型、Struct、Slice、Map、指针类型的内存布局会在以后单独分析。接口变量的值包含两部分内容:赋值给接口类型变量的实际值(concrete value),实际值的类型信息(type descriptor)。两部分在一起构成接口的值(interface value)。
接口变量的这两部分内容由两个字来存储(假设是 32 位系统,那么一个字就是 32 位),第一个字指向 itable (interface table)。itable 表示 interface 和实际类型的转换信息。itable 开头是一个存储了变量实际类型的描述信息,接着是一个由函数指针组成的列表。注意 itable 中的函数和接口类型相对应,而不是和动态类型。例如下面例子,itable 只关联了 Stringer 中定义的 String 方法,而 Binary 中定义的 Get 方法则不在其中。对于每个 interface 和实际类型,只要在代码中存在引用关系, go 就会在运行时为这一对具体的 <Interface, Type> 生成 itable 信息。
第二个字称为 data,指向实际的数据。例子中,赋值语句 var s Stringer = b 实际上对b做了拷贝,而不是对b进行引用。存放在接口变量中的数据大小可能任意,但接口只提供了一个字来专门存储真实数据,所以赋值语句在堆上分配了一块内存,并将该字设置为对这块内存的引用。
type Stringer interface {
String() string
}
type Binary uint64
func (i Binary) String() string {
return strconv.Uitob64(i.Get(), 2)
}
func (i Binary) Get() uint64 {
return uint64(i)
}
b := Binary(200)
var s Stringer = b
Go 是静态类型语言(statically typed)。一个接口类型的不同变量总是有同样静态类型,尽管在运行时,接口变量的保存的实际值会改变。下面例子中,无论 r 被赋予的什么实际值,r 的类型总是 io.Reader。
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
2. 类型断言
类型断言是一个使用在接口变量上的操作。
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
在这个例子中,r 被赋予了 tty 的一个拷贝,所以实际值是 tty。而实际类型是 os.File。需要注意到,os.File 类型自身还实现了除接口方法 Read 以外的方法。尽管接口变量只能访问 Read 方法,但接口的 data 字部分里携带了实际值的全部信息。因此我们可以有如下操作:
var w io.Writer
w = r.(io.Writer)
该赋值语句后边是一个类型断言。它断言的是 r 变量携带的元素,同时是 io.Writer 接口的实现,所以我们才能把 r 赋值给 w。赋值后的 w 可以访问 Write 方法,但无法访问 Read 方法了。
3. 鸭子类型
鸭子类型(duck typing)是动态类型和某些静态语言用到的一种对象推断风格。一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念也可以表述为:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
鸭子类型像多态一样工作,但是没有继承。在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。在常规类型中,我们能否在一个特定场景中使用某个对象取决于这个对象的类型,而在鸭子类型中,则取决于这个对象是否具有某种属性或者方法 —— 即只要具备特定的属性或方法,能通过鸭子类型测试,就可以使用。鸭子类型的缺点是没有任何静态检查,如类型检查、属性检查、方法签名检查等。
Go 语言虽然是静态语言,但在接口类型中使用了鸭子类型。不同于其他鸭子类型语言的是,它实现了在编译时进行静态检查,比如变量是否实现接口方法、调用接口方法时参数个数是否相符,同时也不失鸭子类型带来的灵活和自由。
4. 反射机制
- 什么是反射机制?
在计算机科学中,反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
简单来说,反射只是一种机制,在程序运行时获得对象类型信息和内存结构。通常高级语言借助反射机制来解决,编译时无法知道变量具体类型,而只有等到运行时才能检查值和类型的问题。不同语言的反射模型不尽相同,有些语言还不支持反射。对于低级语言,比如汇编语言,由于自身可以直接和内存打交道,所以无需反射机制。
- 使用反射的场景?
Go 语言中使用反射的场景:有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定,但事先无法不知道接受到的参数是什么类型,全部以 interface{} 类型接受。这时就需要对函数的参数进行反射,在运行期间动态地执行函数。感兴趣的读者可以参考 fmt.Sprint(a ...interface{}) 方法的源码。
5. reflect 包
TypeOf()、ValueOf()
reflect 包封装了很多简单的方法(reflect.TypeOf 和 reflect.ValueOf)来动态获得类型信息和实际值(reflect.Type,reflect.Value)。
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x)) // 打印 type: float64
var r io.Reader = strings.NewReader("Hello")
fmt.Println("type:", reflect.TypeOf(r)) // 打印 type: *strings.Reader
reflect.TypeOf 方法的函数签名是 func TypeOf(i interface{}) Type 。它接受任意类型的变量。当我们调用 reflect.TypeOf(x) 时,x 首先存储在一个空接口类型中,作为传参。reflect.TypeOf 解析空接口,恢复 x 的类型信息。而 reflect.ValueOf 可以恢复 x 实际值。
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String()) // 打印 value: <float64 Value>
Type()、Kind()
reflect.Type 和 reflect.Value 都提供了很多方法支持来操作他们。1. reflect.Value 的 Type() 方法返回对应的 reflect.Type;2. reflect.Type 和 reflect.Value 都有 Kind() 方法,来获得实际值的类型对应 reflect 包中的常量;3. reflect.Value 的以类型名为方法名的方法,比如 Int(),Float(),能获得实际值。
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
打印结果:
shell script type: float64 kind is float64: true value: 3.4
有一点需要注意的是,Kind() 方法返回的是反射对象的底层类型,而不是静态类型。比如,如果反射对象接受一个用户定义的整数型变量:
func main() {
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is int:", v.Kind() == reflect.Int)
fmt.Println("value:", v.Int())
}
打印结果:
shell script type: main.MyInt kind is int: true value: 7
v 调用 Kind() 仍是 reflect.Int,即使 x 的静态类型是 MyInt 而不是 int。总而言之,Kind() 方法无法区分来自 MyInt 的整型,但 Type() 方法可以。
Interface()
Interface() 方法能从 reflect.Value 变量中恢复接口值,是 ValueOf() 的逆向。注意的是,Interface() 方法返回总是静态类型 interface{}。
6. 反射对象的可设置性
SetXXX(), CanSet()
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // will panic: reflect.Value.SetFloat using unaddressable value
运行上面的例子,我们可以发现 v 不可修改(settable)。可设置性(Settability)是 reflect.Value 的一个特性,但不是所有的 Value 都有。
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet()) // settability of v: false
Elem()
由于是 x 的一个拷贝传入 reflect.ValueOf,所以 reflect.ValueOf 创建的接口值也是 x 的一个拷贝,不是原 x 本身。因此修改反射对象,无法修改 x,反射对象不具有可设置性。
显然,要使反射对象具有可设置性。传入 reflect.ValueOf 的参数应该是 x 的地址,即 &x。
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type()) // type of p: *float64
fmt.Println("settability of p:", p.CanSet()) // settability of p: false
反射对象 p 仍是不可设置的,因为我们不是要设置 p,而是 p 所指向的内容。使用 Elem 方法获取。
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value
v := p.Elem()
fmt.Println("settability of v:", v.CanSet()) // settability of v: true
v.SetFloat(7.1)
fmt.Println(v.Interface()) // 7.1
fmt.Println(x) // 7.1
7. Struct 的反射
NumField(), Type.Field()
我们用 struct 的地址来创建反射对象,这样后续我们可以修改这个 struct:
type T struct {
A int
B string
}
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())
}
打印结果:
0: A int = 23
1: B string = skidoo
Value.Field()
T 的字段必须是首字母大写的才可以设置,因为只有暴露的 struct 字段,才具有可设置性。
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t) // t is now {77 Sunset Strip}
参考文件
Go Data Structures: Interfaces
Golang 接口与反射知识要点的更多相关文章
- golang:reflect反射
因为之前一直以C++为主要开发语言,所以刚接触go语言中的reflect时感觉很懵逼,因此决定找资料彻底学习一下. 到底反射是什么? https://blog.golang.org/laws-of-r ...
- JAVA 构造器, extends[继承], implements[实现], Interface[接口], reflect[反射], clone[克隆], final, static, abstrac
记录一下: 构造器[构造函数]: 在java中如果用户编写类的时候没有提供构造函数,那么编译器会自动提供一个默认构造函数.它会把所有的实例字段设置为默认值:所有的数字变量初始化为0;所有的布尔变量设置 ...
- [CISCO] 转载:冲突域与广播域(区别、知识要点)
[CISCO] 转载:冲突域与广播域(区别.知识要点) 1.传统以太网操作(Ethernet Connection Ethernet) 传统共享式以太网的典型代表是总线型以太网.在这种类型的以太网中, ...
- Go语言的接口与反射
美女图片没啥用,就是为了好看 本文还在完善中... go总体而言是一门比较好入门的语言,许多特性都很精简易懂,但是接口与反射除外.他们真的让人头疼,不知道是自身资质问题还是怎么着,总是觉得很多书上写的 ...
- RIP 知识要点
RIP知识要点: UDP:520 版本:v1(广播包更新) / v2(组播更新 224.0.0.9 ) 度量值:跳数(最多跳15跳,路由为16跳时路由不可达) =================== ...
- 总结了零基础学习Java编程语言的几个基础知识要点
很多Java编程初学者在刚接触Java语言程序的时候,不知道该学习掌握哪些必要的基础知识.本文总结了零基础学习Java编程语言的几个基础知识要点. 1先了解什么是Java的四个方面 初学者先弄清这 ...
- Golang通脉之反射
什么是反射 官方关于反射定义: Reflection in computing is the ability of a program to examine its own structure, pa ...
- golang中的反射reflect详解
先重复一遍反射三定律: 1.反射可以将"接口类型变量"转换为"反射类型对象". 2.反射可以将"反射类型对象"转换为"接口类型变量 ...
- CentOs7下systemd管理知识要点
centOs7的一个巨大的变动就是用systemd取代了原来的System V init.systemd是一个完整的软件包,安装完成后有很多物理文件组成,大致分布为,配置文件位于/etc/system ...
随机推荐
- 牛客练习赛39 D 动态连通块+并查集 X bitset 优化
https://ac.nowcoder.com/acm/contest/368/D 题意 小T有n个点,每个点可能是黑色的,可能是白色的.小T对这张图的定义了白连通块和黑连通块:白连通块:图中一个点集 ...
- HDU4289Control 无向图拆点最大流
/* ** 无向图拆点,求最大流,最大流即为割点个数. */ #include <iostream> #include <cstdio> #include <cstrin ...
- CodeForces 1082 F Speed Dial
题目传送门 题意:现在有n个电话号码,每个电话号码为si,拨打次数为pi. 现在有k 个快捷键,每次拨打号码之前可以先按一次快捷键,然后再输入数字,现在问输入数字次数是多少.快捷键的号码可以不在电话簿 ...
- 51nod 1376 最长递增子序列的数量(不是dp哦,线段树 + 思维)
题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1376 题解:显然这题暴力的方法很容易想到就是以每个数为结尾最 ...
- Promise then中回调为什么是异步执行?Promise执行机制问题
今天发现一个问题,看下方代码 let p = new Promise(function(resolve, reject) { resolve() console.log('); }); p.then( ...
- 【Spring】 AOP Base
1. AOP概述 2. AOP的术语: 3. AOP底层原理 4. Spring 中的AOP 4.1 概述 4.2 分类 4.3 Spring的传统AOP 针对所有方法的增强:(不带有切点的切面) 带 ...
- HTTPS页面使用CNZZ统计代码,Chrome显示警告怎么办?
很多站长会遇到一个问题,网站加入CNZZ的JS统计代码后,Chrome浏览器出现警告:阻止跨站解析器阻断脚本通过document.write调用(A parser-blocking, cross si ...
- 【LeetCode】34-在排序数组中查找元素的第一个和最后一个位置
题目描述 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置. 你的算法时间复杂度必须是 O(log n) 级别. 如果数组中不存在目标值 ...
- 实验吧CTF练习题---WEB---因缺思汀的绕过解析
实验吧web之因缺思汀的绕过 地址:http://www.shiyanbar.com/ctf/1940 flag值: 解题步骤: 1.点开题目,观察题意 2.通过观察题目要求,判断此道题还有代码审 ...
- [币严区块链]数字货币交易所之比特币(BTC)钱包对接 | 自建节点JSON-RPC访问
BTC钱包对接流程 一. 部署BTC钱包节点 二. 分析BTC钱包的API 三. 通过JSON-RPC访问BTC钱包API 四. 部署测试 一.部署钱包节点 交易平台对接BTC之前,要 ...