这个章节主要讲解Scala和Java进行互操做。java
Javapnode
类闭包
异常app
Traitide
对象函数
闭包函数(closures functions)工具
Javapthis
javap是JDK附带的一个工具,而不是JRE。它们之间仍是有差异的。Javap反编译class文件,而且向你展现它里面放的是什么。使用起来很简单。spa
[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTraitscala
Compiled from "Scalaisms.scala"
public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
public abstract java.lang.String traitName();
public abstract java.lang.String upperTraitName();
}
若是你想了解底层的话,你能够查看对应的字节码
[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap -c MyTrait\$class
Compiled from "Scalaisms.scala"
public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
Code:
0: aload_0
1: invokeinterface #12, 1; //InterfaceMethod com/twitter/interop/MyTrait.traitName:()Ljava/lang/String;
6: invokevirtual #17; //Method java/lang/String.toUpperCase:()Ljava/lang/String;
9: areturn
public static void $init$(com.twitter.interop.MyTrait);
Code:
0: return
}
若是你在Java平台上有什么问题,你能够经过javap来排查。
类
从Java的角度来使用Scala的_class_须要注意的四个要点以下:
类参数
类常量
类变量
异常
咱们来建立一个简单的scala类来展现这几个要点
package com.twitter.interop
import java.io.IOException
import scala.throws
import scala.reflect.{BeanProperty, BooleanBeanProperty}
class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) {
val foo = "foo"
var bar = "bar"
@BeanProperty
val fooBean = "foobean"
@BeanProperty
var barBean = "barbean"
@BooleanBeanProperty
var awesome = true
def dangerFoo() = {
throw new IOException("SURPRISE!")
}
@throws(classOf[IOException])
def dangerBar() = {
throw new IOException("NO SURPRISE!")
}
}
类参数
默认状况下,类参数实际上就是Java里构造函数的参数。这就意味着你不能在这个class以外访问它们。
把类参数定义成一个val/var的方式和下面的代码相同
class SimpleClass(acc_: String) {
val acc = acc_
}
下面就能够经过Java代码来访问它们了。
常量(Val)
常量(val)都会定义有对应的供Java代码访问的方法。你能够经过”foo()”方法来取得常量(val)“foo”的值。
变量(Var)
变量(var)会多定义一个_$eq方法。你能够这样调用来设置变量的值:
foo$_eq("newfoo");
BeanFactory
你能够经过@BeanProperty注解来标注val和var。这样就会生成相似于POJO的getter/setter方法。假如你想要访问isFoo变量,使用BooleanBeanProperty注解。那么难以理解的foo$eq就能够换成:
setFoo("newfoo");
getFoo();
异常
Scala里没有受检异常(checked exception),可是Java里有。这是一个语言层面上的问题,咱们这里不进行讨论,可是在Java里你对异常进行捕获的时候你仍是要注意的。dangerFoo和dangerBar的定义里对这进行了示范。在Java里,你不能这样作。
// exception erasure!
// 异常擦除!
try {
s.dangerFoo();
} catch (IOException e) {
// UGLY
// 很是丑陋
}
Java编译器会由于s.dangerFoo不会抛出IOException而报错。咱们能够经过捕获Throwable来绕过这个错误,可是这样作没多大用处。
不过,做为一个Scala用户,比较正式的方式是使用throws注解,就像咱们以前在dangerBar上的同样。这个手段使得咱们可以使用Java里的受检异常(checked exception)。
延伸阅读
支持Java互操做的注解的完整列表在http://www.scala-lang.org/node/106。
Trait
咱们怎样能够获得一个接口和对应的实现呢?咱们简单看看trait的定义
trait MyTrait {
def traitName:String
def upperTraitName = traitName.toUpperCase
}
这个trait有一个抽象的方法(traitName)和一个已实现的方法(upperTraitName)。对于这样的trait,Scala会生成什么样的代码呢?它会生成一个MyTrait接口,同时还会生成对应的实现类MyTrait$class。
MyTrait的实现和你猜测的差很少:
[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
Compiled from "Scalaisms.scala"
public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
public abstract java.lang.String traitName();
public abstract java.lang.String upperTraitName();
}
不过MyTrait$class的实现更加有趣:
[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait\$class
Compiled from "Scalaisms.scala"
public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
public static void $init$(com.twitter.interop.MyTrait);
}
MyTrait$class类只有一个静态的接受一个MyTrait实例做为参数的方法。这样给了咱们在Java如何实现trait的一条线索。
首先要作的是:
package com.twitter.interop;
public class JTraitImpl implements MyTrait {
private String name = null;
public JTraitImpl(String name) {
this.name = name;
}
public String traitName() {
return name;
}
}
而后咱们会获得下面的错误:
[info] Compiling main sources...
[error] /Users/mmcbride/projects/interop/src/main/java/com/twitter/interop/JTraitImpl.java:3: com.twitter.interop.JTraitImpl is not abstract and does not override abstract method upperTraitName() in com.twitter.interop.MyTrait
[error] public class JTraitImpl implements MyTrait {
[error] ^
咱们_能够_本身来实现他们。不过还有一种更加诡异的方式。
package com.twitter.interop;
public String upperTraitName() {
return MyTrait$class.upperTraitName(this);
}
咱们只须要把相应的方法调用代理到Scala的实现上。而且咱们还能够根据实际须要进行重写。
对象
在Scala里,是用对象来实现静态方法和单例模式的。若是在Java里使用它们就会显得比较怪。在语法上没有什么比较优雅的方式来使用它们,可是在Scala 2.8里就没有那么麻烦了。
Scala对象会被编译成一个名称带有“$”后缀的类。咱们来建立一个类以及对应的对象(Object)。咱们来建立一个类以及对应的伴生对象(companion object)。
class TraitImpl(name: String) extends MyTrait {
def traitName = name
}
object TraitImpl {
def apply = new TraitImpl("foo")
def apply(name: String) = new TraitImpl(name)
}
咱们能够经过下面这种奇妙的方式在Java里访问它:
MyTrait foo = TraitImpl$.MODULE$.apply("foo");
如今你也许会问本身,这到底是神马?这是一个很正常的反应。咱们如今一块儿来看看TraintImpl$内部到底是怎么实现的。
local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap TraitImpl\$
Compiled from "Scalaisms.scala"
public final class com.twitter.interop.TraitImpl$ extends java.lang.Object implements scala.ScalaObject{
public static final com.twitter.interop.TraitImpl$ MODULE$;
public static {};
public com.twitter.interop.TraitImpl apply();
public com.twitter.interop.TraitImpl apply(java.lang.String);
}
其实它里面没有任何静态方法。相反,它还有一个静态成员叫作MODULE$。实际上方法的调用都是代理到这个成员变量上的。这种实现使得访问起来以为比较恶心,可是若是你知道怎么使用MODULE$的话,其实仍是很实用的。
转发方法(Forwarding Method)
在Scala 2.8里,处理Object会比较简单点。若是你有一个类以及对应的伴生对象(companion object),2.8 的编译器会在伴生对象里生成转发方法。若是使用2.8的编译器进行构建,那么你能够经过下面的方法来访问TraitImpl对象:
MyTrait foo = TraitImpl.apply("foo");
闭包函数
Scala最重要的一个特色就是把函数做为一等公民。咱们来定义一个类,它里面包含一些接收函数做为参数的方法。
class ClosureClass {
def printResult[T](f: => T) = {
println(f)
}
def printResult[T](f: String => T) = {
println(f("HI THERE"))
}
}
在Scala里我能够这样调用:
val cc = new ClosureClass
cc.printResult { "HI MOM" }
可是在Java里却没有这么简单,不过也没有想象的那么复杂。咱们来看看ClosureClass最终到底编译成怎样:
[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap ClosureClass
Compiled from "Scalaisms.scala"
public class com.twitter.interop.ClosureClass extends java.lang.Object implements scala.ScalaObject{
public void printResult(scala.Function0);
public void printResult(scala.Function1);
public com.twitter.interop.ClosureClass();
}
这个看起来也不是很可怕。”f: => T” 转换成”Function0″,”f: String => T” 转换成 “Function1″。Scala定义了从Function0到Function22,一直支持到22个参数。这么多确实已经足够了。
如今咱们只须要弄清楚,怎么在Java去实现这个功能。事实上,Scala提供了AbstractFunction0和AbstractFunction1,咱们能够这样来传参:
@Test public void closureTest() {
ClosureClass c = new ClosureClass();
c.printResult(new AbstractFunction0() {
public String apply() {
return "foo";
}
});
c.printResult(new AbstractFunction1() {
public String apply(String arg) {
return arg + "foo";
}
});
}
注意咱们还可使用泛型来参数化参数的类型。