型アサーションの形式
型アサーションは、インターフェイス値に対して実行される操作です。構文的には、i.(T) はアサート型と呼ばれるように見えます。ここで、i はインターフェイスの型を示し、T は型を示します。型アサーションは、操作対象のオブジェクトの動的型がアサートされた型と一致するかどうかをチェックします。
型アサーションの基本的な形式は次のとおりです。
t := i.(T)
このうち、i はインターフェース変数、T は変換対象の種類、t は変換後の変数を表します。
ここには 2 つの可能性があります。まず、アサートされた型 T が具象型の場合、型アサーションは i の動的型が T と同じかどうかをチェックします。このチェックが成功すると、型アサーションの結果は i の動的な値になります。これはもちろん型 T です。つまり、具象型の型アサーションは、そのオペランドから具象値を取得します。チェックが失敗すると、次の操作がパニックになります。例えば:
var w io.Writer
w = os.Stdout
f := w.(*os.File) // 成功: f == os.Stdout
c := w.(*bytes.Buffer) // 死にます:インタフェースには*os.Fileが保存されているため、*bytes.Bufferに変換できません。
次に、型 T がインターフェイス型であると代わりにアサートされた場合、型アサーションは i の動的型が T を満たすかどうかをチェックします。このチェックが成功した場合、動的値はフェッチされません。結果は同じ型と値部分を持つインターフェイス値のままですが、結果の型は T になります。つまり、インターフェイス型の型アサーションは型の表現方法を変更し、使用可能な (通常はより大きな) メソッドのセットを変更しますが、動的型とインターフェイス値内の値の一部は保持されます。
以下の最初の型アサーションの後、w と rw は両方とも os.Stdout を保持するため、それぞれ動的型 *os.File を持ちますが、変数 w はファイルのみを公開する io.Writer 型の Write メソッドですが、rw 変数のみですRead メソッドを公開します。
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // Success: *os.file has both read and write functions
w = new(ByteCounter)
rw = w.(io.ReadWriter) // Deadlock: *ByteCounter does not have a read method
アサーション操作のオブジェクトが nil インターフェイス値である場合、アサートされる型に関係なく、型アサーションは失敗します。制限の少ないインターフェイス タイプ (メソッド セットが少ない) では、インターフェイス値が nil の場合を除いて代入操作のように動作するため、アサートする必要はほとんどありません。
T インターフェイスのメソッドを完全に実装していない場合、このステートメントはクラッシュを引き起こします。ダウンタイムのトリガーはあまり親切ではないため、上記のステートメントを別の方法で書くこともできます。
t,ok := i.(T)
このように、インターフェイスが実装されていない場合、ok は false に設定され、t は T 型の 0 に設定されます。正常に実装された場合、ok は true になります。ここでの ok は、i インターフェイスが型 T の結果を実装するかどうかとみなすことができます。
インターフェースを別のインターフェースに変換する
インターフェイスを実装する型は別のインターフェイスも実装し、この時点で 2 つのインターフェイス間で変換できます。
鳥と豚には異なる特性があり、鳥は飛ぶことができ、豚は飛ぶことができませんが、どちらの動物も歩くことができます。鳥と豚が構造体を使用して実装されている場合、鳥と豚に独自の特性を持たせるための Fly() メソッドと Walk() メソッドを使用すると、鳥と豚が飛行する動物のインターフェイス (Flyer) と歩く動物のインターフェイス (Walker) を実装できます。 ) それぞれ。
鳥と豚のインスタンスが作成された後、それらはタイプ インターフェイスのマップに保存されます。{} Interface{} タイプは空のインターフェースを表します。つまり、このインターフェースは任意のタイプとして保存できます。鳥または豚のインスタンスを保持するインターフェイス変数に対してアサーション オペレーションを実行します。アサーション オブジェクトがアサーションで指定された型である場合は、アサーション オブジェクトの型に変換されたインターフェイスを返します。そうでない場合は、アサーション オブジェクトの型に変換されたインターフェイスを返します。{}指定されたアサーション タイプでは、アサーションの 2 番目のパラメーターは false を返します。たとえば、次のコード:
var obj interface = new(bird)
f, isFlyer := obj.(Flyer)
コードでは、 new(bird) は *bird 型の Bird インスタンスを生成し、このインスタンスは、interface{} 型の obj 変数に保存されます。 obj.(Flyer) 型アサーションを使用して、obj を Flyer インターフェイスに変換します。 f は変換が成功した場合の Flyer インターフェイスの型で、isFlyer は変換が成功したかどうかを示し、型は bool です。
詳細なコード (コード 1) は次のとおりです。
package main
import "fmt"
// 飛行動物のインターフェースを定義する
type Flyer interface{
Fly()
}
// 歩行動物のインターフェースを定義する
type Walker interface{
Walk()
}
// 鳥を定義する
type bird struct{
}
// 飛行動物のインターフェースを実装する
func (b * bird) Fly(){
fmt.Println("bird: fly")
}
// 鳥にWalk()メソッドを追加し、歩行動物のインターフェースを実装する
func (b * bird) Walk(){
fmt.Println("bird: walk")
}
// ブタを定義する
type pig struct{
}
// ブタにWalk()メソッドを追加し、歩行動物のインターフェースを実装する
func (p * pig) Walk(){
fmt.Println("pig: walk")
}
func main(){
// 名前とインスタンスのマッピングを作成する
animals := map[string]interface{}{
"bird": new(bird),
"pig": new(pig),
}
// マッピングを反復処理する
for name, obj := range animals {
// オブジェクトが飛行動物かどうかを判断する
f, isFlyer := obj.(Flyer)
// オブジェクトが歩行動物かどうかを判断する
w, isWalker := obj.(Walker)
fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker)
// 飛行動物の場合は、飛行動物インターフェイスを呼び出す
if isFlyer {
f.Fly()
}
// 歩行動物の場合は、歩行動物インターフェースを呼び出す
if isWalker {
w.Walk()
}
}
}
コードの説明は次のとおりです。
- 6 行目は、飛行する動物のインターフェイスを定義します。
- 11 行目は、動物が歩くためのインターフェイスを定義します。
- 16 行目と 30 行目はそれぞれ鳥と豚の 2 つのオブジェクトを定義し、それぞれ飛行動物と歩行動物のインターフェイスを実現しています。
- 行 41 はオブジェクト名をオブジェクト インスタンスにマッピングするマップです。例としては鳥と豚があります。
- 47 行目はマップの走査を開始します。obj はインターフェース タイプです。{}
- 50 行目では、型アサーションを使用して f を取得し、型 Flyer と isFlyer のアサーションが成功します。
- 52 行目では、型アサーションを使用して w を取得し、型 Walker と isWalker のアサーションが成功します。
- 57 行目と 62 行目は、飛行動物と歩行動物の両方についてアサーションが成功したかどうかに応じて、そのインターフェイスを呼び出します。
コード出力は次のとおりです。
name: pig isFlyer: false isWalker: true
pig: walk
name: bird isFlyer: true isWalker: true
bird: fly
bird: walk
コード 1 では、インターフェイスを共通のポインター型に変換できます。たとえば、Walker インターフェイスを *pig タイプに変換するには、次のコードを参照してください。
p1 := new(pig)
var a Walker = p1
p2 := a.(*pig)
fmt.Printf("p1=%p p2=%p", p1, p2)
コードの説明は次のとおりです。
- 3 行目では、pig は Walker インターフェイスを実装しているため、暗黙的に Walker インターフェイス タイプに変換して、a に格納できます。
- 4行目では、aに格納されているのは*pigオントロジーなので、*pig型に変換できます。
- 6 行目では、p1 と p2 のポインタが同じであることが比較により示されています。
上記のコードで Walker 型を *bird 型に変換しようとすると、実行時エラーが発生します。次のコードを参照してください。
p1 := new(pig)
var a Walker = p1
p2 := a.(*bird)
実行時のエラー:
panic: interface conversion: main.Walker is *main.pig, not *main.bird
エラー メッセージは、インターフェイスが変換されると、main.Walker インターフェイスの内部ストレージが *main.bird ではなく *main.pig になることを意味します。
したがって、インターフェイスを別の型に変換する場合、インターフェイスに格納されているインスタンスに対応する型ポインタが、変換対象の対応する型ポインタである必要があります。
要約する
インターフェースやその他の型への変換は、完全に実装されていれば、Go 言語で自由に行うことができます。
インターフェイス アサーションは、フロー制御の場合と似ています。ただし、多数の型アサーションが表示される場合は、より効率的な型分岐切り替え機能を使用する必要があります。