与Java互操作

1. 在Scala里使用Scala类

如果在单独的文件里创建Scala类,就可以轻松地使用它们,就像(无需显式编译)在Scala脚本里使用一样。不过,如果想在编译过的Scala或Java代码里使用Scala类,那就必须编译了。

举例如下:

package com.cn.peng

class Person(val firstName:String, val lastName:String) {
    override def toString():String = firstName + " " + lastName
}
package com.cn.peng

class Dog(name:String) {
    override def toString():String = name
}

下面是使用上面两个类的脚本:

package com.cn.peng

object use {
  def main(args:Array[String]){
    val george = new Person("George", "Washington")
    
    val geogesDogs = List(new Dog("Captain"), new Dog("Clode"),
        new Dog("Forester"), new Dog("Searcher"))
        
    printf("%s had several dogs %s...", george,geogesDogs mkString ", ")
  }
    
}

脚本会产生如下输出:

George Washington had several dogs Captain, Clode, Forester, Searcher...

 2. 在Scala里使用Java类

在Scala里可以直接使用Java类。如果要用的Java类是标准JDK的一部分,直接用就是了。如果它不在java.lang里,就要导入类的包。下面用到了java.util和java.lang.reflect包:

package com.cn.peng
import java.util.Date
import java.lang.reflect._
object UseJDKClasses extends App {
    println("Today is " + new Date())
    
    val methods = getClass.getMethods()
    methods.foreach{method:Method => println(method.getName())}
}

程序运行结果如下:

Today is Sat Apr 04 16:53:55 CST 2015
main
delayedEndpoint$com$cn$peng$UseJDKClasses$1
args
delayedInit
scala$App$_setter_$executionStart_$eq
executionStart
scala$App$$_args
scala$App$$initCode
methods
scala$App$$_args_$eq
scala$App$_setter_$scala$App$$initCode_$eq
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

如果想用的Java类是你自己创建的,或是来自第三方,请确保scalac的classpath指向字节码的位置。假定我们有如下的Java文件:

package investments;
public enum InvestmentType {
    SHORT_TERM,
    BOND,
    STOCK,
    REAL_ESTATE,
    COMMODITIES,
    COLLECTIBLES,
    MUTUAL_FUNDS
}
package investments;

public class Investment {
    private String investmentName;
    private InvestmentType investmentType;
    
    public Investment(String name, InvestmentType type){
        investmentName = name;
        investmentType = type;
    }
    
    public int yield() { return 0; }
}

在Scala代码里使用这些类,同使用Scala类一样。下面是一个在Scala里创建Investment实例的例子:

package com.cn.peng
import investments._
object UseInvestment {
    def main(args: Array[String]){
      val investment = new Investment("XYZ Corporation", InvestmentType.STOCK)
      println(investment.getClass())
    }
}

运行结果如下:

class investments.Investment

Investment类的yield()方法需要小心使用。如果Java代码有方法或字段的名字(比如trait或yield等)于Scala的关键字冲突,调用它们会导致Scala编译器死掉。比如,下面的代码是不行的:

      val theYield1 = investment.yield   //ERROR
      val theYield2 = investment.yield() //ERROR

幸运的是,Scala提供了一个解决方案。把冲突的变量/方法放到反引号里,就可以绕开这个问题。改一下代码就可以让上面的两个调用工作了:

      val theYield1 = investment.`yield`   
      val theYield2 = investment.`yield`()

3. 在Java里使用Scala类

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

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

遵循标准Java构造的Scala类相当直白,在Java端使用它们很容易。我们写一个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类:

 

package com.cn.peng;
import automobiles.Car;
public class UseCar {
    public static void main(String[] args){
        Car car = new Car(2014);
        System.out.println(car);
        car.drive(10);
        System.out.println(car);
    }
}

程序运行结果如下:

year: 2014 miles: 0
year: 2014 miles: 10

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

package com.cn.peng

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完全可用。

3.2 同trait一起工作

没有方法实现的trait在字节码层面上就是简单的接口。Scala不支持interface关键字。因此,如果想在Scala里创建接口,就创建一个没有实现的trait。下面是个Scala trait的例子,他也是个接口:

package com.cn.peng

trait Writable {
    def wirte(message: String):Unit
}

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

package com.cn.peng;

public class AWritableJavaClass implements Writable{

    @Override
    public void wirte(String message) {
        // TODO Auto-generated method stub
        
    }

}

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

package com.cn.peng

trait Printable {
    def print(){} //default print nothing
}

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

3.3 单例对象和伴生对象

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

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

package com.cn.peng

object Single {
    def greet(){println("Hello from Single")}
}

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

package com.cn.peng;

public class SingleUser {
    public static void main(String[] args){
        Single.greet();
    }
}

上面代码的输出如下:

Hello from Single

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

package com.cn.peng

class Buddy {
    def greet(){println("Hello from Buddy class")}
}

object Buddy {
    def greet(){println("Hello from Buddy object")}
}

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

package com.cn.peng;

public class BuddyUser {
    public static void main(String[] args){
        new Buddy().greet();
        Buddy$.MODULE$.greet();
    }
}

输出如下:

Hello from Buddy class
Hello from Buddy object

4. 继承类

Scala类可以继承Java类,反之亦然。大多数情况下,这应该够用了。之前也讨论过,如果方法接收闭包作为参数,重写起来就有些麻烦。异常也是这个问题。

Scala没有throws字句。在Scala里,任意方法都可以抛出异常,无需显式声明成方法签名的一部分。不过,如果在Java里重写这样的方法,试图抛出异常,就会陷入麻烦。看个例子,假设Scala定义了Bird:

package com.cn.peng

abstract class Bird {
    def fly();
    //...
}

还有另一个类Ostrich:

package com.cn.peng

class Ostrich extends Bird {
    def fly(){
      throw new NoFlyException
    }
    //...
}

其中NoFlyException定义如下:

package com.cn.peng

class NoFlyException extends Exception{

}

在上面的代码里,Ostrich的fly()抛出异常没有任何问题。不过,如果要在Java里实现一个不能飞的鸟,就会有麻烦,如下所示:

package com.cn.peng;

public class Penguin extends Bird{
    public void fly() throws NoFlyException {
        throw new NoFlyException();
    }
    //...
}

首先,如果只是抛出异常,Java会报错“unreported exception NoFlyException;must be caught or declared to be thrown." 一旦加上了throws子句,java又会报错”Exception NoFlyException is not compatible with throws clause in Bird.fly()“。

即便Scala很灵活,并不强求一定要指定抛出哪些异常,但是要想在Java里继承这些方法,就要告诉Scala编译器,把这些细节记录在方法签名里。Scala为此提供了一个后门:定义@throws注解。

虽然Scala支持注解,但它却不提供注解的语法。如果想创建自己的注解,就不得不用Java来做。@throws是已经提供好的注解,用以表示方法抛出的受控异常。这样,对我们来说,要在Java里实现Penguin,必须在把Bird改成这样:

package com.cn.peng

abstract class Bird {
  @throws(classOf[NoFlyException]) def fly();
    //...
}

现在,编制上面的代码,Scala编译器会在字节码里为fly()方法放上必要的签名。经过了这个修改,Java类Penguin就可以正常编译了。

posted @ 2015-04-05 16:10  ~风轻云淡~  阅读(1845)  评论(0编辑  收藏  举报