Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a factory method to an existing Java class in Scala

In a pure Scala environment, I could do the following if I wanted to "add" a factory method to an existing object:

object Test

object Extensions {

    object RichTest {
        def someFactory = new Test()
    }
    implicit def fromTest(t: Test.type) = RichTest

}

...

import Extensions._
val t = Test.someFactory

I would need such a functionality in combination with an existing Java class. In my concrete example, I would like to add a factory method fromLocation to the class com.google.android.maps.GeoPoint (and I guess every Android developer will know why this would be useful ;-) ).

However, if I try to do something like

implicit def fromGeoPoint(t: GeoPoint.type) = RichTest

I get an error stating

type mismatch; found : com.google.android.maps.GeoPoint.type (with underlying type object com.google.android.maps.GeoPoint) required: AnyRef

So I wonder if there is any way how the above approach could be implemented - or would providing an implicit conversion from Location to GeoPoint be the preferred way in Scala so a Location could be used whenever a GeoPoint is required?


As requested in the comments, a usage scenario:

// Callback you get from the GPS
override def onLocationChanged(l: Location) {
    // You want to put a marker on a map, hence a GeoPoint is required
    val gp: GeoPoint = GeoPoint.fromLocation(location)
    val item = new OverlayItem(gp, ...)
    ....
}

However, keep in mind that this is just one specific example for the general problem ;-)

like image 387
Leo Avatar asked Mar 03 '12 14:03

Leo


2 Answers

As of Scala version 2.9.1 it is not possible to extend Java class with a factory method.

However, there are three alternative solutions:

  1. Write Scala compiler plugin to accommodate the changes required and then extend the Java class.
  2. Create a standalone factory method.
  3. Avoid using a factory method altogether and add an implicit conversion so that Location object can always be used in place of a GeoPoint.

Personally I'd go for the third option if I used Location -> GeoPoint or any other conversion a lot of the time, especially in the scenario when conversion can always be done without exceptions and class interfaces don't overlap.

implicit def geoPointFromLocation(l: Location):GeoPoint = new GeoPoint...

override def onLocationChanged(l: Location) {        
    val gp: GeoPoint = l  // let Scala compiler do the conversion for you
    val item = new OverlayItem(gp, ...)
    ....
    // or just pass the location to OverlayItem constructor directly
    val item2 = new OverlayItem(l, ...)
}
like image 72
Vlad Gudim Avatar answered Sep 18 '22 15:09

Vlad Gudim


Great question! Unfortunately, I don't think it is possible. Since in Java there is no way to use the static parts of an class as a value, there is no reason for the type of the static members of a Java class to extend AnyRef. And unfortunately, to make that Java object extend AnyRef you would use implicit conversion, which require the Java object to extend AnyRef...

I'd love to be proven wrong though!

Update: you can't do this in Java, and there I think the best practice is to create your own static class in which to add the factory methods. For example, consider List in Guava.

Update: here is a full example of the differences between Java and Scala (what Dario was describing).

# vim Test.java
class Test {
  public static final int foo = 1;
  public final int bar = 2;
}

# javac Test.java
# ls

Test.class  Test.java

# javap Test

Compiled from "Test.java"
class Test {
  public static final int foo;
  public final int bar;
  Test();
}

Compared to, with scala:

# vim Test.scala

object Test {
  val foo = 1
}

class Test {
  val bar = 2
}

# javac Test.scala
# ls

Test$.class  Test.class  Test.scala

# javap Test

public class Test implements scala.ScalaObject {
  public static final int foo();
  public int bar();
  public Test();
}

# javap Test$

Compiled from "Test.scala"
public final class Test$ implements scala.ScalaObject {
  public static final Test$ MODULE$;
  public static {};
  public int foo();
}
like image 40
schmmd Avatar answered Sep 18 '22 15:09

schmmd