Go中赋值、转换、反射创建对象、调用方法

在这里插入图片描述

Go中的赋值跟类型转换:

在java中反射是可以获取继承关系,而go语言实际是不支持继承的,所以必须是相同的类型才能使用AssignableTo(),ConvertibleTo()

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name string
}
func demo(){
	user:=User{
		Name:"Hello",
	}
	u:=reflect.TypeOf(user)
	user2:=User{
		Name: "world",
	}
	u1:=reflect.TypeOf(user2)
	b:=u.AssignableTo(u1)//u1类型的是否可以赋值给u2类型的---实际就是判断一下 类型1跟类型2是否是同一类型
	fmt.Println(b)
	b1:=u.ConvertibleTo(u1)//是否可以转换
	fmt.Println(b1)
}

func main() {
	demo()
}

在这里插入图片描述

Go中的伪继承

对比java中 的继承 (extends 关键字),Go中无extends关键字,这意味着Go中并没有对继承的支持。但是,Go使用interface实现的功能叫组合,Go是使用组合来实现的继承,说的更精确一点,是使用组合来代替的继承;

伪继承的实现:

話不多説,先看代码:

结构体的嵌套实现继承

package main

import "fmt"

//相当于java中的父类

type Animal struct {
	name string
}

//java中的 子类

type Cat struct {
	//我要继承 Animal
	Animal
	like string
}

//为猫定制的的方法

func (c Cat) Eat() string {
	//可以看到我们没有在Cat中声明 name属性,但是这里可以使用Animal声明的name
	fmt.Println(c.name + "爱吃" + c.like)
	return c.like
}

func (a Animal) Name() string {
	fmt.Println(a.name)
	return a.name
}

func main() {
	a := Animal{
		name: "小动物",
	}
	c := Cat{
		Animal: Animal{
			name: "小猫猫",
		}, like: "小鱼干",
	}
	fmt.Println(c.Name() + c.Eat() + a.Name())
}


这样Cat就继承了Animal中的属性name;

输出结果:
在这里插入图片描述

简单来看这种继承的方式实际就是结构体的嵌套(刚开始学,不知道这样理解是否正确)

接下来我们使用接口的方式来进行封装:

接口的方式实现继承:

package main

import "fmt"

//一个接口

type Animals interface {
	//接口中的方法
	eat()
}

//一个结构体

type Animal struct {
	name string
	like string
}
type Dog struct {
	Animal
	sleepTime int
}

type Cat struct {
	Animal
	hobby string
}
	
// Dog也实现了 eat
func (d Dog) eat() {
	fmt.Println(d.like)
}

// Cat也实现了 eat
func (c Cat) eat() {
	fmt.Println(c.like)
}

//实现接口---即实现接口中的方法eat

func (a Animal) eat() {
	fmt.Println(a.name + "爱吃" + a.like)
}

func main() {
	c := Cat{
		Animal: Animal{
			name: "小花猫",
			like: "小鱼干",
		},
		hobby: "睡觉",
	}
	d := Dog{
		Animal: Animal{
			name: "大黄狗",
			like: "大棒骨",
		},
		sleepTime: 100,
	}

	testinter(c)
	testinter(d)
}
func testinter(animals Animals){
	animals.eat()
}

简单来讲跟结构体的嵌套差不多,只不过这次是实现接口,从代码的实际应用来讲,实现接口是为了实现特定的目的,这个跟java中接口的实现的作用是差不多的功能;

接口的实现判断:

增量代码:

fmt.Println("======================")
	//获取接口的类型
	//先传 nil 进来 强转为接口的指针,再来获得 其类型
	animals := reflect.TypeOf((*Animals)(nil)).Elem()
	//判断是否实现接口
	bools:=cat.Implements(animals)

	fmt.Println(bools)

在这里插入图片描述

implements 方法要求传入Type类型
不能直接传入接口,
这里通过 将nil强制转换为 接口指针,在然后判断其类型

于是reflect.TypeOf((*Animals)(nil)) 就会看起来很怪;

增量代码:

	c:=Cat{
		Animal: Animal{},
		hobby:  "小鱼干",
	}
	cc:=reflect.ValueOf(c)
	fmt.Println(cc.IsNil())//判断 cc 是否是 nil

// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is //
not, IsNil panics. Note that IsNil is not always equivalent to a //
regular comparison with nil in Go. For example, if v was created // by
calling ValueOf with an uninitialized interface variable i, // i==nil
will be true but v.IsNil will panic as v will be the zero // Value.

