Abstract methods and the varargs annotation

2.1k views Asked by At

Scala provides a @varargs annotation that generates a Java varargs forwarder method, which makes it possible to write something like this:

import scala.annotation.varargs

class Foo {
  @varargs def foo(args: String*): Unit = {
    args.foreach(println)
  }
}

And then to call this method from Java without needing to create a scala.Seq:

Foo foo = new Foo();
foo.foo("a", "b");

Which is pretty nice.

Unfortunately the forwarding part doesn't seem to happen when the method is abstract:

trait Bar {
  @varargs def bar(args: String*): Unit
}

class Baz extends Bar {
  def bar(args: String*): Unit = {
    args.foreach(println)
  }
}

Now if we have this Java code:

Bar bar = new Baz();
bar.bar("a", "b");

We get this exception (at runtime):

java.lang.AbstractMethodError: Baz.bar([Ljava/lang/String;)V

We can confirm the problem with javap:

public interface Bar {
  public abstract void bar(java.lang.String...);
  public abstract void bar(scala.collection.Seq<java.lang.String>);
}

public class Baz implements Bar {
  public void bar(scala.collection.Seq<java.lang.String>);
  public Baz();
}

So nope, the forwarder definitely isn't getting implemented.

Putting the annotation on both bar methods fails to compile:

A method with a varargs annotation produces a forwarder method with the same
signature (args: Array[String])Unit as an existing method.

And of course putting the annotation only on the bar in Baz means we can't use the forwarder from a Bar instance.

This seems like it must be a bug, but it also seems extremely easy to run into, and I'm not seeing anything in the issue tracker. Am I using @varargs correctly? If so, is there a workaround that would make it do what you'd expect here?

2

There are 2 answers

1
Suzan Cioc On

I don't know Scala, but in Java varargs is just an array.

So in Java it will work this way, with one warning:

package tests.StackOverflow.q27052394;

import java.util.Arrays;

public class Runner {

    public interface Bar {
      public abstract void bar(java.lang.String ... ss);
    }

    public static class Baz implements Bar {
      public void bar(java.lang.String[] array) {
          System.out.println(Arrays.toString(array));
      }
    }

    public static void main(String[] args) {

        Bar b = new Baz();

        b.bar("hello", "world");

    }

}

May be if you can fool Scala the same way, you will overcome the bug.

0
hgrey On

One way to achieve what you want is to define interface in Java as follows

interface Bar {
  public void bar(String... args);
}

and then to define the implementation normally in Scala as follows

class Baz extends Bar {
  def bar(args: String*): Unit = {
    args.foreach(println)
  }
}

Scala compiler will recognize that it needs to generate vargs style method and will generate both varargs and Seq implementation.

javap output is as you would expect

public class Baz implements Bar {
  public void bar(scala.collection.Seq<java.lang.String>);
  public void bar(java.lang.String[]);
  public Baz();
}

interface Bar {
  public abstract void bar(java.lang.String...);
}