11.3 在Java里使用Scala类

Scala提供了与Java之间完整的双向互操作性。因为Scala能编译成字节码,所以在Java里使用Scala类相当容易。记住,默认情况下,Scala并不遵循JavaBean的约定,要用@scala.reflect.BeanProperty这个注解生成符合JavaBean约定的getter和setter(参见4.2节,“定义字段、方法和构造函数”)。还可以从Scala类继承Java类,不过,要运行使用了Scala类的Java代码,classpath里需要有scala-library.jar。在本节里,我们会看到Scala的构造在Java端会表现出怎样的不同。

11.3.1 有普通函数和高阶函数的Scala类

遵循标准Java构造的Scala类相当直白,在Java端使用它们很容易。我们写一个Scala类:

WorkingWithScriptsAndClasses/Car.scala

  1. package automobiles
  2. class Car(val year: Int) {
  3. private[this] var miles : Int = 0
  4. def drive(distance: Int) { miles += distance }
  5. override def toString() : String = "year: " + year + " miles: " + miles
  6. }

下面是个使用这个Scala类的Java类:

WorkingWithScriptsAndClasses/UseCar.java

  1. //Java code
  2. package automobiles.users;
  3. import automobiles.Car;
  4. public class UseCar {
  5. public static void main(String[] args) {
  6. Car car = new Car(2009);
  7. System.out.println(car);
  8. car.drive(10);
  9. System.out.println(car);
  10. }
  11. }

用scalac编译Scala代码,用javac编译Java代码:

  1. scalac -d classes Car.scala
  2. javac -d classes -classpath classes UseCar.java
  3. java -classpath \
  4. /opt/scala/scala-2.7.4.final/lib/scala-library.jar:classes automobiles.
  5. users.UseCar

在上面的例子里,生成的字节码放到了classes目录。在Java里使用Scala类相当简单。不过,不是所有的Scala类都那么友善。比如,如果Scala类有方法接收闭包,这些方法在Java里就不可用,因为Java目前尚不支持闭包。下面Equipment类的simulate()方法对Java就是不可用的;不过,我们可以用run()方法:

WorkingWithScriptsAndClasses/Equipment.scala

  1. class Equipment {
  2. // Not usable from Java
  3. def simulate(input: Int)(calculator: Int => Int) : Int = {
  4. //...
  5. calculator(input)
  6. }
  7. def run(duration: Int) {
  8. println("running")
  9. //...
  10. }
  11. }

因此,设计API的时候,如果类主要是给Java用,请在提供高阶函数的同时也提供普通函数,让这个类对Java完全可用。

11.3.2 同trait一起工作

我们来了解一下在Java里使用trait的限制。没有方法实现的trait在字节码层面上就是简单的接口。Scala不支持interface关键字。因此,如果想在Scala里创建接口,就创建一个没有实现的trait。下面是个Scala trait的例子,它也是个接口:

WorkingWithScriptsAndClasses/Writable.scala

  1. trait Writable {
  2. def write(message: String) : Unit
  3. }

上面的trait里有个抽象方法,混入这个trait的类都应该实现这个方法。在Java端,Writable可以看做与其他接口一样;它对Scala根本没有依赖。所以,可以这样实现(implement)它:

WorkingWithScriptsAndClasses/AWritableJavaClass.java

  1. //Java code
  2. public class AWritableJavaClass implements Writable {
  3. public void write(String message) {}
  4. }

不过,如果trait有方法实现,那么Java类就不能实现这个trait/interface,虽然它们可以使用它。因此,在Java里不能实现下面的Printable,但可以持有一个Printable的引用:

WorkingWithScriptsAndClasses/Printable.scala

  1. trait Printable {
  2. def print() {} // default print nothing
  3. }

如果想让Java类实现trait,就让它纯粹些;换句话说,不要有实现。在这种情况下,任何公共的实现都应该放到抽象基类里,而不是trait里。不过,如果只是想让Java类使用trait,就没有任何限制。

11.3.3 单例对象和伴生对象

Scala将对象(单例对象或伴生对象)编译成一个“单例类”——这个类的名字末尾有一个特殊$符。这样,下面所示的Object Single,会产生一个类名为Single$。不过,Scala处理单例对象和伴生对象有些不同,稍后可以看到。

Scala把单例对象编译到一个单例类(它用的是Java的静态方法)中,此外,还会创建一个普通的类,它把调用传递给单例类。所以,下面这段代码创建了一个单例对象Single,而Scala则创建了两个类:Single$和用来传递调用的类Single:

WorkingWithScriptsAndClasses/Single.scala

  1. object Single {
  2. def greet() { println("Hello from Single")}
  3. }

在Java里使用上面的单例对象,就像使用有static方法的Java类一样,如下所示:

WorkingWithScriptsAndClasses/SingleUser.java

  1. //Java code
  2. public class SingleUser {
  3. public static void main(String[] args) {
  4. Single.greet();
  5. }
  6. }

上面代码的输出如下:

  1. Hello from Single

如果对象是同名类的伴生对象,Scala会创建两个类,一个类表示Scala类(下面例子里的Buddy),另一个类表示伴生对象(下面例子里的Buddy$):

WorkingWithScriptsAndClasses/Buddy.scala

  1. class Buddy {
  2. def greet() { println("Hello from Buddy class")}
  3. }
  4. object Buddy {
  5. def greet() { println("Hello from Buddy object")}
  6. }

访问伴生类可以直接使用类的名字。访问伴生对象需要使用特殊符号MODULE$,如下例所示:

WorkingWithScriptsAndClasses/BuddyUser.java

  1. //Java code
  2. public class BuddyUser {
  3. public static void main(String[] args) {
  4. new Buddy().greet();
  5. Buddy$.MODULE$.greet();
  6. }
  7. }

输出如下:

  1. Hello from Buddy class
  2. Hello from Buddy object