Mike Slinn

Classes Part 3

— Draft —

Published 2013-11-06. Last modified 2024-07-20.
Time to read: 6 minutes.

This lecture continues the exploration of Scala classes.

Variable Number of Arguments (varargs)

Sometimes it is desirable to write a method that accepts an arbitrary number of arguments of a certain type, and for one reason or another it is undesirable to provide a collection. If you are a Java programmer, your are familiar with the Java varargs syntax. A Java vararg is presented to the method body as an array. Here is a modified version of Oracle’s Java varargs example that actually runs. This code is provide as courseNotes/src/main/java/VarArgsJava.java.

Java code
import java.awt.Polygon;
import java.awt.Point;
public class VarArgsJava { public static Polygon polygonFrom(Point...points) { Polygon polygon = new Polygon(); for (Point point: points) polygon.addPoint(point.x, point.y); return polygon; }
public static void main(String[] args) { Polygon square = polygonFrom(new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0)); System.out.println("Bounding box of polygon is: " + square.getBounds2D()); } }

Output is:

Output
Bounding box of polygon is: java.awt.Rectangle[x=0,y=0,width=1,height=1]

Scala’s varargs syntax is slightly different, but also easily recognizable: a variable name and type are specified as usual, followed by an asterisk. The asterisk indicates to the compiler that a variable number of values of the specified type can be supplied.

A Scala vararg is made available to the body of the method as a Seq instead of an Array. We’ll learn about Seq in the Collections Overview lecture of the Intermediate Scala course but for now suffice it to say that a Seq is a collection that can be iterated over.

The Scala equivalent of the above Java code is:

Scala code
object VarArgsScala extends App {
  import java.awt.{Polygon, Point}
/** @return a polygon connecting the given points */ def polygonFrom(points: Point*): Polygon = { val polygon = new Polygon() points.foreach { point => polygon.addPoint(point.x, point.y) } polygon }
val square = polygonFrom(new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0)) println(s"Bounding box of polygon is: ${square.getBounds2D}") }

Output is the same as the Java version.

There are two restrictions on when Scala varargs can be used:

  • The vararg parameter must be the last parameter in the method’s parameter list
  • A method with a varargs parameter cannot define default values for any of its parameters

Splatting an Seq or Array

_* is placeholder notation that indicates a Seq[T] or Array[T] should be provided to a method that accepts varargs of type T. We will discuss type parameters in the Parametric Types lecture of the Intermediate Scala course.

For the curious: this placeholder notation tells the compiler to modify the mechanics of this particular method invocation, without modifying the collection. We can use this placeholder notation to rewrite the above invocation of polygonFrom.

Scala code
val points = List(new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0))
val square2 = polygonFrom(points: _*)
println(s"Bounding box of polygon is: ${square2.getBounds2D}")

Output is the same as above.

An asterisk is often referred to as splat in computer science, thus this placeholder syntax is sometimes referred to as placeholder splat.

We will see another usage of this placeholder notation in the Pattern Matching on Collections lecture of the Intermediate Scala course.

Anonymous Classes

Classes that do not have a name are called anonymous classes. Their primary constructor cannot accept any parameters. Here is an example.

Scala code
val adhoc = new {
  def test(predicate: Boolean): String =
    if (predicate) "Yes" else "No"
}

Let’s use this anonymous class.

Scala REPL
scala> adhoc.test(3>2)
res7: String = Yes 

You can also run this from the console by typing.

Shell
$ sbt "runMain animals.Anonymous1"

BTW, anonymous functions are often called lambda functions, or even just lambdas. Anonymous classes can also be created by extending an existing class, trait, or Java interface. We will cover traits in the Scala Traits lecture.

If the definition being extended has any abstract members, they must be implemented in the body of the anonymous class. The syntax is similar to how this is implemented in Java.

Let’s use the REPL to extend java.util.Date by defining an anonymous class that adds a new method called dayAfter.

Scala REPL
scala> import java.util.Date
import java.util.Date
scala>
val myDate = new Date { | def dayAfter = new Date(getTime + 1000L*60L*60L*24L) | } myDate: java.util.Date{def dayAfter: java.util.Date} = Sun Oct 27 22:13:22 PDT 2013
scala>
myDate.dayAfter res1: java.util.Date = Mon Oct 28 22:13:22 PDT 2013

You can also run this from the console by typing.

Shell
$ sbt "runMain animals.Anonymous2"

Here is an example of extending a class with an anonymous class. As we have seen before, the REPL invokes the class’s toString method after creating the instance. The toString method for this subclass concatenates the value returned by the superclass’s toString method with our French frog’s favorite food.

Scala REPL
scala> val grenouille = new Frog4 {
     |   var favoriteFood = "insects"
     |   override def toString = super.toString + s" favorite food=$favoriteFood"
     | }
grenouille: Frog4{def favoriteFood: String; def favoriteFood_=(x$1: String): Unit} = canSwim: true; 4 legs; breathesAir:true; favorite food=insects 

You can also run this from the console by typing:

Shell
$ sbt "runMain animals.Anonymous3"

Anonymous Subclass of an Abstract Class

There is nothing to prevent someone from defining an anonymous class from the Animal abstract class and creating an instance, like this.

Scala REPL
scala> val naughty = new Animal(4, true) {}
naughty: Animal = $anon$1@4c398c80 

