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に入れるのは誤りである.