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
package automobiles
class Car(val year: Int) {
private[this] var miles : Int = 0
def drive(distance: Int) { miles += distance }
override def toString() : String = "year: " + year + " miles: " + miles
}
下面是个使用这个Scala类的Java类:
WorkingWithScriptsAndClasses/UseCar.java
//Java code
package automobiles.users;
import automobiles.Car;
public class UseCar {
public static void main(String[] args) {
Car car = new Car(2009);
System.out.println(car);
car.drive(10);
System.out.println(car);
}
}
用scalac编译Scala代码,用javac编译Java代码:
scalac -d classes Car.scala
javac -d classes -classpath classes UseCar.java
java -classpath \
/opt/scala/scala-2.7.4.final/lib/scala-library.jar:classes automobiles.
users.UseCar
在上面的例子里,生成的字节码放到了classes目录。在Java里使用Scala类相当简单。不过,不是所有的Scala类都那么友善。比如,如果Scala类有方法接收闭包,这些方法在Java里就不可用,因为Java目前尚不支持闭包。下面Equipment
类的simulate()
方法对Java就是不可用的;不过,我们可以用run()
方法:
WorkingWithScriptsAndClasses/Equipment.scala
class Equipment {
// Not usable from Java
def simulate(input: Int)(calculator: Int => Int) : Int = {
//...
calculator(input)
}
def run(duration: Int) {
println("running")
//...
}
}
因此,设计API的时候,如果类主要是给Java用,请在提供高阶函数的同时也提供普通函数,让这个类对Java完全可用。
11.3.2 同trait一起工作
我们来了解一下在Java里使用trait的限制。没有方法实现的trait在字节码层面上就是简单的接口。Scala不支持interface
关键字。因此,如果想在Scala里创建接口,就创建一个没有实现的trait。下面是个Scala trait的例子,它也是个接口:
WorkingWithScriptsAndClasses/Writable.scala
trait Writable {
def write(message: String) : Unit
}
上面的trait里有个抽象方法,混入这个trait的类都应该实现这个方法。在Java端,Writable
可以看做与其他接口一样;它对Scala根本没有依赖。所以,可以这样实现(implement)它:
WorkingWithScriptsAndClasses/AWritableJavaClass.java
//Java code
public class AWritableJavaClass implements Writable {
public void write(String message) {}
}
不过,如果trait有方法实现,那么Java类就不能实现这个trait/interface,虽然它们可以使用它。因此,在Java里不能实现下面的Printable
,但可以持有一个Printable
的引用:
WorkingWithScriptsAndClasses/Printable.scala
trait Printable {
def print() {} // default print nothing
}
如果想让Java类实现trait,就让它纯粹些;换句话说,不要有实现。在这种情况下,任何公共的实现都应该放到抽象基类里,而不是trait里。不过,如果只是想让Java类使用trait,就没有任何限制。
11.3.3 单例对象和伴生对象
Scala将对象(单例对象或伴生对象)编译成一个“单例类”——这个类的名字末尾有一个特殊$
符。这样,下面所示的Object Single
,会产生一个类名为Single$
。不过,Scala处理单例对象和伴生对象有些不同,稍后可以看到。
Scala把单例对象编译到一个单例类(它用的是Java的静态方法)中,此外,还会创建一个普通的类,它把调用传递给单例类。所以,下面这段代码创建了一个单例对象Single,而Scala则创建了两个类:Single$和用来传递调用的类Single:
WorkingWithScriptsAndClasses/Single.scala
object Single {
def greet() { println("Hello from Single")}
}
在Java里使用上面的单例对象,就像使用有static
方法的Java类一样,如下所示:
WorkingWithScriptsAndClasses/SingleUser.java
//Java code
public class SingleUser {
public static void main(String[] args) {
Single.greet();
}
}
上面代码的输出如下:
Hello from Single
如果对象是同名类的伴生对象,Scala会创建两个类,一个类表示Scala类(下面例子里的Buddy
),另一个类表示伴生对象(下面例子里的Buddy$
):
WorkingWithScriptsAndClasses/Buddy.scala
class Buddy {
def greet() { println("Hello from Buddy class")}
}
object Buddy {
def greet() { println("Hello from Buddy object")}
}
访问伴生类可以直接使用类的名字。访问伴生对象需要使用特殊符号MODULE$
,如下例所示:
WorkingWithScriptsAndClasses/BuddyUser.java
//Java code
public class BuddyUser {
public static void main(String[] args) {
new Buddy().greet();
Buddy$.MODULE$.greet();
}
}
输出如下:
Hello from Buddy class
Hello from Buddy object