ฉันต้องการรับประเภทของตัวแปรที่รันไทม์


คำตอบ:


134

ดังนั้นพูดอย่างเคร่งครัด "ประเภทของตัวแปร" จะปรากฏอยู่เสมอและสามารถส่งผ่านเป็นพารามิเตอร์ประเภทได้ ตัวอย่างเช่น:

val x = 5
def f[T](v: T) = v
f(x) // T is Int, the type of x

แต่ขึ้นอยู่กับสิ่งที่คุณต้องการทำนั่นจะไม่ช่วยคุณ ตัวอย่างเช่นอาจไม่ต้องการทราบว่าประเภทของตัวแปรคืออะไร แต่ต้องการทราบว่าประเภทของค่าเป็นประเภทเฉพาะเช่นนี้หรือไม่:

val x: Any = 5
def f[T](v: T) = v match {
  case _: Int    => "Int"
  case _: String => "String"
  case _         => "Unknown"
}
f(x)

ที่นี่ไม่สำคัญว่าตัวแปรจะเป็นประเภทAnyใด สิ่งที่สำคัญสิ่งที่ตรวจสอบคือประเภทของ5ค่า ในความเป็นจริงTไม่มีประโยชน์ - คุณอาจเขียนมันdef f(v: Any)แทนก็ได้เช่นกัน นอกจากนี้สิ่งนี้ใช้อย่างใดอย่างหนึ่งClassTagหรือค่าClassซึ่งอธิบายไว้ด้านล่างและไม่สามารถตรวจสอบพารามิเตอร์ประเภทของประเภท: คุณสามารถตรวจสอบได้ว่าบางสิ่งเป็นList[_]( Listของบางสิ่ง) หรือไม่ แต่ไม่ว่าจะเป็นเช่น a List[Int]หรือList[String].

ความเป็นไปได้อีกประการหนึ่งคือคุณต้องการระบุประเภทของตัวแปรอีกครั้ง นั่นคือคุณต้องการแปลงชนิดเป็นค่าเพื่อให้คุณสามารถเก็บไว้ผ่านมันไปรอบ ๆ ฯลฯ นี้เกี่ยวข้องกับการสะท้อนและคุณจะใช้อย่างใดอย่างหนึ่งหรือClassTag TypeTagตัวอย่างเช่น:

val x: Any = 5
import scala.reflect.ClassTag
def f[T](v: T)(implicit ev: ClassTag[T]) = ev.toString
f(x) // returns the string "Any"

ยังจะช่วยให้คุณใช้พารามิเตอร์ชนิดที่คุณได้รับในClassTag matchสิ่งนี้ใช้ไม่ได้:

def f[A, B](a: A, b: B) = a match {
  case _: B => "A is a B"
  case _ => "A is not a B"
}

แต่สิ่งนี้จะ:

val x = 'c'
val y = 5
val z: Any = 5
import scala.reflect.ClassTag
def f[A, B: ClassTag](a: A, b: B) = a match {
  case _: B => "A is a B"
  case _ => "A is not a B"
}
f(x, y) // A (Char) is not a B (Int)
f(x, z) // A (Char) is a B (Any)

ที่นี่ฉันใช้ไวยากรณ์ขอบเขตบริบทB : ClassTagซึ่งทำงานเหมือนกับพารามิเตอร์นัยในClassTagตัวอย่างก่อนหน้านี้แต่ใช้ตัวแปรที่ไม่ระบุชื่อ

เราสามารถรับ a ClassTagจากค่าได้Classเช่นนี้:

val x: Any = 5
val y = 5
import scala.reflect.ClassTag
def f(a: Any, b: Any) = {
  val B = ClassTag(b.getClass)
  ClassTag(a.getClass) match {
    case B => "a is the same class as b"
    case _ => "a is not the same class as b"
  }
}
f(x, y) == f(y, x) // true, a is the same class as b

A ClassTagมีข้อ จำกัด ที่ครอบคลุมเฉพาะคลาสพื้นฐานเท่านั้น แต่ไม่ครอบคลุมพารามิเตอร์ประเภท นั่นคือClassTagสำหรับList[Int]และเป็นเหมือนกันList[String] Listหากคุณต้องการพารามิเตอร์ประเภทคุณต้องใช้TypeTagแทน TypeTagแต่ไม่สามารถหาได้จากค่าหรือสามารถที่จะนำมาใช้ในการแข่งขันรูปแบบเนื่องจาก JVM ของการลบออก

ตัวอย่างที่มีTypeTagอาจค่อนข้างซับซ้อน - การไม่เปรียบเทียบแท็กสองประเภทนั้นไม่ใช่เรื่องง่ายอย่างที่เห็นด้านล่าง:

import scala.reflect.runtime.universe.TypeTag
def f[A, B](a: A, b: B)(implicit evA: TypeTag[A], evB: TypeTag[B]) = evA == evB
type X = Int
val x: X = 5
val y = 5
f(x, y) // false, X is not the same type as Int

