从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
方法。