This same trick works with traits. The only requirement is that the superclass must not have any abstract methods or properties.

Bad Habit: Defining Too Many Anonymous Classes

It is easy to get in the habit of defining an anonymous class even though a concrete class would be equally convenient. Here are some reasons for why this is a bad habit.

  1. Anonymous classes cannot be reused because each instance is unique.
  2. Anonymous classes take up the same memory space and require the same overhead as concrete classes, so if you created large quantities of them your program will become bloated and slow.
  3. Anonymous classes cannot be tested because each instance is unique.

For example, here we define an anonymous class that extends Frog3 and overrides the standard method toString.

Scala REPL
scala> val frog3b = new Frog3(canSwim=true, 4, breathesAir=true) {
     |   override def toString = s"$canSwim; $numLegs legs"
     | }
res6: Frog3 = true; 4 legs; breathesAir=true
scala>
grenouille.favoriteFood res0: String = insects
scala>
grenouille.canSwim res1: Boolean = true

The good habit is to define a similar concrete class as the anonymous class above. This would be desirable to do if we needed more than one instance, or you wanted to be able to test it.

Scala REPL
scala> class Frog5(override val canSwim: Boolean=true,
                breathesAir: Boolean=true
               ) extends Frog3(canSwim, 4, breathesAir) {
     |   override def toString = s"canSwim: $canSwim; $numLegs legs; breathesAir: $breathesAir"
     | }
defined class Frog5
scala>
val frog5 = new Frog5(canSwim=true, breathesAir=true) frog5: Frog5 = canSwim: true; 4 legs; breathesAir: true

Operator Overloading

Scala allows you to create multiple methods with the same name that accept different parameters.

Let’s define a complex number class that defines binary addition and a unary negation operator. Note how the unary minus sign operator is preceded with unary_ so the compiler knows what we mean.

Scala code
class Complex(val re: Double, val im: Double) {
  def +(another: Complex) =
    new Complex(re + another.re, im + another.im)
def unary_- = new Complex(-re, -im)
override def toString = s"$re + ${im}i" }

Scala allows methods to be defined called unary_!, unary_~, unary_+ and unary_-.

Now let’s use this definition in some complex arithmetic operations.

Scala REPL
scala> val c1 = new Complex(2, 5) + new Complex(1, -2) // infix notation
c1: Complex = 3.0 + 3.0i
scala>
val c2 = new Complex(2, 5).+(new Complex(1, -2)) // same computation using dot notation c2: Complex = 3.0 + 3.0i
scala>
val c3 = -c1 c3: Complex = -3.0 + -3.0i
scala>
val c4 = -new Complex(2, 5) c4: Complex = -2.0, -5.0i
scala>
val c5 = - c1 // space after unary operator is optional c5: Complex = -3.0 + -3.0i
scala>
val c6 = (new Complex(1, -2)).unary_- // this syntax is of academic interest only c6: Complex = -1.0 + 2.0i

Types vs. Classes

This section has been paraphrased from James Iry’s Stack Overflow posting (James worked on the Scala compiler). Some of this explanation goes beyond the material of this course, and some of it goes beyond the material of the Intermediate Scala course.

A static type is a property of a portion of a program that can be statically proven, that is proven correct without running the program. In a statically typed language, every expression has a type whether you write it or not. For instance, in the following Scala snippet, a, b, c, and d have types, a * b has a type, a * b + c has a type and a * b + c - d has a type, even though only x is explicitly annotated with a type.

Scala code
val a = 3
val b = 2.0
val c = 5L
val d = 4.2
val x: Int = a * b + c - d

A Scala class, on the other hand, is just the specification for a set of objects. That specification includes some type information and includes a lot of implementation and representation details such as method bodies and private fields, etc. In Scala a class also specifies some module boundaries.

Many languages have types but don’t have classes and many languages have classes but don’t have (static) types.

There are several observable differences between types and classes. List[String] is a type but not a class. A Scala List is a class, but normally not a type (it’s actually a higher-kinded type). In Java, List is a raw type.

Scala offers structural types (discussed in the Structural Types lecture of the Intermediate Scala course). For example, { def foo: Bar } means any object that has a foo method which returns a Bar. It’s a type, but not a class.

Types can be abstracted using type parameters (discussed in the Parametric Types of the Intermediate Scala course). When you write:

Scala code
def foo[T](x: T) = { /* whatever */ }

then inside the body of foo, T is a type, however T need not be a class.

Scala types can be virtual (i.e. "abstract type members"), but Scala classes can’t be virtual.

Dynamic types are properties of objects that the language runtime automatically checks before performing certain operations. In dynamically typed class-based OO languages there’s a strong correlation between types and classes. The same thing happens on JVM languages such as Scala and Java which have operations that can only be checked dynamically such as reflection and casting. In those languages, type erasure more or less means that the dynamic type of most objects is the same as their class. That’s not true of, arrays whose types are preserved so that the Java runtime can tell the difference between Array[Int] and Array[String].

When you use reflection it is possible to invoke any message on any object. If the object supports that method then everything works out. Thus it makes sense to talk of all objects that can quack like a duck as a dynamic type, even though they are not defined as a class. Scala calls this structural types, and the Python and Ruby communities call this duck typing.

Finally, there are types like Int which don’t have a class as an implementation detail, types like Null and Any which are a bit special but could have classes and don’t, and types like Nothing which doesn’t even have any values let alone a class.


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.