源码解释告诉我们 只有是chan, func, interface, map, pointer, or slice ,否则就会报错 ,所以在使用之前要判断一下是否符合调用的条件在做处理;

当同时调用 cc.IsNil() ,cc.isValid()时会出现错误,导致程序不能正常运行—>>

panic: reflect: call of reflect.Value.IsNil on struct Value
//不能对结构的值调用reflect.Value.IsNil
goroutine 1 [running]:
reflect.Value.IsNil(...)

注销掉其中一个—>>
在这里插入图片描述
运行结果如下:
在这里插入图片描述

值转为Type


func main() {
	//返回值为Value类型
	i:=reflect.ValueOf(100)
	g:=reflect.ValueOf("abc")

    //打印的是值
	fmt.Println(i)
	fmt.Println(g)
    //可以直接强转--如果确定数据类型的话
	fmt.Println(i.Int())
	fmt.Println(g.String())

	//将Value转为Type
	h:=i.Type()
	k:=g.Type()
	//判断类型
	fmt.Println(h.Kind()==reflect.Int)
	fmt.Println(k.Kind()==reflect.String)
}

Golang中的reflect.Elem()函数用于获取接口v包含的值或指针v指向的值

调用该函数Elem()只能是接口指针

反射修改数据

通过反射可以修改原始变量的值,但是需要一定的条件支持,传入的实参必须是指针类型

package main

import (
	"fmt"
	"reflect"
)

func main(){
	//字符串
	var s ="hello"
	//这里要传指针,否则不会修改
	vs:=reflect.ValueOf(&s)
	vs.Elem().SetString("world")

	fmt.Println(s)
}

这里通过 Elem()方法把指针Value转换为非指针Value----即通过反射获取指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作

我们来操作一下struct

package main

import (
	"fmt"
	"reflect"
)

type user struct {
	name string
	age int
}
func main(){
	//字符串
	var s ="hello"
	//这里要传指针,否则不会修改
	vs:=reflect.ValueOf(&s)
	vs.Elem().SetString("world")

	fmt.Println(s)

u:=user{
	name: "小红",
	age: 18,
}
h:=reflect.ValueOf(&u)//传指针
k:=h.Elem().FieldByName("name")//根据属性名获取值 Elem将其转为值
fmt.Println(k)
fmt.Println(k.CanSet())//只有值是可寻址的时候才可以被修改---即可以被外界访问的
if k.CanSet(){
	k.SetString("小明")
	//k.Elem().SetString("小明")
	fmt.Println(u.name)
}
}

输出结果:
在这里插入图片描述
因为这里name属性的访问权限问题,所以 canSet()的结果为false, 我们将user属性name改为"Name" 再来看输出结果—>>
在这里插入图片描述
当 我们通过反射去修改结构体中的属性时,这个属性 要是能够被访问的—即首字母大写,
如果是接口中的即使时小写的也可以被访问;

反射构建对象

反射创建struct

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name string
	age  int
}
//声明一个切片

var slice1 []User

func main() {
	//Users 切片实例化

	user1 := make([]User, 10)
	//向切片添加元素
	user1 = append(user1, User{
		Name: "小红",
		age:  20,
	})
	user1 = append(user1, User{
		Name: "小明",
		age:  20,
	})
	u := reflect.ValueOf(&user1)

	fmt.Println(u)

	test()
}


func test(){
	uu:=User{
		Name: "test",
		age:  0,
	}
	//获取了类型
	userType:=reflect.TypeOf(uu)
	//反射去构造一个对象,此时返回的是一个指针
	userValue:=reflect.New(userType)
	//获得其中的属性
	userValue.Elem().FieldByName("Name").SetString("小花")
	//将value转化为user类型,这里传的应该是一个值,而不是指针

	if u ,ok:= userValue.Interface().(*User) ;ok{
		fmt.Println(u.Name)
	}
}

运行结果:
在这里插入图片描述

反射创建切片


//声明一个切片

var slice1 []User

	//获取切片的类型
	sliceType := reflect.TypeOf(slice1)
	//构建一个slice  这里是一个指针
	sliceValue := reflect.MakeSlice(sliceType, 2, 8)
	//sliceValue.SetLen(2)
//
	sliceValue.Index(0).Set(reflect.ValueOf(User{
		Name: "创建的slice1",
		age:  10,
	}))
	sliceValue.Index(1).Set(reflect.ValueOf(User{
		Name: "创建的slice2",
		age:  10,
	}))

