Goベースシリーズ:インタフェースタイプ断言とtype-switch

4567 ワード

インタフェースタイププローブインタフェースタイププローブ:タイプブレークスルー


インタフェースインスタンスには、実装インタフェースのタイプインスタンスが格納されます.タイプインスタンスには、値タイプインスタンスとポインタタイプインスタンスの2つがあります.プログラムの実行中に、インタフェースインスタンスが格納するインスタンスタイプが動的に変更される可能性があります.例:
// ins 
var ins Shaper

// ins 
ins = c1

//  ...
...

// ins , 
ins = c2

//  ...

// ins 
ins = s1

したがって、プローブインタフェースインスタンスに格納される値タイプかポインタタイプかを検出する必要がある.
プローブの方法は、ins.(Type)およびins.(*Type)である.これらには2つの戻り値があり、2番目の戻り値はok戻り値、ブールタイプ、1番目の戻り値は検出されたタイプです.また、検出されたタイプの戻り値は1つしかありません.
//  ins Type, 
if t, ok := ins.(Type); ok {
    fmt.Printf("%T
", v) } // ins *Type, if t, ok := ins.(*Type); ok { fmt.Printf("%T
", v) } // t := ins.(Type) t := ins.(*Type)

次の例を示します.
package main

import "fmt"

// Shaper  
type Shaper interface {
    Area() float64
}

// Square struct 
type Square struct {
    length float64
}

// Square Shaper Area()
func (s Square) Area() float64 {
    return s.length * s.length
}

func main() {
    var ins1, ins2 Shaper

    //  
    s1 := new(Square)
    s1.length = 3.0
    ins1 = s1
    if v, ok := ins1.(*Square); ok {
        fmt.Printf("ins1: %T
", v) } // s2 := Square{4.0} ins2 = s2 if v, ok := ins2.(Square); ok { fmt.Printf("ins2: %T
", v) } }

上記の2つのPrintfは、タイプ判定がtrueを返すため出力されます.ins2.(Square)ins2.(*Square)に変更すると、ins 2は値タイプのインスタンスを保存しているため、2番目のPrintfは出力されません.
出力結果は次のとおりです.
ins1: *main.Square
ins2: main.Square

特にinsはインタフェースインスタンスであることを明確にしなければならない.たとえば、次の2つの宣言は有効であり、3つ目の推定タイプはインタフェースインスタンスである可能性があり、タイプのインスタンスコピーである可能性があるため、エラーです.
var ins Shaper     //  
ins := Shaper(s1)  //  
ins := s1          //  

insがインタフェースインスタンスであると判断できない場合、ins.(Square)などのテストに使用します.
invalid type assertion:ins.(Square) (non-interface type (type of ins) on left)

左側のinsが非インタフェースタイプ(non-interface type)であることを示した.
一方、インタフェースタイプ断言(ins.(Type))により、Typeがインタフェースタイプであれば、インタフェースインスタンスinsに保存されているタイプもTypeインタフェースを実現しているか否かを判断することができる.例:
var r io.Read
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

var w io.Writer
w = r.(io.Writer)

上のrはioです.Readインタフェースの一例の変数で、その中にttyとそのタイプ、すなわち(tty, *os.File)が保存され、それからrのタイプを断言し、その中のタイプ*Fileもioを実現したかどうかを検出する.Writerインタフェースは、実現するとioに保存される.Writerインタフェースのインスタンス変数wでは、wインスタンスも(tty,*os.File)保存される.
いずれのコンテンツも空のインタフェースを実装するため、常に1つのインタフェースインスタンスを任意の断言で空のインタフェースインスタンスに値を割り当てる必要はありません.
var empty interface{}
empty = w

現在emptyも(tty,*os.File)保存されています.

type Switch構造


switchプロセス制御構造は、インタフェースインスタンスが保存するタイプを検出するためにも使用することができる.この構造をtype−switchと呼ぶ.
使用法は次のとおりです.
switch v := ins.(type) {
case *Square:
    fmt.Printf("Type Square %T
", v) case *Circle: fmt.Printf("Type Circle %T
", v) case nil: fmt.Println("nil value: nothing to check?") default: fmt.Printf("Unexpected type %T", v) }

このうちins.(type)の小文字typeは固定語である.
使用例は次のとおりです.
package main

import (
    "fmt"
)

// Shaper  
type Shaper interface {
    Area() float64
}

// Circle struct 
type Circle struct {
    radius float64
}

// Circle Shaper Area()
func (c *Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}

// Square struct 
type Square struct {
    length float64
}

// Square Shaper Area()
func (s Square) Area() float64 {
    return s.length * s.length
}

func main() {
    s1 := &Square{3.3}
    whichType(s1)

    s2 := Square{3.4}
    whichType(s2)

    c1 := new(Circle)
    c1.radius = 2.3
    whichType(c1)
}

func whichType(n Shaper) {
    switch v := n.(type) {
    case *Square:
        fmt.Printf("Type Square %T
", v) case Square: fmt.Printf("Type Square %T
", v) case *Circle: fmt.Printf("Type Circle %T
", v) case nil: fmt.Println("nil value: nothing to check?") default: fmt.Printf("Unexpected type %T", v) } }

上のtype-switchにcase Circleを付けなかったのは、Circleがポインタタイプのreceiverのみを実現しているため、Method Setによるインタフェースの実装規則によれば、ポインタタイプのCircle例のみがインタフェースShaperを実現しているので、値タイプの例case Circleをtype-switchに入れるのは誤りである.