Question

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 ;-)

Was it helpful?

Solution

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, ...)
}

OTHER TIPS

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();
}

This is not possible, because in scala on object ... will become a singleton instance made accessible through static methods in the java class. E.g.

object Foo {
  def bar = 2
}

will become something like:

public final class Foo {
  public static int bar() {
    return Foo..MODULE$.bar();
  }
}

public final class Foo$
  implements ScalaObject
{
  public static final  MODULE$;

  static
  {
    new ();
  }

  public int bar()
  {
    return 2;
  }

  private Foo$()
  {
    MODULE$ = this;
  }
}

What gets passed into the implicit conversion method is in this case the Foo..MODULE$ which is an instance of type Foo$. Java statics don't have an underlying singleton instance and can therefore not be passed into a function to be converted to another type.

Hope this helps a bit to understand why it is not possible ;-).

Although you cannot add a method by implicit conversion to this kind of singleton objects, you can use an implicit parameter to make the actual creation. For instance:

trait Factory1[-A,+B] {
  def buildFrom( a: A ): B
}

trait Factory2[-A1,-A2,+B] {
  def buildFrom( a1: A1, a2: A2 ): B
}

object Factories {
  implicit object GeoPointFromLocation extends Factory1[Location,GeoPoint] {
    def buildFrom( location: Location ): GeoPoint = //actual code
  }
  implicit object GeoPointFromXY extends Factory2[Double,Double,GeoPoint] {
    def buildFrom( x: Double, y: Double ): GeoPoint = //actual code
  }
}

object GeoPoint {
  def from[A]( a: A )( implicit fac: Factory1[A,GeoPoint] ) = fac.buildFrom(a)
  def from[A,B]( a: A, b: B )( implicit fac: Factory2[A,B,GeoPoint] ) = fac.buildFrom(a,b)
}

import Factories._
val loc = new Location(...)
val gp1 = GeoPoint.from( loc )
val gp2 = GeoPoint.from( 101.0, -12.6 )

So factories are still "static" as you seem to expect them, but you can enrich or change the behavior without having to change GeoPoint object. You can for example import special factories when testing.

This approach is used in Scala library, for example to build the right collection type when applying generic Traversable methods like map.

What you want to do is impossible. Because a Java class is not a Scala object there is no way to enlarge it with methods.

The only point I see to make work as easy as possible is to create an object in Scala and make a qualified import for the Java class:

import test.{ Test => JTest }
object Test {
  def anyMethodHere ...
}

This is not possible, but you can have a companion object with same class name in a package object and use it as you wanted,

import com.google.android.maps.GeoPoint

package object com.xyz.mappy.maps {
   object GeoPoint {        
     def from(....): GeoPoint = new GeoPoint(...)
   }
}


.................................

package com.xyz.mappy.maps

import com.google.android.maps.GeoPoint

class MapRenderer  {

  val geopoint = GeoPoint.from(...)

}    
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top