fabric链码的编写-入门

article/2025/10/3 7:07:12

链码的编写

前言:fabric链码的编写较简单,在熟悉了基本结构和相关API之后就可上手编写,但是要多多练习,提高编写链码的速度和正确度。

学习步骤:

1.熟悉链码的基本结构

2.熟练链码相关API

3.练习,练习,练习

参考链接:https://www.cnblogs.com/zongmin/p/11874792.html#_label0_1

1.链码的基本结构

链码的启动必须通过调用shim包中的Start函数,传递一个类型为Chaincode的参数,该参数是一个接口类型,有两个重要的函数InitInvoke函数,(即每个链码都需实现chaincode接口)

Start函数
xiaoliu- Init

在链码实例化或者升级时被调用,完成初始化数据的工作

在此方法中实现链码的初始化或升级时的处理逻辑
xiaoliu

  • Invoke
    更新或查询账本中的数据状态时被调用,需要在此方法中实现响应调用或查询的业务逻辑
    在此方法中实现链码运行中被调用或查询时的处理逻辑
    在这里插入图片描述

在实际开发中,我们可以自行定义一个结构体,实现chaincode接口,重写chaincode接口的两个方法,并将两个方法指定为自定义结构体的成员方法

链码基本结构如下所示:

package mainimport ("github.com/hyperledger/fabric/core/chaincode/shim"pb "github.com/hyperledger/fabric/protos/peer"
)
//自定义结构体
type SimpleChaincode struct {}
//为结构体添加Init方法
func (s*SimpleChaincode)Init(stub shim.ChaincodeStubInterface)pb.Response  {return shim.Success(nil)
}
//为结构体添加Invoke方法
func (s *SimpleChaincode)Invoke(stub shim.ChaincodeStubInterface)pb.Response  {return shim.Success(nil)
}
//调用Start()函数
func main()  {shim.Start(new(SimpleChaincode))
}
  • shim: 用来访问/操作数据状态、事务上下文和调用其他链代码的 API, 链码通过 shim.ChaincodeStub 提供的方法来读取和修改账本的状态
  • peer: 提供了链码执行后的响应信息的 API,peer.Response 封装了响应信息

2.链码常用API

shim 包提供了如下几种类型的接口:

  • **参数解析 API:**调用链码时需要给被调用的目标函数/方法传递参数,该 API 提供解析这些参数的方法
  • **账本状态数据操作 API:**该 API 提供了对账本数据状态进行操作的方法,包括对状态数据的查询及事务处理等
  • **交易信息获取 API:**获取提交的交易信息的相关 API
  • 对 PrivateData 操作的 API: Hyperledger Fabric 在 1.2.0 版本中新增的对私有数据操作的相关 API
  • **其他 API:**其他的 API,包括事件设置、调用其他链码操作

2.1 参数解析 API

// 返回调用链码时指定提供的参数列表(以字符串数组形式返回)
GetStringArgs() []string// 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回)
GetFunctionAndParameters() (function string, params []string)// 返回提交交易提案时提供的参数列表(以字节串数组形式返回)
GetArgsSlice() ([]byte, error)// 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回)
GetArgs() [][]byte

一般使用 GetFunctionAndParameters() 及 GetStringArgs() 。

2.2 账本数据状态操作 API

