ホーム プログラミング言語 golang golang interface Go 言語のインターフェースと型の間の変換

Go 言語のインターフェースと型の間の変換


 
 
Go 言語では、インターフェイスを別のインターフェイスに変換するためにインターフェイス アサーション (型アサーション) が使用され、インターフェイスを別の型に変換することもできます。インターフェイスの変換は開発において非常に一般的であり、非常に頻繁に使用されます。

 

型アサーションの形式

型アサーションは、インターフェイス値に対して実行される操作です。構文的には、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 言語で自由に行うことができます。

インターフェイス アサーションは、フロー制御の場合と似ています。ただし、多数の型アサーションが表示される場合は、より効率的な型分岐切り替え機能を使用する必要があります。

 

「 Go 言語のインターフェースと型の間の変換」についてわかりやすく解説!絶対に観るべきベスト2動画

一緒に学ぶGo言語入門 ~その9:メソッドとインターフェース(前編)~
Go (Golang) io.Reader インターフェイス