How do I disambiguate in Scala between methods with vararg and without
Asked Answered
B

5

24

I'm trying to use the java jcommander library from Scala. The java JCommander class has multiple constructors:

 public JCommander(Object object)  
 public JCommander(Object object, ResourceBundle bundle, String... args)   
 public JCommander(Object object, String... args)   

I want to to call the first constructor that takes no varargs. I tried:

jCommander = new JCommander(cmdLineArgs)

I get the error:

error: ambiguous reference to overloaded definition,
both constructor JCommander in class JCommander of type (x$1: Any,x$2: <repeated...>[java.lang.String])com.beust.jcommander.JCommander
and  constructor JCommander in class JCommander of type (x$1: Any)com.beust.jcommander.JCommander
match argument types (com.lasic.CommandLineArgs) and expected result type com.beust.jcommander.JCommander
jCommander = new JCommander(cmdLineArgs)

I've also tried using a named parameter, but got the same result:

jCommander = new JCommander(`object` = cmdLineArgs)

How do I tell Scala I want to call the constructor that doesn't take varargs?

I'm using Scala 2.8.0.

Bloodandthunder answered 22/7, 2010 at 22:13 Comment(0)
P
18

Sorry, I now realize this is a known interoperability problem with Java. See this question and the ticket. The only work around I know of is to create a small Java class just to disambiguate these calls.

Perl answered 22/7, 2010 at 22:50 Comment(0)
S
11

The only Scala solution to this problem that I know involves reflection.

Ambiguous Methods

Let's suppose we have a Java test class:

public class Ambig {
  public Ambig() {}
  public String say(Object o) { return o.toString(); }
  public String say(Object o, String... ss) { return o.toString()+ss.length; }
}

We can get access to the method via reflection directly:

val ambig = new Ambig
val methods = ambig.getClass.getMethods.filter(_.getName == "say")
val wanted = methods.find(_.getParameterTypes.length == 1).get
wanted.invoke(ambig, Some(5)).asInstanceOf[String]

or we can use structural types (which use reflection under the hood) to achieve the same thing with less boilerplate:

def sayer(speaker: { def say(o: Object): String }, o: Object) = speaker.say(o)
sayer(new Ambig, Some(5))

Ambiguous Constructors

Our strategy has to differ because we don't actually have an object to begin with. Let's suppose we have the Java class

public class Ambig2 {
  public final String say;
  public Ambig2(Object o) { say = o.toString(); }
  public Ambig2(Object o, String... ss) { say = o.toString()+ss.length; }
}

The structural types approach no longer works, but we can still use reflection:

val mkAmbig2 = classOf[Ambig2].getConstructors.filter(_.getParameterTypes.length==1)
val ambig = mkAmbig2.head.newInstance(Some(5)).asInstanceOf[Ambig2]
ambig.say   // Some(5)
Stowaway answered 2/6, 2011 at 17:51 Comment(1)
How about ambiguous static method?Arlana
D
5

I think your easiest option is to have a Java class with a factory method to bridge the issue:

package com.beust.jcommander;

public class JCommanderFactory {
    public static createWithArgs(Object cmdLineArgs) {
        return new JCommander(cmdLineArgs);
    }
}

Alternatively you could use http://jewelcli.sourceforge.net/usage.html instead. JewelCli has an unambiguous factory method for the same purpose and also uses PICA (Proxied Interfaces Configured with Annotations) technique http://www.devx.com/Java/Article/42492/1954.

In fact I have an example of using JewelCLI with Scala here on Stack Overflow.

Despond answered 23/7, 2010 at 0:6 Comment(0)
N
2

The way to avoid this ambiguity is to force the compiler to pick the overload that takes more than one argument, using Scala's collection explosion syntax to pass in a singleton collection:

import java.util.stream.Stream
val stream = Stream.of(List(1):_*)
Nestle answered 14/5, 2018 at 17:35 Comment(0)
A
1

You can call the constructor with varags, but pass an empty list of varags.

(Of course, if you know that constructing JCommander with empty varags will produce the same result as calling the overloaded constructor (or method) without vargs)

jCommander = new JCommander(cmdLineArgs, Nil: _*)

Azure answered 7/10, 2018 at 19:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.