// 查询账本,返回指定键对应的值
GetState(key string) ([]byte, error)// 尝试添加/更新账本中的一对键值
// 这一对键值会被添加到写集合中,等待 Committer 进一步确认,验证通过后才会真正写入到账本
PutState(key string, value []byte) error// 尝试删除账本中的一对键值
// 同样,对该对键值删除会添加到写集合中,等待 Committer 进一步确认,验证通过后才会真正写入到账本
DelState(key string) error// 查询指定范围的键值,startKey 和 endkey 分别指定开始(包括)和终止(不包括),当为空时默认是最大范围
// 返回结果是一个迭代器结构,可以按照字典序迭代每个键值对,最后需要调用 Close() 方法关闭
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)// 返回指定键的所有历史值。该方法的使用需要节点配置中打开历史数据库特性(ledger.history.enableHistoryDatabase=true)
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)// 给定一组属性(attributes),将这些属性组合起来构造返回一个复合键
// 例如:CreateComositeKey("name-age",[]string{"Alice", "12"});
CreateCompositeKey(objectType string, attributes []string) (string, error)// 将指定的复合键进行分割,拆分成构造复合键时所用的属性
SplitCompositeKey(compositeKey string) (string, []string, error)// 根据局部的复合键(前缀)返回所有匹配的键值,即与账本中的键进行前缀匹配
// 返回结果是一个迭代器结构,可以按照字典序迭代每个键值对,最后需要调用 Close() 方法关闭
GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)// 对(支持富查询功能的)状态数据库进行富查询,返回结果是一个迭代器结构,目前只支持 CouchDB
// 注意该方法不会被 Committer 重新执行进行验证,所以不能用于更新账本状态的交易中
GetQueryResult(query string) (StateQueryIteratorInterface, error)

注意: 通过 put 写入的数据状态不能立刻 get 到,因为 put 只是链码执行的模拟交易(防止重复提交攻击),并不会真正将状态保存到账本中,必须经过 Orderer 达成共识之后,将数据状态保存在区块中,然后保存在各 peer 节点的账本中。

2.3 交易信息相关 API[了解即可]

// 返回交易提案中指定的交易 ID。
// 一般情况下,交易 ID 是客户端提交提案时由 Nonce 随机串和签名者身份信息哈希产生的数字摘要
GetTxID() string// 返回交易提案中指定的 Channel ID
GetChannelID() string// 返回交易被创建时的客户端打上的的时间戳
// 这个时间戳是直接从交易 ChannnelHeader 中提取的,所以在所以背书节点处看到的值都相同
GetTxTimestamp() (*timestamp.Timestamp, error)// 返回交易的 binding 信息
// 交易的 binding 信息是将交提案的 nonse、Creator、epoch 等信息组合起来哈希得到数字摘要
GetBinding() ([]byte, error)// 返回该 stub 的 SignedProposal 结构,包括了跟交易提案相关的所有数据
GetSignedProposal() (*pb.SignedProposal, error)// 返回该交易提交者的身份信息(用户证书)
// 从 SignedProposal 中的 SignatureHeader.Creator 提取
GetCreator() ([]byte, error)// 返回交易中带有的一些临时信息
// 从 ChaincodeProposalPayload.transient 提取,可以存放与应用相关的保密信息,该信息不会被写入到账本
GetTransient() (map[string][]byte, error)

GetTransient()在私有数据的编写会用到

2.4 对 PrivateData [私有数据]操作的 API


2.5 其他 API

// 设定当这个交易在 Committer 处被认证通过,写入到区块时发送的事件(event),一般由 Client 监听
SetEvent(name string, payload []byte) error// 调用另外一个链码的 Invoke 方法
// 如果被调用链码在同一个通道内,则添加其读写集合信息到调用交易;否则执行调用但不影响读写集合信息
// 如果 channel 为空,则默认为当前通道。目前仅限读操作,同时不会生成新的交易
InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response

3.链码开发示例

此链码为fabric-sample/chaincode中的实例链码,可在fabric-sample中查看详情

[要自己跟着手敲一遍!不能光看,多练习!,先自己缕一遍思路,先回想一下链码的基本结构,以及API]

