20.4. Niejawne konwersje

Składowe klas, cech lub obiektów mogą definiować tak zwane niejawne konwersje z typu A na typ B. Mogą to być następujące rodzaje składowych pod warunkiem, że są zdefiniowane z użyciem modyfikatora implicit:

Niejawne konwersje mogą być użyte w jednej z trzech sytuacji. Pierwsza taka sytuacja występuje, gdy wyrażenie a jest typu A, ale jest użyte w miejscu, w którym spodziewane jest wyrażenie typu B. W poniższym przykładzie wyrażenie 1 (typu Int) jest użyte w miejscu, w którym spodziewane jest wyrażenie typu String.

scala> var b: String = 1
<console>:10: error: type mismatch;
 found   : Int(1)
 required: String
       var b: String = 1
                       ^

Z powodu niedopasowania typów przypisanie wartości 1 do zmiennej b nie udaje się. Przypisanie powiedzie się, gdy jawnie przekształcimy liczbę 1 na odpowiadający jej łańcuch znaków za pomocą metody toString, jak w poniższym przykładzie.

scala> var b: String = 1.toString
b: String = 1

Tym razem przypisanie powiodło się, gdyż wyrażenie po prawej stronie znaku równości odpowiada typowi zmiennej definiowanej po lewej stronie. Taka jawna konwersja nie jest konieczna, jeśli dysponujemy niejawną konwersją z typu Int na typ String. Definicja takiej konwersji może wyglądać następująco.

scala> implicit val convIntToString = (x: Int) => x.toString
convIntToString: Int => String = <function1>

Dysponując taką konwersją niejawną możemy zmienić wartość zmiennej b bez uciekania się do konwersji jawnej.

scala> b = 1
b: String = 1

Drugą sytuacją, w której może zostać użyta niejawna konwersja, jest przypadek wyrażenia a.b, w którym a jest typu A, natomiast b nie jest składową typu A. Konwersja może być zastosowana do wyrażenia a przed odwołaniem do składowej b, w celu konwersji wartości typu A na wartość typu B. Warunkiem koniecznym jest istnienie niejawnej konwersji z typu A na typ B taki, że typ B zawiera składową o nazwie b. Ilustruje to następny przykład. Następującego wyrażenia nie udaje się wywołać, gdyż w klasie Double (literał 123.45 jest typu Double) nie istnieje metoda length.

scala> (123.45).length
<console>:12: error: value length is not a member of Double
       (123.45).length
                ^

Zdefiniujmy konwersję z typu Double na typ String. Obiekty typu String posiadają metodę length.

scala> import language.implicitConversions
import language.implicitConversions

scala> implicit def convDoubleToString(x: => Double):String = x.toString
convDoubleToString: (x: => Double)String

Tym razem konwersja zdefiniowana jest nie za pomocą funkcji, jak poprzednio w przypadku konwersji z Int na String, tylko za pomocą metody. Użycie takiej formy wymaga aktywowania flagi funkcjonalności implicitConversions, co zostaje zrobione za pomocą klauzuli importu. Ponadto parametr metody jest przekazywany przez nazwę, a nie przez wartość. Podane wcześniej warunki dopuszczają taką postać definicji niejawnej konwersji. Teraz możemy wywołać poprzednie wyrażenie nie otrzymując komunikatu o błędzie.

scala> (123.45).length
res1: Int = 6

To wyrażenie odpowiada następującemu wyrażeniu, które jawnie przekształca wartość typu Double na wartość typu String i dopiero na tej przekształconej wartości wywołuje metodę length.

scala> ((123.45).toString).length
res2: Int = 6

Trzecią sytuacją, w której może zostać użyta niejawna konwersja, jest przypadek wyrażenia a.b(…), w którym a jest typu A, natomiast b jest nazwą składowej (składowych) istniejącej (istniejących) w typie A, ale z których żadna nie pasuje do argumentów użytych w wyrażeniu. Warunkiem koniecznym jest istnienie niejawnej konwersji z typu A na typ B taki, że typ B zawiera składową o nazwie b, która pasuje do użytych w wywołaniu argumentów. Ilustruje to następny przykład. Następującego wyrażenia nie udaje się wywołać, gdyż w klasie List nie istnieje metoda mkString przyjmująca dwa argumenty typu Char.

scala> List(1,2,3,4).mkString('|',',')
<console>:14: error: overloaded method value mkString with alternatives:
  => String <and>
  (sep: String)String <and>
  (start: String,sep: String,end: String)String
 cannot be applied to (Char, Char)
       List(1,2,3,4).mkString('|',',')
                     ^

Istnieje za to metoda mkString przyjmująca jeden argument. Istnieje także wersja przyjmująca trzy argumenty.

scala> List(1,2,3,4).mkString(",")
res4: String = 1,2,3,4

scala> List(1,2,3,4).mkString("[",",","]")
res5: String = [1,2,3,4]

Poniższa metoda definiuje konwersję listy na obiekt zawierający dwuargumentową metodę mkString.

scala> implicit class MkStringView(list: List[_]) {
     |   def mkString(a: Char, b: Char) =
     |     list.mkString(a.toString,b.toString,a.toString)
     | }
defined class MkStringView

Po zdefiniowaniu takiej konwersji, wywołanie metody mkString z dwoma argumentami, które wcześniej nie działało, teraz zostaje wykonane.

scala> List(1,2,3,4).mkString('|',',')
res6: String = |1,2,3,4|

Specyfikacja języka Scala opisuje niejawne konwersje w punkcie 7.3.

Obiekt scala.Predef, którego składowe są standardowo importowane w programach Scali, zawiera definicje wielu niejawnych konwersji, co umożliwia na przykład wykonywanie poniższych wyrażeń.

scala> val r:Range = 1 to 5
r: Range = Range(1, 2, 3, 4, 5)

scala> val a = (-1).abs
a: Int = 1

scala> val b = 3 max 4
b: Int = 4

scala> 123.toHexString
res7: String = 7b

scala> "hello" -> "world"
res8: (String, String) = (hello,world)

Klasa Range reprezentuje przedział liczb całkowitych. Metoda -> może być wywoływana nie tylko na napisach, ale na dowolnych wartościach.

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.