if uu,ok:=sliceValue.Interface().([]User);ok{
	fmt.Println(uu[0].Name)
	fmt.Println(uu[1].Name)
}

运行结果---->>
在这里插入图片描述
一些异常的展示:

这里是为了在以后工作时找到报错的一些情况:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name string
	Age int
}

func demo()  {
	uu:=User{
		Name: "一个名字而已",
		Age:  100,
	}
	//获得一个类型
	UserType:=reflect.TypeOf(uu)
//去创建一个对象,这里是指针指向这个对象
	newUser:=reflect.New(UserType)
	//将这个对象的属性赋值
	newUser.Elem().FieldByName("Name").SetString("换个名字又何妨")
	newUser.Elem().FieldByName("age").SetInt(111)//注意这里获得的属性 age 在User中并不存在

	//将这个对象转为实际的对象而非指针指向这个对象
	//断言他为User--这里要传值*User
	if uu,ok:=newUser.Interface().(*User);ok{
		//打印这个对象的名字吧
		fmt.Println(uu.Name)
		fmt.Println(uu.Age)
	}
}
func main() {
	demo()
}

这时候会报panic

panic: reflect: call of reflect.Value.SetInt on zero Value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x0?)

这里报错—>>是因为设置 int的时候 找不到属性—即不能将不存在的value设置值,所以在设置时要判断是否存在该属性;但是我们在看源码时发现当在调用这个方法的值无效时会直接报错----即值无效是无法调用这个方法的;
为了不报panic,所以可以使用IsValid() 来做判断;

	n:=newUser.Elem().FieldByName("age")
if n.IsValid() {
	n.SetInt(111)
}

另一个panic就是当User struct的属性为小写字母时—即不可被导出,我们在调用FieldByName()时会报错

panic: reflect: reflect.Value.SetString using value obtained using unexported field

在这里插入图片描述

在使用go的反射机制时要注意的一些事情是:
1,结构体的属性是否可被导出
2,通过反射获得的Value或者Type isValid ,isZero.isNil 是否为true

func main() {
	demo()

	var s []User
	//获得切片类型
	sliceType:=reflect.TypeOf(s)
	//构建一个slice
	sliceT:=reflect.MakeSlice(sliceType,2,8)

	sliceT.Index(0).Set(reflect.ValueOf(User{
		Name: "第一个用户",
		Age:  10,
	}))
	sliceT.Index(1).Set(reflect.ValueOf(User{
		Name: "第2个用户",
		Age:  122,
	}))

	uu:=sliceT.Interface().([]User)

	for i := 0; i < len(uu); i++ {
		fmt.Println(uu[i].Name)
	}
}



下面代码报错了,

panic: interface conversion: interface {} is *[]main.User, not []main.User
	//修改切片的长度

sliceV:=reflect.ValueOf(&S2)
	sliceV.Elem().SetLen(19)
	sliceV.Elem().Index(0).Set(reflect.ValueOf(User{
	Name: "我就不信了",
	Age:  100,
}))
ss:=sliceV.Interface().([]User)
fmt.Println(len(ss))

不报错的代码如下,
在这里插入图片描述
运行结果:
在这里插入图片描述

对比发现原来ss:=这里要通过Elem()转为实际的类型;

小结一下:

如有理解不到位的地方还请老师指点

在通过反射创建 struct或者其他引用类型的对象时, 通过reflect.Typeof()获得其类型
然后通过reflect.Valueof()获得其 带有指针的Value

通过Elem ()来将其转为非指针的,这样才能修改其中的属性;

反射调用方法

//通过反射调用方法
func main() {
	//反射获得方法
	funcValue:=reflect.ValueOf(TestMap)
	//调用方法--->>参数 数组的形式 也要转换为反射的Value
result:=funcValue.Call([]reflect.Value{reflect.ValueOf(3),reflect.ValueOf(5)})
	//因为只有一个返回值,所以取0就行了
	result2:=result[0].Interface().(int)
	fmt.Println(result2)

}

结果:
在这里插入图片描述
这里反射调用时的参数要是value ---->>
源码:
func (v Value) Call(in []Value) []Value { v.mustBe(Func) //必须是一个方法 v.mustBeExported()//方法必须可被外界访问--首字母大写 return v.call("Call", in) }
源码要求我们传入的是[]Value(通过reflect.Value()转为 Value),返回值也是[]Value(因为返回值可能不止一个,所以用[]Value来接收)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeMartain

祝:生活蒸蒸日上!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值