Parameterized Types

As we’ve seen, not only is extending a class concise in Scala, but it’s also streamlined to alleviate some common programming errors. Generic or parameterized types further help to create classes and functions that can work with multiple different types. The types can be specified at compile time instead of code writing time, making the code more extensible without losing type safety.

Since in Scala you can create stand-alone functions, you can create parameterized functions as well. Let’s create one:

WorkingWithObjects/Parameterized.scala
 
def​ echo[T](input1: T, input2: T) =
 
println(s​"got $input1 (${input1.getClass}) $input2 (${input2.getClass})"​)

Rather than specifying the echo function’s parameter types to be some type like Int or String, we left them open as parametric type T for the programmer to decide. The notation [T] signals to the compiler that the type T that follows is not some existing, poorly named, single-letter class but is a parameterized type.

You’d call this function much like any function, but the types of the parameters are decided at this time. Let’s call the echo function using two different types of arguments:

WorkingWithObjects/Parameterized.scala
 
echo(​"hello"​, ​"there"​)
 
echo(4, 5)

We passed Strings in the first call, and then Ints in the second call. The compiler accepts these with no complaint and synthesizes the function, tailored to the type of the argument. Let’s see the output:

 
got hello (class java.lang.String) there (class java.lang.String)
 
got 4 (class java.lang.Integer) 5 (class java.lang.Integer)

Since we used the same type T for both the parameters, Scala will require that the arguments be of the same type. There is one gotcha, however. All types in Scala derive from Any, which we’ll see in Chapter 5, Making Use of Types. The following call will, unfortunately, work:

WorkingWithObjects/Parameterized.scala
 
echo(​"hi"​, 5)

The result of this call shows:

 
got hi (class java.lang.String) 5 (class java.lang.Integer)

If you want to prevent fellow programmers from mixing arguments of different types during refactoring, you can place a directive, like so:

 
echo[​Int​](​"hi"​, 5) ​//error: type mismatch

In this case the compiler will insist that both the arguments are of type Int. This shows that it’s hard to reliably enforce that the two parameters are of the same type.

If your intention was to receive two different types of arguments, then you can express that more clearly, like in the following example:

 
def​ echo2[T1, T2](input1: T1, input2: T2) =
 
println(s​"received $input1 and $input2"​)
 
 
echo2(​"Hi"​, ​"5"​)

Creating parameterized classes is just as easy as creating parameterized functions. Let’s create a Message class that again postpones the type of its field:

WorkingWithObjects/Parameterized.scala
 
class​ Message[T](​val​ content: T) {
 
override​ ​def​ toString = s​"message content is $content"
 
 
def​ is(value: T) = value == content
 
}

The type of the field content is parameterized and will be decided per instance of the class. The same is true for the type of the parameter of the is method. Unlike the stand-alone function, we did not have to place the notation [T] in the definition of the is method. If the method were to receive a parameterized type other than the parameterized type T specified at the class level, then we’d have to use that notation for the same reasons as mentioned before.

Let’s create a few instances of the Message class and invoke the is method:

WorkingWithObjects/Parameterized.scala
 
val​ message1 : Message[​String​] = ​new​ Message(​"howdy"​)
 
val​ message2 = ​new​ Message(42)
 
 
println(message1)
 
println(message1.is(​"howdy"​))
 
println(message1.is(​"hi"​))
 
println(message2.is(22))

We explicitly specified the type for the first variable message, but let Scala infer the type for message2. Unlike Java, Scala does not permit raw types; providing the type as Message instead of Message[String] in the definition of message1 will be an error. Either provide the full details of the parameterized type or let Scala infer it for you.

Once we created the two instances we implicitly call the toString method in the println call and also invoke the is method a few times. Let’s see the response:

 
message content is howdy
 
true
 
false
 
false

The parameterized type is specialized when the instances are created. If you try to send an incorrect type, you will receive a stern error, like in this example:

 
message1.is(22) ​//error: type mismatch

Sadly, the following code will not produce any error—the character input is quietly converted to the compatible Int type. Don’t rely too much on the type checking; use caution:

 
message2.is('A') ​//No error!

The Message class is defined to take one parameterized type. In general, a class can accept multiple parameterized types, much like the echo2 method received two.

In Java angle brackets (<>) are used to specify the generic types. We saw how in Scala, square brackets ([]) are used instead. That’s not the only difference, however. Unlike Java where type erasure make generics rather weak, Scala performs more rigorous type checking on parameterized types—we will see this in Type Inference for Generics and Collections. Also, we can place constraints on the parameterized types—we will explore this in Variance of Parameterized Type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.144.69