Ich möchte den Typ einer Variablen zur Laufzeit erhalten

97

Ich möchte den Typ einer Variablen zur Laufzeit erhalten. Wie mache ich das?

ア レ ッ ク ス
quelle

Antworten:

132

Genau genommen ist der "Typ einer Variablen" immer vorhanden und kann als Typparameter weitergegeben werden. Beispielsweise:

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

Aber je nachdem, was Sie tun möchten, hilft Ihnen das nicht weiter. Beispielsweise möchten Sie möglicherweise nicht wissen, um welchen Typ es sich bei der Variablen handelt, sondern ob es sich bei dem Typ des Werts um einen bestimmten Typ handelt, z.

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

Hier spielt es keine Rolle, was der Typ der Variablen ist Any. Was zählt, was überprüft wird, ist die Art 5, der Wert. In der Tat Tist nutzlos - Sie könnten es genauso gut def f(v: Any)stattdessen geschrieben haben . Dies verwendet auch entweder ClassTagoder einen Wert Class, der unten erläutert wird, und kann die Typparameter eines Typs nicht überprüfen: Sie können überprüfen, ob etwas ein List[_]( Listvon etwas) ist, aber nicht, ob es beispielsweise ein List[Int]oder ist List[String].

Eine andere Möglichkeit besteht darin, dass Sie den Typ der Variablen ändern möchten . Das heißt, Sie möchten den Typ in einen Wert konvertieren, damit Sie ihn speichern, weitergeben usw. Dies beinhaltet Reflexion und Sie verwenden entweder ClassTagoder a TypeTag. Beispielsweise:

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"

Mit A ClassTagkönnen Sie auch Typparameter verwenden, für die Sie empfangen haben match. Das wird nicht funktionieren:

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

Aber das wird:

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)

Hier bin ich mit den Kontext Grenzen Syntax B : ClassTag, die ebenso wie die impliziten Parameter in dem vorherigen Werk ClassTagBeispiel, verwendet aber eine anonyme Variable.

Man kann auch ClassTageinen Wert Classwie folgt erhalten:

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 ClassTagist insofern begrenzt, als es nur die Basisklasse abdeckt, nicht jedoch deren Typparameter. Das heißt, das ClassTagfür List[Int]und List[String]ist das gleiche List. Wenn Sie Typparameter benötigen, müssen Sie TypeTagstattdessen a verwenden. A TypeTagkann jedoch aufgrund der Löschung durch JVM weder aus einem Wert erhalten noch für eine Musterübereinstimmung verwendet werden .

Beispiele mit TypeTagkönnen sehr komplex werden - nicht einmal das Vergleichen von zwei Typ-Tags ist nicht ganz einfach, wie unten zu sehen ist:

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

Natürlich gibt es Möglichkeiten, diesen Vergleich wahr werden zu lassen, aber es würde ein paar Buchkapitel erfordern, um wirklich zu behandeln TypeTag, also werde ich hier aufhören.

Schließlich interessiert Sie der Typ der Variablen vielleicht überhaupt nicht. Vielleicht möchten Sie nur wissen, was die Klasse eines Wertes ist. In diesem Fall ist die Antwort ziemlich einfach:

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

Es wäre jedoch besser, genauer zu sagen, was Sie erreichen möchten, damit die Antwort genauer auf den Punkt kommt.

Daniel C. Sobral
quelle
Der Beispielcode, den Sie nach "Aber das wird:" geschrieben haben, ist verwirrend. Es wird kompiliert, aber das Ergebnis ist nicht das, das Sie in den Kommentaren anzeigen. Beide Aufrufe geben das gleiche Ergebnis zurück: "A ist ein B". Weil der Wert 5sowohl eine Instanz Intals auch eine Instanz von ist Any. Abgesehen davon war Ihre Erklärung perfekt :)
Readren
@Readren Der Wert wird nicht getestet, die Klasse ist. Intist Any, aber Anynicht Int. Es funktioniert unter Scala 2.10, und es sollte unter Scala 2.11 funktionieren, und ich weiß nicht, warum es nicht so ist.
Daniel C. Sobral
1
Es macht mir Angst, einer Eminenz wie Ihnen zu widersprechen, aber der Code a match { case _: B => ...testet den Typ des tatsächlichen Werts der Variablen a, nicht den Typ der Variablen a. Sie haben Recht damit, dass es das zurückgibt, was Sie in Scala 2.10.6 sagen. Aber es sollte ein Fehler sein. In Scala 2.11.8 wird der Typ des Istwerts so getestet, wie er sollte.
Readren
Sehr schöne Berichterstattung über Unterschiede zwischen ClassTag und TypeTag, genau das, wonach ich gesucht habe.
marcin_koss
Gibt es eine Möglichkeit, dies auf Null zu setzen?
ChiMo
53

Ich denke, die Frage ist unvollständig. Wenn Sie gemeint haben, dass Sie die Typinformationen einer Typklasse erhalten möchten, gehen Sie wie folgt vor:

Wenn Sie wie angegeben drucken möchten, gehen Sie wie folgt vor:

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]

Wenn Sie sich dann im Repl-Modus befinden

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

Oder wenn Sie nur wissen möchten, was der Klassentyp ist "string".getClass, kann dies den Zweck lösen, wie @monkjack erklärt

Jatin
quelle
3
für Leser: Dies ist die nützlichste Lösung . Wie in Javascript typeof x, hier manOf(x)sagen Sie den Datentyp!
Peter Krauss
23

Wenn Sie unter dem Typ einer Variablen die Laufzeitklasse des Objekts verstehen, auf das die Variable zeigt, können Sie dies über die Klassenreferenz erhalten, über die alle Objekte verfügen.

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

Wenn Sie jedoch den Typ meinen, als den die Variable deklariert wurde, können Sie diesen nicht erhalten. ZB wenn du sagst

val name: Object = "sam"

dann erhalten Sie immer noch eine StringRückmeldung vom obigen Code.

sksamuel
quelle
8
Sie können auch name.getClass.getSimpleNamefür eine besser lesbare Ausgabe tun
David Arenburg
21

Ich habe das getestet und es hat funktioniert

val x = 9
def printType[T](x:T) :Unit = {println(x.getClass.toString())}
SENHAJI RHAZI Hamza
quelle