แน่นอนว่ามีหลายวิธีที่จะทำให้การเปรียบเทียบนั้นกลับมาเป็นจริงได้ แต่ต้องใช้หนังสือสองสามบทเพื่อให้ครอบคลุมจริงๆTypeTagฉันจะหยุดตรงนี้

สุดท้ายบางทีคุณอาจไม่สนใจประเภทของตัวแปรเลย บางทีคุณอาจแค่อยากรู้ว่าคลาสของค่าคืออะไรซึ่งในกรณีนี้คำตอบนั้นค่อนข้างง่าย:

val x = 5
x.getClass // int -- technically, an Int cannot be a class, but Scala fakes it

อย่างไรก็ตามจะเป็นการดีกว่าหากเจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณต้องการทำให้สำเร็จเพื่อให้คำตอบตรงประเด็นมากขึ้น


โค้ดตัวอย่างที่คุณเขียนหลัง "แต่สิ่งนี้จะ:" ทำให้สับสน มันรวบรวม แต่ผลลัพธ์ไม่ใช่สิ่งที่คุณแสดงในความคิดเห็น การเรียกทั้งสองให้ผลลัพธ์เหมือนกัน: "A is a B" เพราะค่าที่5เป็นทั้งตัวอย่างของและเป็นตัวอย่างของInt Anyนอกเหนือจากนั้นคำอธิบายของคุณก็สมบูรณ์แบบ :)
อ่านเมื่อ

@ อ่านค่าไม่ได้ทดสอบคลาสคือ Intเป็นAnyแต่ไม่ได้เป็นAny Intมันใช้งานได้กับ Scala 2.10 และควรใช้กับ Scala 2.11 และฉันไม่รู้ว่าทำไมถึงไม่เป็นเช่นนั้น
Daniel C. Sobral

1
มันทำให้ฉันกลัวที่จะขัดแย้งกับโดดเด่นเช่นเดียวกับคุณ แต่รหัสa match { case _: B => ...ทดสอบประเภทของค่าจริงของตัวแปรไม่ประเภทของตัวแปรa aคุณพูดถูกที่มันจะส่งคืนสิ่งที่คุณพูดใน scala 2.10.6 แต่น่าจะเป็นบั๊ก ใน scala 2.11.8 จะมีการทดสอบประเภทของค่าจริงตามที่ควร
Readren

ความครอบคลุมที่ดีมากเกี่ยวกับความแตกต่างระหว่าง ClassTag และ TypeTag สิ่งที่ฉันกำลังมองหา
marcin_koss

มีวิธีการตรวจสอบค่าว่างหรือไม่?
ChiMo

53

ฉันคิดว่าคำถามไม่สมบูรณ์ หากคุณหมายถึงว่าคุณต้องการรับข้อมูลประเภทของคลาสประเภทบางประเภทจากด้านล่าง:

หากคุณต้องการพิมพ์ตามที่ระบุไว้:

scala>  def manOf[T: Manifest](t: T): Manifest[T] = manifest[T]
manOf: [T](t: T)(implicit evidence$1: Manifest[T])Manifest[T]

scala> val x = List(1,2,3)
x: List[Int] = List(1, 2, 3)

scala> println(manOf(x))
scala.collection.immutable.List[Int]

หากคุณอยู่ในโหมดจำลองแล้ว

scala> :type List(1,2,3)
List[Int]

หรือหากคุณต้องการทราบว่าประเภทของชั้นเรียนเป็นอย่างไรตามที่ @monkjack อธิบายไว้"string".getClassอาจช่วยแก้จุดประสงค์ได้


3
สำหรับผู้อ่าน: นี่คือวิธีการแก้ปัญหามีประโยชน์มากที่สุด เช่นเดียวกับใน Javascript typeof xนี่manOf(x)คือประเภทข้อมูล!
Peter Krauss

23

หากตามประเภทของตัวแปรคุณหมายถึงคลาสรันไทม์ของอ็อบเจ็กต์ที่ตัวแปรชี้ไปคุณจะได้รับสิ่งนี้ผ่านการอ้างอิงคลาสที่อ็อบเจ็กต์ทั้งหมดมี

val name = "sam";
name: java.lang.String = sam
name.getClass
res0: java.lang.Class[_] = class java.lang.String

อย่างไรก็ตามหากคุณหมายถึงประเภทที่มีการประกาศตัวแปรคุณจะไม่สามารถรับสิ่งนั้นได้ เช่นถ้าคุณพูด

val name: Object = "sam"

จากนั้นคุณจะยังคงได้รับStringคืนจากรหัสด้านบน


8
คุณสามารถทำname.getClass.getSimpleNameเพื่อให้ได้ผลลัพธ์ที่อ่านได้มากขึ้น
David Arenburg

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.