从Java角度看Scala
-
scala一般要和Java在大型程序中使用,使用Java中的框架。Scala的实现方式是将代码翻译成为标准的Java字节码,Scala的特性尽可能地直接映射为Java中的特性。可以使用javap来查看.class中Scala进行的翻译。
1. 值类型
对于值类型,在能够确定的情况下,优先翻译成为Java中的值类型用以获得更好的性能,如果不能确定,则翻译为包装类型。
2. 单例对象
- 采用静态和实例方法相结合的方式来翻译单例对象,如果是单个的对象,则生成类
Test$和Test,
public final class Test$ {
public static final Test$ MODULE$;
public static {};
public void main(java.lang.String[]);
public java.lang.String select(java.lang.String[]);
}
其中,编译器插入了代码,保证在运行的时候私有化了Test$的构造方法,并对MODULE$进行了赋值。在Test类中
public final class Test {
public static java.lang.String select(java.lang.String[]);
public static void main(java.lang.String[]);
}
对Test$中的所有方法在Test中都存在有static的转发版本。在书中说如果object有了对应的class,则在java的class中不会添加额外的转发方法,但是在实际过程中发现即使有了对应的class,java中的class也添加了额外的转发方法。
3.接口
- 特质被翻译成为接口。如果在特质中有实现的部分,使用
abstract class进行实现。
注解
- 主要讨论的是
Java中的注解。如果是@deprecated,则在Java代码上也加入该注解,这样,当Java代码试图访问Scala中带有@deprecated的代码时,会抛出警告。@volatile也是同样的操作。对与序列化的注解,Scala中的@serializable在Java中会翻译为实现了Serializable接口,@SerialVersionUID(1234L)被翻译成为
// Java serial version marker
private final static long SerialVersionUID = 1234L
@transient也是在Java代码上加上同样的注解。
1.异常抛出
-
Scala并不检查是由否有异常抛出,所以转换得到的所有Java代码都是不带throw的。因为在Java中,大程序可能就是吞下并隐藏了许多异常,所以在Scala中并不采用这种throw,catch的方法进行处理。如果生成的Java代码中必须要有throws异常的语法,则在scala中使用@throws的注解,
class Reader(fname: String) {
private val in =
new BufferedReader(new FileReader(fname))
@throws(classOf[IOException])
def read() = in.read()
}
2.Java注解
现在的Java框架注解都可以直接应用在Scala代码中。
3.定制注解
如果需要注解在Java反射的时候可以看到,则必须使用Java中的注解,并使用javac来编译它。因为Scala不能完全的支持Java中的注解,使用的反射机制也是Java的。可以这么使用:
import java.lang.annotation.*; // This is Java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore { }
object Tests {
@Ignore
def testData = List(0, 1, -1, 5, -5)
def test1 = {
assert(testData == (testData.head :: testData.tail))
}
def test2 = {
assert(testData.contains(testData.head))
}
}
for {
method <- Tests.getClass.getMethods
if method.getName.startsWith("test")
if method.getAnnotation(classOf[Ignore]) == null // 使用的是Java中的反射API,所以比较使用的是null
} {
println("found a test method: " + method)
}
使用的顺序如下所示:
$ javac Ignore.java
$ scalac Tests.scala
$ scalac FindTests.scala
$ scala FindTests
found a test method: public void Tests$.test2()
found a test method: public void Tests$.test1()
注意到这里是Tests$而不是Tests,因为使用的是Java中的反射API,所以在Tests.getClass.getMethods的时候类就是Tests$,而不是Test,同时注意在Java中的注解参数只能是常量,不能是x*2,x是常量这种形式。
通配符类型
-
Java和Scala是相通的,一般的转换都是非常简单的,Java中的Iterator<Component>就是Scala中的Iterator[Component],但是如果在Java中存在有通配符的泛型,比如Iterator<?>或者Iterator<? extends Father>这样的类型,在Scala中有同样的成为通配符的进行匹配转换。使用的是占位符的思想,使用下划线作为通配符进行转换,Iterator<?>就是Iterator[_],表示一个Iterator,但是其中的类型是不明的。同样地,可以插入上限符号和下限符号来限制范围。Iterator[_ <: Father],表示必须是Father的子类,Iterator[_ >: Child],表示必须是Child的父类。
同时编译Scala代码和Java代码
- 通常如果
Scala代码依赖Java代码的话,首先编译Java代码生成对应的class文件,再编译Scala文件,将Java class文件放入到classpath中,如果在Java中同样引用了Scala代码,那么这个方法就失效了,Scala提供了一种解决这个问题的方法。
在Scala2.12中集成Java8
- 在
Java8中,任意一个需要一个类或者接口对象的地方都可以使用lamda表达式替代,这个类或者接口只能含有一个抽象的方法,叫Single Abstract Method对象,在Scala2.12中对SAM进行子类实现的时候,可以直接使用一个函数代替匿名类的实例。
在Scala2.12中使用Java8中的流
-
Java中的Stream是一个函数数据结构,提供了一个map方法。