Chcę uzyskać typ zmiennej w czasie wykonywania


Odpowiedzi:


132

Tak więc, ściśle mówiąc, „typ zmiennej” jest zawsze obecny i może być przekazywany jako parametr typu. Na przykład:

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

Ale w zależności od tego, co chcesz zrobić , to ci nie pomoże. Na przykład może chcieć nie wiedzieć, jaki jest typ zmiennej, ale wiedzieć, czy typ wartości jest określonym typem, na przykład:

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

Tu nie ma znaczenia, jaki jest typ zmiennej Any. Liczy się, co się sprawdza, to rodzaj 5, wartość. W rzeczywistości Tjest bezużyteczny - równie dobrze mógłbyś go napisać def f(v: Any). Ponadto używa to ClassTagalbo wartości Class, które są wyjaśnione poniżej, i nie można sprawdzić parametrów typu: możesz sprawdzić, czy coś jest List[_]( Listczegoś), ale nie, czy jest to na przykład a List[Int]lub List[String].

Inną możliwością jest to, że chcesz zmienić typ zmiennej. Oznacza to, że chcesz przekonwertować typ na wartość, aby móc go przechowywać, przekazywać itd. Wymaga to odbicia, a będziesz używać albo ClassTaglub a TypeTag. Na przykład:

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"

A ClassTagpozwoli ci również użyć parametrów typu, które otrzymałeś match. To nie zadziała:

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

Ale to będzie:

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)

Tutaj używam składni granic kontekstuB : ClassTag , która działa tak samo jak niejawny parametr w poprzednim ClassTagprzykładzie, ale używa anonimowej zmiennej.

Można również uzyskać a ClassTagz wartości Class, na przykład:

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 ClassTagjest ograniczony, ponieważ obejmuje tylko klasę bazową, ale nie obejmuje jej parametrów typu. Oznacza to, że ClassTagdla List[Int]i List[String]jest taka sama List. Jeśli potrzebujesz parametrów typu, musisz TypeTagzamiast tego użyć . TypeTagJednak nie można uzyskać z wartości, ani nie może być stosowany na meczu wzór, ze względu na JVM za skasowaniem .

Przykłady z TypeTagmogą być dość skomplikowane - nawet porównanie dwóch typów tagów nie jest do końca proste, jak widać poniżej:

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

Oczywiście istnieją sposoby, aby to porównanie powróciło, ale naprawdę wymagałoby to kilku rozdziałów w książce TypeTag , więc na tym zakończę.

Wreszcie, być może w ogóle nie obchodzi cię typ zmiennej. Może po prostu chcesz wiedzieć, jaka jest klasa wartości, w takim przypadku odpowiedź jest raczej prosta:

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

Lepiej byłoby jednak bardziej szczegółowo określić, co chcesz osiągnąć, aby odpowiedź była bardziej konkretna.


Przykładowy kod napisany po „Ale to będzie:” jest mylący. Kompiluje się, ale wynik nie jest tym, który pokazujesz w komentarzach. Oba wywołania zwracają ten sam wynik: „A to B”. Ponieważ wartość 5jest zarówno wystąpieniem, jak Inti wystąpieniem Any. Poza tym twoje wyjaśnienie było idealne :)
Readren

@Readren Wartość nie jest testowana, klasa to. Intjest Any, ale Anynie jest Int. Działa na Scali 2.10 i powinno działać na Scali 2.11 i nie wiem, dlaczego tak nie jest.
Daniel C. Sobral,

1
Przeraża mnie zaprzeczenie takiej wzniosłości jak ty, ale kod a match { case _: B => ...testuje typ rzeczywistej wartości zmiennej a, a nie typ zmiennej a. Masz rację, ponieważ zwraca to, co mówisz w scali 2.10.6. Ale to powinien być błąd. W scali 2.11.8 testowany jest typ wartości rzeczywistej, tak jak powinien.
Readren

Bardzo ładne pokrycie różnic między ClassTag i TypeTag, właśnie tego szukałem.
marcin_koss

Czy istnieje sposób, aby to sprawdzić?
ChiMo,

53

Myślę, że pytanie jest niepełne. jeśli chodziło Ci o to, że chcesz uzyskać informacje o typie jakiejś typeklasy, to poniżej:

Jeśli chcesz drukować zgodnie z ustaleniami:

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]

Jeśli jesteś w trybie repl, to

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

Lub jeśli chcesz tylko wiedzieć, jaki typ klasy, to, jak wyjaśnia @monkjack, "string".getClassmoże rozwiązać cel


3
dla czytelników: to najbardziej przydatne rozwiązanie . Podobnie jak w Javascript typeof x, podaj tutaj manOf(x)typ danych!
Peter Krauss

23

Jeśli przez typ zmiennej masz na myśli klasę środowiska wykonawczego obiektu, na który wskazuje zmienna, możesz to uzyskać za pomocą odwołania do klasy, które mają wszystkie obiekty.

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

Jeśli jednak masz na myśli typ, jako że zmienna została zadeklarowana, nie możesz tego uzyskać. Np. Jeśli powiesz

val name: Object = "sam"

wtedy nadal otrzymasz Stringzwrot z powyższego kodu.


8
Możesz także zrobić name.getClass.getSimpleNamedla bardziej czytelnego wyjścia
David Arenburg

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.