Scala: Abstract Types

The basic idea of abstract typing in Scala is leaving undefined some types when implementing a new abstract class. This types will be defined on subclasses. Additionally, you can specify some restrictions about the hierarchy of this unknown type as: Must be subclass of or Must be superclass of. This is called lower and upper bounds.
To show the potential of this tools, the same example will be developed with four different approaches. The following objects must be modelled:

  • Generic trash.
  • Different kinds of trash, must inherit from the abstract one.
  • An abstract recycling container. Must contain common logic.
  • Different kinds of recycling containers.
  • At least one recycling container must inherit from a non-abstract one.

Simple and buggy solution

Here there is the implementation of the trash and its hierarchy. There is one generic trash called Trash, and two specializations Paper and Glass. Finally, there’s a subtype of Glass, which is WhiteGlass.

public class Trash{} public class Paper extends Trash{} public class Glass extends Trash{} public class WhiteGlass extends Glass{}

This is the simplest way to implement a generic recycling container. It should contain some logic which is not relevant for this example. The main method takes an object of type Trash. This decision will be revealed as simply wrong when implementing the subclasses.

public abstract class RecyclingContainer{ public abstract void fill(Trash trash); //some common logic. }

When creating the subtype for paper container, we realize that the previous abstract class was a bad choice. We can’t override the method taking Paper instead of Trash.

public class PaperContainer extends RecyclingContainer{ @Override public void fill(Paper paper){} }

The only thing that can make this class compile is creating a method fill that takes Trash.

public class RecyclingContainer{ public void fill(Trash trash){} }

Obviously, this solution is very buggy. In only two lines we can do something forbidden.

PaperContainer cont = new PaperContainer(); cont.fill(new Glass()); //glass in a paper container!!!

Java Generics Solution I

As using a simple solution is not working, the complexity of the code must be increased. We will create a class using generics. We want an abstract recycling container being able to carry all types of trash. This class will use Generics and the method fill will take an object of type E (any class inheriting from Trash).

public abstract class RecyclingContainer<E extends Trash>{ public abstract void fill(E trash); //some common logic. }

PaperContainer and GlassContainer will extend this abstract class delimiting the genetic type E to the proper kind of trash.

public class PaperContainer extends RecyclingContainer<Paper>{ public void fill(Paper paper){} } public class GlassContainer extends RecyclingContainer<Glass>{ public void fill(Glass glass){} }

However, this solution could not accomplish the 5th goal.

public class WhiteGlassContainer extends GlassContainer{ public void fill(WhiteGlass whiteGlass){} //won't compile }

Java Generics Solution II

Increasing again the complexity of this solution we reach the most robust option.
We will keep RecyclingContainer as it is but we will modify its subclasses. This new subclasses will lay also on the use of generics. Every fill method will take an object of the undefined type E.

public class PaperContainer<E extends Paper> extends RecyclingContainer<E>{ public void fill(E paper){} } public class GlassContainer<E extends Glass> extends RecyclingContainer<E>{ public void fill(E glass){} } public class WhiteGlassContainer<E extends Glass>; extends GlassContainer<E>{ public void fill(E whiteGlass){} }

Using this classes in a correct way, it’s easy to recycle properly:

//with a glass container RecyclingContainer<Glass> container = new GlassContainer<Glass>(); //you can add glass container.fill(new Glass()); //and white glass container.fill(new WhiteGlass()); //but no paper container.fill(new Paper()); //won't compile //and you can't "dress" a glass container like a generic one... RecyclingContainer<Trash> nonComp = new GlassContainer<Glass>(); //won't compile

However, if the developer of the client side doesn’t use generics properly, some forbidden actions can happen in runtime.

//he haven't heard about Generics! RecyclingContainer buggy = new PaperContainer(); buggy.fill(new Paper()); //and he managed to ruin the recycling plant buggy.fill(new Glass());//compiles, but throws a ClassCastException

Scala Approach

Scala provides a more robust solution to this problem. The abstract container will be created with an unknown type, called SuitableTrash in this case.

abstract class RecyclingContainer{ type SuitableTrash <: Trash def fill(trash : SuitableTrash) } class PaperContainer extends RecyclingContainer{ type SuitableTrash = Paper def fill(trash : Paper) {} } class GlassContainer extends RecyclingContainer{ type SuitableTrash = Glass def fill(trash : Glass) {} } class WhiteGlassContainer extends RecyclingContainer{ type SuitableTrash = WhiteGlass def fill(trash : WhiteGlass) {} }

This solution is safer and even inexperienced developers can’t break the logic of these classes.
* As Volker noticed, there was a problem in the code pasted, WhiteGlassContainer must extend RecyclingContainer. This means that we can’t accomplish last point of the requirements. Suggestions are welcome.

//You can fill the glass container with all types of glass val container = new GlassContainer container fill (new Glass) container fill (new WhiteGlass) //but no with paper container fill (new Paper) //won't compile //Trying to John Buggy's issue val buggy : RecyclingContainer = new GlassContainer buggy fill (new Paper) //won't compile //however neither this won't compile buggy fill (new Glass) //won't compile

In conclusion, Java provides powerful tools for checking types and avoid runtime exceptions. However, they are only applied if the developer coding the client class is programming using them. Additionally, Java is verbose enough to make things a little more complicated. Scala instead, offers a simpler and cleaner solution for the same problem which is safer than the Java one.