package mainimport ("fmt""strconv""github.com/hyperledger/fabric/core/chaincode/shim"pb "github.com/hyperledger/fabric/protos/peer"
)type SimpleChaincode struct {
}// 初始化数据状态,实例化/升级链码时被自动调用
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {// println 函数的输出信息会出现在链码容器的日志中fmt.Println("ex02 Init")// 获取用户传递给调用链码的所需参数_, args := stub.GetFunctionAndParameters()var A, B string    // 两个账户var Aval, Bval int // 两个账户的余额var err error// 检查合法性, 检查参数数量是否为 4 个, 如果不是, 则返回错误信息if len(args) != 4 {return shim.Error("Incorrect number of arguments. Expecting 4")}A = args[0]  // 账户 A 用户名Aval, err = strconv.Atoi(args[1])  // 账户 A 余额if err != nil {return shim.Error("Expecting integer value for asset holding")}B = args[2]  // 账户 B 用户名Bval, err = strconv.Atoi(args[3])  // 账户 B 余额if err != nil {return shim.Error("Expecting integer value for asset holding")}fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)// 将账户 A 的状态写入账本中err = stub.PutState(A, []byte(strconv.Itoa(Aval)))if err != nil {return shim.Error(err.Error())}// 将账户 B 的状态写入账本中err = stub.PutState(B, []byte(strconv.Itoa(Bval)))if err != nil {return shim.Error(err.Error())}// 一切成功,返回 nil(shim.Success)return shim.Success(nil)
}// 对账本数据进行操作时(query, invoke)被自动调用
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {fmt.Println("ex02 Invoke")// 获取用户传递给调用链码的函数名称及参数function, args := stub.GetFunctionAndParameters()// 对获取到的函数名称进行判断if function == "invoke" {// 调用 invoke 函数实现转账操作return t.invoke(stub, args)} else if function == "delete" {// 调用 delete 函数实现账户注销return t.delete(stub, args)} else if function == "query" {// 调用 query 实现账户查询操作return t.query(stub, args)}// 传递的函数名出错,返回 shim.Error()return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}// 账户间转钱
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {var A, B string    // 账户 A 和 Bvar Aval, Bval int // 账户余额var X int          // 转账金额var err errorif len(args) != 3 {return shim.Error("Incorrect number of arguments. Expecting 3")}A = args[0]       // 账户 A 用户名B = args[1]       // 账户 B 用户名// 从账本中获取 A 的余额Avalbytes, err := stub.GetState(A)if err != nil {return shim.Error("Failed to get state")}if Avalbytes == nil {return shim.Error("Entity not found")}Aval, _ = strconv.Atoi(string(Avalbytes))// 从账本中获取 B 的余额Bvalbytes, err := stub.GetState(B)if err != nil {return shim.Error("Failed to get state")}if Bvalbytes == nil {return shim.Error("Entity not found")}Bval, _ = strconv.Atoi(string(Bvalbytes))// X 为 转账金额X, err = strconv.Atoi(args[2])if err != nil {return shim.Error("Invalid transaction amount, expecting a integer value")}// 转账Aval = Aval - XBval = Bval + Xfmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)// 更新转账后账本中 A 余额err = stub.PutState(A, []byte(strconv.Itoa(Aval)))if err != nil {return shim.Error(err.Error())}// 更新转账后账本中 B 余额err = stub.PutState(B, []byte(strconv.Itoa(Bval)))if err != nil {return shim.Error(err.Error())}return shim.Success(nil)
}// 账户注销
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {if len(args) != 1 {return shim.Error("Incorrect number of arguments. Expecting 1")}A := args[0]   // 账户用户名// 从账本中删除该账户状态err := stub.DelState(A)if err != nil {return shim.Error("Failed to delete state")}return shim.Success(nil)
}// 账户查询
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {var A stringvar err errorif len(args) != 1 {return shim.Error("Incorrect number of arguments. Expecting name of the person to query")}A = args[0]   // 账户用户名// 从账本中获取该账户余额Avalbytes, err := stub.GetState(A)if err != nil {jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"return shim.Error(jsonResp)}if Avalbytes == nil {jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"return shim.Error(jsonResp)}jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"fmt.Printf("Query Response:%s\n", jsonResp)// 返回转账金额return shim.Success(Avalbytes)
}func main() {err := shim.Start(new(SimpleChaincode))if err != nil {fmt.Printf("Error starting Simple chaincode: %s", err)}
}

4.链码测试

好耶,走到这里,你应该已经学会了链码的基本编写,但是我们编写的链码是需要部署在fabric网络之中的,不能直接运行,在开发过程中可选择单元测试或在fabric中设置dev模式【开发者模式】,将链码部署在网络中,进行测试。

单元测试:无需将链码部署在网络中,操作简单但是不太方便,无法进行富查询【富查询需要在有fabric网络的情况下】

dev模式【建议测试方式】:在fabric网络中的测试,需要将链码部署在fabric网络中。测试较方便,可以进行富查询。但是相较于单元测试,会有些难度。

建议学习方式:

在学习链码的初级阶段,先使用单元测试【对fabric还不是很了解】(如果对fabric已经掌握的不错了,环境搭建方面没什么问题了,可直接使用dev模式进行测试),在之后的学习过程中,使用dev。

单元测试:

参考链接:https://www.cnblogs.com/skzxc/p/12150476.html

fabric中提供了一个MockStub类用于单元测试。

单元测试不需要启动任何网络节点,通过我们的测试文件就可以在本地对链码中的接口进行调用测试。其原理就是在MockStub类中维护一个 map 来模拟 ke,key-val 的状态数据库,链码调用的PutState() 和 GetState() 其实是作用于内存中的map。

MockStub主要提供两个函数来模拟背书节点对链码的调用:MockInit()MockInvoke(),分别调用Init和Invoke接口。接收的参数均为类型为string的uuid(随便设置即可),以及一个二维byte数组(用于测试的提供参数)。二维byte数组的第一个参数为方法名。例如参数为[][]byte{[]byte(“query”),[]byte(“A”)},使用stub.GetFunctionAndParameters方法返回的第一个参数是string类型的方法名,第二个参数为字符串切片类型,在此例中,则方法名为query,参数为[]string{“A”}

单元测试要求:

1.测试文件要以xx_test.go方式命名(即要以文件命名要以_test结尾)

2.测试方法名要以Testxxxx的方式命名(即方法名要以Test开头)

3.要导入testing包

单元测试示例:

package mainimport ("fmt""github.com/hyperledger/fabric/core/chaincode/shim""testing"
)
//测试init方法
func TestSmartContract_Init(t *testing.T) {//调用NewMockStub方法返回一个MockStub类,来模拟背书节点对链码的调用//接收参数为一个string类型的name,随便写无需注意,和一个chaincode接口类型(即你要测试的链码)stub:=shim.NewMockStub("1",new(SimpleChaincode))//调用MockInit()方法测试初始化,返回一个response,//response.Payload中包含链码执行成功响应的数据,response.Message包含链码执行失败返回的错误信息//接收的参数均为类型为string的uuid(随便设置即可),以及一个二维byte数组(用于测试的提供参数)re:=stub.MockInit("1",[][]byte{[]byte(""),[]byte("A"),[]byte("100"),[]byte("B"),[]byte("100")})if len(re.Message)!=0 {fmt.Println(re.Message)}
}
//测试query方法
func TestSimpleChaincode_Invoke(t *testing.T) {//每一个测试方法都是一个单独的模块,不能在上面的方法中测试了初始化,而对于这次的测试query方法就不        进行初始化,两个方法是没有关联的//如果直接调用MockInvoke()取执行query操作,是无法查询到数据的//需要先执行初始化方法(即对数据的添加),然后在执行query操作(仅针对于本例操作讲解,在往后链码的        编写中,若无需进行初始化操作,无需执行初始化//但是要测试一个查询或者删除,要在用一个方法内,先执行新增的操作,然后再去进行查询或删除操作)stub:=shim.NewMockStub("1",new(SimpleChaincode))re:=stub.MockInit("1",[][]byte{[]byte(""),[]byte("A"),[]byte("100"),[]byte("B"),[]byte("100")})if len(re.Message)!=0 {fmt.Println(re.Message)}re2:=stub.MockInvoke("2",[][]byte{[]byte("query"),[]byte("A")})if len(re2.Message)!=0 {fmt.Println(re.Message)}fmt.Println(re2.Payload)
}

测试结果:
xiaoliu
dev测试

【要在搭建好fabric网络环境的基础下】

该链码位于fabric-sample/chaincode/chaincode_example02中,可启动dev网络进行测试

1.启动网络

$ cd ./fabric-samples/chaincode-docker-devmode/
#启动网络
$ docker-compose -f docker-compose-simple.yaml up -d

2.进入链码容器,对链码进行编译

$ docker exec -it chaincode bash
# cd chaincode_example02/go/
# go build
# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go

3.打开一个新的终端,进入 cli 容器,安装并示例化链码

$ docker exec -it cli bash
# peer chaincode install -p chaincodedev/chaincode/chaincode_example02/go -n test -v 0
# peer chaincode instantiate -n test -v 0 -c '{"Args":["init","a", "100", "b","200"]}' -C myc

4.查询账户 a 的余额,返回结果为 100

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

5.从账户 a 转账 10 给 b

# peer chaincode invoke -n test -c '{"Args":["invoke","a","b","10"]}' -C myc

6.再次查询账户 b 的余额,返回结果为 90

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

可以在 chaincode 容器中查看到运行的日志:

ex02 Init
Aval = 100, Bval = 200
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210
ex02 Invoke
Query Response:{"Name":"a","Amount":"90"}

7.关闭网络

$ docker-compose -f docker-compose-simple.yaml down

**5.从账户 a 转账 10 给 b**```shell
# peer chaincode invoke -n test -c '{"Args":["invoke","a","b","10"]}' -C myc

6.再次查询账户 b 的余额,返回结果为 90

# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc

可以在 chaincode 容器中查看到运行的日志:

ex02 Init
Aval = 100, Bval = 200
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210
ex02 Invoke
Query Response:{"Name":"a","Amount":"90"}

7.关闭网络

$ docker-compose -f docker-compose-simple.yaml down

关于dev更多详情及部署参考上方链接或文件中的链码开发dev模式.docx


http://chatgpt.dhexx.cn/article/s9VwcR1G.shtml

相关文章

Hyperledger Fabric 链码

懂哪写哪,随时补充 链码结构 链码API 链码在开发过程中需要实现链码接口,交易的类型决定了哪个接口函数将会被调用,链码的接口定义如下: type Chaincode interface {Init(stub ChaincodeStubInterface) pb.ResponseInvoke(stu…

fabric2.3链码对比1.4链码小记

最近实验室的项目要部署到fabric2.0以上版本,之前写的都是1.4的链码,现在看2.0版本的链码还是有些不一样的,主要是链码api改了: 前提:如果想在fabric2.0以上环境中还是想用shim和peerAPI的话:也就是&#…

Hyperledger Fabric 链码生命周期

目录 一、什么是链码 二、部署链码 2.1 安装和定义链码 2.1.1 打包智能合约 2.1.2 peer节点安装链码 2.1.3 组织批准链码 2.1.4 将链码提交到通道 2.2 升级链码 总结 一、什么是链码 ChainCode(链码)是一个程序,用Go、Node.js或Java编…

matlab freeman链码,对Freeman链码分析的角点检测算法

图像中的角点是图像的重要特征, 具有旋转不变性, 决定了图像形状, 可以降低图像信息的存储效率, 在目标跟踪, 目标检测, 图像匹配, 图像轮廓拟合等领域都有重要的应用价值. 近几十年来, 国内外学者提出的图像角点检测算法[, 各有各的优缺点, 大致可分为三大类: 基于灰度强度的角…

Fabric链码升级

一、修改链码,上传 二、打包链码 1、设置组织1环境变量 export PATH${PWD}/../bin:$PATH export FABRIC_CFG_PATH$PWD/../config/ # Environment variables for Org1 export CORE_PEER_TLS_ENABLEDtrue export CORE_PEER_LOCALMSPID"Org1MSP" export C…

图像特征-链码(Freeman code)

目录 引言链码编程实现轮廓提取链码计算 总结 引言 本文介绍了图像的形状特征–链码,以及通过python和opencv实现的链码提取方法。所有opencv的版本为3.4.2,已经移除了直接返回链码的选项。 链码 链码用于描述图像的形状特征,首先需要获得图像…

Java知识扫盲——向上转型(类向上转型、接口向上转型)以及向上转型的优势、灵活运用

目录 普通类示例 抽象类的向上转型, 接口向上转型: 作用:使用向上转型可以,提高代码的简洁性、灵活性、适用性。 普通类示例 父类: package 普通类向上转型;public class Father {public int age 48;public void…

java 向上转换_java的向上转型总结

在《think of java》中对向上转型有着如下的描述 看完之后很蒙圈,所以自己在网上找了一些描述,并自己做了简单的总结 简单的例子 class A{ public void A1(){ System.out.println("父类方法1"); } public void A2(){ System.out.println("…

JAVA中多态以及向上转型向下转型、重写的讲解

重写 重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 注意!!!返回值和形参都不能改变。 重写的好处在于子类可以根据需要&#x…

JAVA中的向上转型和向下转型

一、向上转型和向下转型 向上转型和向下转型是在JAVA继承操作中用到的东西,在讲到转型之前我们需要认识到继承过程中的对象类型转换,这种转换有两个特点: 1.这是继承过程中发生的操作 2.新类是现有类的一种类型。这种说法主要是因为现有类的方…

java转型 内存_Java向上转型及内存分析

学习设计模式的时候,发现很多模式都用到了向上转型(eg. 工厂方法)。而我对向上转型(upcasting)的机制并不十分熟悉。这篇文章将深入分析向上转型的机制、内存分析。 概念 先从几个基本概念开始: 1. Java中的引用类型(reference type) Java中的数据类型分…

java向上转型_Java向上转型和向下转型

一、向上转型 简单的说向.上转型就是:将-一个子类类型的对象赋值给- -个父类类型的变量, 基本的实现语法: 父类类型变量new 子 类类型(); DEMO:向上转型 发现了可以使用父类类型的变量调用子类覆写了父类的方法,但是为什么不调用Worker的say()方法呢?原…

C++ 向上转型

在 C 中经常会发生数据类型的转换,例如将 int 类型的数据赋值给 float 类型的变量时,编译器会先把 int 类型的数据转换为 float 类型再赋值;反过来,float 类型的数据在经过类型转换后也可以赋值给 int 类型的变量。 数据类型转换…

java 对象向上转型_JAVA对象向上转型和向下转型

今天做了一个测试的题目,发现自己还是很多问题没有静下心来做。很多问题是可以自己解决的但是自己一是没有读清题意,二是自己心里太急躁了。所以这个要自己应以为鉴! 对象的转型问题其实并不复杂,我们记住一句话:“父类…

java 向上转型_Java 转型问题(向上转型和向下转型)

Java 转型问题其实并不复杂,只要记住一句话:父类引用指向子类对象。 什么叫父类引用指向子类对象? 从 2 个名词开始说起:向上转型(upcasting) 、向下转型(downcasting)。 举个例子:有2个类,Father 是父类&a…

java向上转型_Java向上转型

向上类型转换: 1.可以通过父类引用变量调用的方法是子类覆盖或继承父类的方法。 2.父类引用变量无法调用子类新增成员变量和新增成员方法。 举个例子 public classAnimal { public voidsleep() { System.out.println("Animal sleep"); } public voidjump(…

向上转型与向下转型(超详细)

本文利用代码例子解释向上转型与向下转型,文末有举例整合原代码。 首先,我们要知道:转型发生在继承后,也就是父类子类存在的前提下。其次,我们要清楚:向下转型的前提是已经发生了向上转型,向下转…

java向上转型

我们知道按照现有类的类型来创建新类的方式为继承。 换句话说 “新类是现有类的一种类型” 因为继承可以确保父类中所有的方法在子类中也同样有效,所以能够向父类发送的所有信息也同样可以向子类发送。 这儿有一个例子: public class Test {public sta…

类中的向上转型与向下转型详解

我们的类与类之间会存在继承关系,子类继承父类,一个父类可以有多个子类,例如Animal类就可以有Cat子类,Dog子类,等等。那么我们在运用的时候根据不同的场景会出现向上转型和向下转型的情况。 一、向上转型 1、Animal a…

【基础知识】向上转型和向下转型

向上转型和向下转型的利弊 一、向上转型 好处:隐藏了子类型,提高了代码的扩展性。 坏处:只能使用父类的功能,不能使用子类特有功能,功能被限定。 使用场景:不需要面对子类型,通过提高扩展性&am…