5

Back2Basics: The Story of Trait – Part 2

 3 years ago
source link: https://blog.knoldus.com/back2basics-the-story-of-trait-part-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Back2Basics: The Story of Trait – Part 2

Reading Time: 3 minutes

In our previous blog The Story of Trait – Part 1, we have discussed how traits are the just regular interfaces at the very basic form. In this blog, we will explore about traits can have method implementation too.

We will continue with our previous example and modify it and see how trait behaves in different scenarios.

https://gist.github.com/mahesh2492/912fa3da297660afc96c657815610e2d.js

trait Animal{

def speak = println("speaking..")

def walk = println("walking fastly..")

def comeToMaster: Unit

}

class Cat extends Animal{

override def speak: Unit = println("meow….")

def comeToMaster = println("catch me if you can..")

}

object Sample extends App{

val kity = new Cat

kity.speak

kity.walk

kity.comeToMaster

}

Here we have implemented two method and other method is abstract in trait Animal and class Cat will extend it and give an implementation of abstract method comeToMaster. Let’s see how trait looks now under the hood.

If you take a trait with an implementation in it, it quickly becomes two things instead of one i.e an interface and an abstract class as well.

After compiling Sample.scala, use ls for .class files. And you will notice there will be two class files for Animal i.e  Animal.class, Animal$class.class.

If you take a look at them, Animal class will have an interface and Animal$class will have abstract base class. Let see first Animal.class, that should be an interface.

xxxxxxxxxx
javap -c Animal.class
Compiled from "Sample.scala"
public abstract class Animal$class {
  public static void speak(Animal);
    Code:
       0: getstatic     #13                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: ldc           #15                 // String speaking..
       5: invokevirtual #19                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
       8: return
  public static void walk(Animal);
    Code:
       0: getstatic     #13                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: ldc           #24                 // String walking fastly..
       5: invokevirtual #19                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
       8: return
  public static void $init$(Animal);
    Code:
       0: return
}

Oh! , wait a minute, this does not show the interface, it is an abstract class. And if you see the Animal$class it will show the same bytecode as above. What exactly is happening?

Let’s dig deeper, we are being fooled by the tool. Let’s remove the Animal$class.class and now we are having only Animal.class.

xxxxxxxxxx
javap -c Animal.class
Compiled from "Sample.scala"
public interface Animal {
  public abstract void speak();
  public abstract void walk();
  public abstract void comeToMaster();
}

Now you can see how the tool is fooling us. In Animal.class file, there is an interface. And All methods become abstract in interface i.e speak, walk, comeToMaster. We know that speak and walk have already implemented and the only comeToMaster is truly abstract in a trait.

What exactly happens at the bytecode level?

trait Animal{

def speak: Unit

def walk: Unit

def comeToMaster: Unit

}

abstract class AnimalClass extends Animal{

def speak = println("speaking..")

def walk = println("walking fastly..")

}

class Cat extends Animal{

override def speak: Unit = println("meow….")

def comeToMaster = println("catch me if you can..")

}

object ByteCode extends App{

val kity = new Cat

kity.speak

kity.walk

kity.comeToMaster

}

We can think about it slightly differently, we can provide our implemented method in abstract AnimalClass. In other words, when we give the implementation in a trait.

What Scala does under the hood, it moves an implementation to an abstract base class and keeps the trait as an interface all the way.

Let’ see the bytecode of Cat class,

xxxxxxxxxx
Compiled from "Sample.scala"
public class Cat implements Animal {
public void walk();
Code:
0: aload_0
1: invokestatic #17 // Method Animal$class.walk:(LAnimal;)V
4: return
public void speak();
Code:
0: getstatic #26 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #28 // String meow....
5: invokevirtual #32 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: return
public void comeToMaster();
Code:
0: getstatic #26 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #35 // String catch me if you can..
5: invokevirtual #32 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: return
public Cat();
Code:
0: aload_0
1: invokespecial #38 // Method java/lang/Object."":()V
4: aload_0
5: invokestatic #41 // Method Animal$class.$init$:(LAnimal;)V
8: return
}

Notice, Cat class implements the Animal trait. However, it does implement the walk method. walk method makes an invokestatic call to walk method of abstract class Animal$class. Since we have overridden method speak in Cat, so it won’t make an invokestatic call to an abstract class.

So, we saw how trait behaves when it has an implemented methods also and how things work under the hood. In our next blog The Story of Trait – Part 3, we will see the mixin of traits. Till then Stay tuned 🙂

Please feel free to suggest and comment.

References:

Scala CookBook






About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK