Home > database >  Scala Function1<T,U> becomes Function1<Object,Object> in Java
Scala Function1<T,U> becomes Function1<Object,Object> in Java

Time:08-11

I am trying to use the Deequ library from Java, and am having issue using the following method:

  def hasCompleteness(
      column: String,
      assertion: Double => Boolean,
      hint: Option[String] = None)
    : CheckWithLastConstraintFilterable = {
    addFilterableConstraint { filter => completenessConstraint(column, assertion, filter, hint) }
  }

In particular, for the assertion, I need to pass in a Function1<Double, Boolean>

So I use the following code to create an assertion function in Java:

public static Function1<Double, Boolean> atLeast(double thresholdPercentage) {
  return new scala.compat.java8.functionConverterImpls.FromJavaFunction<>((Double actualPercentage) -> actualPercentage >= thresholdPercentage);
}

Simple enough, right?

Only, my Java Class now won't compile because the signature of the hasCompleteness method, when viewed from Java is that the assertion parameter is a Function1<Object, Object>. Thus I see the following error:

Required type: Function1<Object, Object>
Provided: Function1<Double, Boolean>

If I change my atLeast method to return an unparametrized Function1 the issue goes away, but this feels like an ugly kludge.

Am I condemned to losing the type params when using this method from Java? Or is there something perhaps wrong with my IntelliJ / Gradle setup causing this weird behavior?

CodePudding user response:

That seems how Scala compiler translates "primitive" Scala types (remember, Double here is scala.Double and not java.lang.Double). E.g. Scala declarations

  var d: Double => Boolean;
  var s: String => String;

produce (as shown by javap):

  public abstract scala.Function1<java.lang.Object, java.lang.Object> d();
  public abstract scala.Function1<java.lang.String, java.lang.String> s();

Note how Double and Boolean both translate to Object, but String is preserved

CodePudding user response:

peterz detailed exactly what happens under the hood.

Sounds like what you need is an explicit conversion from the primitive double Java predicate (java.util.function.DoublePredicate) to the scala.Double Scala predicate (Double => Boolean).

Scala actually offers one such conversion called FromJavaDoublePredicate. The code does not require explicit Double cast anymore:

  /* some scala file */
  def hasCompleteness(
      x: Double,
      f: Double => Boolean
  ): Unit = println(f(x))

  /* some java file */
  public static FromJavaDoublePredicate atLeast(double thresholdPercentage) {
      return new scala.compat.java8.functionConverterImpls.FromJavaDoublePredicate(
        actualPercentage -> actualPercentage >= thresholdPercentage);
  }

  FromJavaDoublePredicate f = atLeast(3.0);
  Test.hasCompleteness(5, f);  // true

PS. From what I saw, you chose scala-java8-compat, so I assumed you use an earlier version of Scala. Their maintainers state:

If you are using Scala 2.13 or newer only, then don't use this library! Use the classes under scala.jdk instead; they were added to the standard library in 2.13.

So for Scala 2.13.x I recommend using the FromJavaDoublePredicate case class in the FunctionWrappers object instead.

  • Related