Traits in Scala- Deep Dive

Traits are units of code reuse in Scala. Traits encapsulates methods and field definitions. Their role is similar to that of interfaces in Java- A workaround for Multiple Inheritance. But unlike Interfaces they can have method and field definitions. More features will be explained in the article.

Defining Traits:

A trait definition looks like a class definition but uses the keyword “trait.

trait Bounceable{

     def bounce():String="Bounce Method in Trait"

}

The above declared trait has a concrete method. The ability to declare concrete methods in an trait gives default implementation option. In interfaces one has to write/copy-paste the default declaration in each class which implements the interface or declare a class that implements that interface and let other classes extend the concrete class. The first method has code repition and the second method has the restriction that a class can extend only one class. So this feature of “trait” stands out best.

As in Java a class implements interface, in Scala classes traits are mixed into a class. This is done using either extends or “with” keywords.

class Ball(s: Int) extends Bouncable{

      var size:Int

      override def toString():String= Integer.toString(size)

}

In the above example we are using extends to mix in the trait- Here Ball is implicitly inherits trait’s superclass which is “AnyRef”. Lets try to use the class Ball to test the mixed in trait.

object Test{

           def main(args:Array[String])={

                    var ball = new Ball(33)

                    var ball2:Bounceable= new Ball(400)

                    println(ball.bounce()) //This prints "Bounce Method in Trait"

                    println(ball) //This prints "33"

                    println(ball2.bounce())

                    println(ball2)

         }

}

Note: Output written as comment.

Regarding the ball2 instance being declared as type “Bounceable”. Its a valid declaration cause supertype references can refer to the instances of its subtypes. But they cannot access the methods/fields declared by the subtypes (different with the overriding case).

Lets consider a more better example which extends a class and mix in multiple traits.

Modifying the Ball class a bit:

class Ball(s:Int){

      var size:Int=s;

      override def toString():String="Size of Ball: "+Integer.toString(size)

}

class Basketball(s:Int) extends Ball(s) with Bounceable{

      override def bounce():String="Basketball is bouncing"

      override def toString():String=" Basketball of size: "+size

}

object Test{

      def main(args:Array[String])={ 

            var basketBall= new BasketBall(400)

            println(basketBall)

            println(basketBall.bounce)

      }

}

In the above example class Ball is a super class which the class Basketball is extending and the class Basketball mixes in with Bounceable. The concrete method in Bouncebale is overridden in Basketball. Though the trait may look like Java Interface there are subtle differences. Traits can maintain state by declaring fields. Traits look more or like class with a few exceptions. The traits cannot have class parameters i.e parameters passed to the primary contructors of a class. For example-

  • we can have class Ball(x:Int) but not trait Ball(x:Int)
  • super call is bound statically but in traits its bound dynamically. This can be used to create a stackable trait.

Let me tell u about Rich and Thin interfaces before contiuning further-

  • Rich Interfaces- Too many methods => Easy for client but difficult for implementer
  • Thin interfaces- Few mehtods =>Easy for implementer but difficult for client as he has to write more code to get the desired output.

Traits can be used to create Rich Interfaces which are Thin, means traits can have lot of methods- Many of them are implemented in terms of the few unimplemented methods. So the class which mixes these traits provides the implementation for the few unimplemented methods. The end result being whole set of methods making it easy for the implementer and the client as well.

Lets look at one such trait “Ordered”. Suppose u want to create a custom data value and want to provide comparision operators, we would be busy writing methods like lessThan(<), greaterThan(>), lessThanEqualto(<=) and so on. Instead scala provides “Ordered” trait. Extending this trait makes the work lot easier.

In the example below i have created a custom MyRectangle class and extended the trait Ordered. I would be comparing the rectangles based on the “area” of the rectangle. Greater the area, larger is the rectangle.

class MyRectangle(w:Int, h:Int) extends Ordered[MyRectangle]{

      val width:Int=w

      val height:Int=h

      override compare(that:MyRectangle):Int=

            (this.width*this.height)-(that.width*that.height)

}

object MyRectangleTest{

      def main(args:Array[String])={

            val myRect1 = new MyRectanlge(3,4)

            val myRect2 = new MyRectanlge(4,5)

            val res= if(myRect1 &gt; myRect2){ myRect1 } else { myRect2 }

            println(res)

      }

}

One should carefully observe the compare() method. If the difference is positive then the receiver is greater than the argument else the argument is greater. Also note that the type of the objects being compared is passed as the type parameter along with the trait name. Here we have found the difference between the areas of the rectangle. The Ordered trait does not provide the equals method.

There’s one more usage of traits called Stackable traits. I would be writing about it in another post.

Leave a Reply

Discover more from Experiences Unlimited

Subscribe now to keep reading and get access to the full archive.

Continue reading