此页面通过分享每种语言的并排示例,对 Java 和 Scala 编程语言进行了比较。它面向了解 Java 并希望了解 Scala 的程序员,特别是通过了解 Scala 特性与 Java 的比较来了解 Scala。
概述
在进入示例之前,本第一部分提供了相对简短的介绍和对后续部分的总结。它从高层次介绍了 Java 和 Scala 之间的相似之处和不同之处,然后介绍了你在编写代码时每天都会遇到的不同之处。
高层次相似之处
从高层次来看,Scala 与 Java 具有以下相似之处
- Scala 代码编译为 .class 文件,打包在 JAR 文件中,并在 JVM 上运行
- 它是一种 面向对象编程 (OOP) 语言
- 它是静态类型的
- 这两种语言都支持 lambda 和 高阶函数
- 它们都可以与 IntelliJ IDEA 和 Microsoft VS Code 等 IDE 一起使用
- 项目可以使用 Gradle、Ant 和 Maven 等构建工具进行构建
- 它拥有出色的库和框架,用于构建服务器端、网络密集型应用程序,包括 Web 服务器应用程序、微服务、机器学习等(请参阅 “Awesome Scala” 列表)
- Java 和 Scala 都可以使用 Scala 库
- 它们可以使用 Akka 演员库 构建基于演员的并发系统,并使用 Apache Spark 构建数据密集型应用程序
- 它们可以使用 Play Framework 开发服务器端应用程序
- 你可以使用 GraalVM 将你的项目编译成本机可执行文件
- Scala 可以无缝使用为 Java 开发的大量库
高级差异
同样在高级层面,Java 和 Scala 之间的差异是
- Scala 具有简洁但可读的语法;我们称之为表达性
- 尽管它是静态类型的,但 Scala 通常感觉像一门动态语言
- Scala 是一门纯 OOP 语言,因此每个对象都是一个类的实例,而类似于
+
和+=
的符号看起来像运算符,实际上是方法;这意味着你可以创建自己的运算符 - 除了是一门纯 OOP 语言之外,Scala 还是一门纯 FP 语言;事实上,它鼓励融合 OOP 和 FP,使用函数进行逻辑处理,使用对象进行模块化
- Scala 有一整套不可变集合,包括
List
、Vector
以及不可变Map
和Set
实现 - Scala 中的一切都是表达式:类似于
if
语句、for
循环、match
表达式,甚至try
/catch
表达式的构造都具有返回值 - Scala 惯用法默认支持不可变性:你应该使用不可变 (
final
) 变量和不可变集合 - 惯用 Scala 代码不使用
null
,因此不会出现NullPointerException
- Scala 生态系统在 sbt、Mill 和其他系统中还有其他 构建工具
- 除了在 JVM 上运行之外,Scala.js 项目还允许你将 Scala 用作 JavaScript 替代品
- Scala Native 项目添加了低级构造,允许你编写“系统”级代码,还可以编译成本机可执行文件
编程级别差异
最后,这些是你每天在编写代码时会看到的一些差异
- Scala 的语法非常一致
- 变量和参数定义为
val
(不可变,如 Java 中的final
)或var
(可变) - 类型推断让你的代码感觉是动态类型的,并有助于保持你的代码简洁
- 除了简单的
for
循环,Scala 还有强大的for
推导,可根据你的算法产生结果 - 模式匹配和
match
表达式将改变你编写代码的方式 - 默认编写不可变代码会导致编写表达式而不是语句;随着时间的推移,你会发现编写表达式简化了你的代码(和你的测试)
- 顶级定义让你可以将方法、字段和其他定义放在任何地方,这也导致了简洁、富有表现力的代码
- 你可以通过将多个特质“混合”到类和对象中来创建混合(特质类似于 Java 8 及更高版本中的接口)
- 默认情况下,类是封闭的,支持 Joshua Bloch 的Effective Java 惯用法,“设计和记录继承或禁止它”
- Scala 的 上下文抽象和术语推断提供了一系列功能
- Scala 拥有最先进的、第三方、开源函数式编程库
- Scala case 类就像 Java 14 中的记录;它们帮助你在编写 FP 代码时对数据建模,并内置对模式匹配和克隆等概念的支持
- 借助按名称参数、中缀表示法、可选括号、扩展方法和 高阶函数 等特性,你可以创建自己的“控制结构”和 DSL
- Scala 文件不必根据它们包含的类或特质来命名
- 其他许多优点:伴随类和对象、宏、联合和交集、数字文字、多个参数列表、参数的默认值、命名参数等
通过示例比较特性
在介绍之后,以下部分将并排比较 Java 和 Scala 编程语言特性。
OOP 样式类和方法
此部分提供了与 OOP 样式类和方法相关的特性的比较。
注释
//
|
//
|
OOP 样式类,主构造函数
Scala 不遵循 JavaBeans 标准,因此,这里不显示以 JavaBeans 样式编写的 Java 代码,而是显示等效于其后的 Scala 代码的 Java 代码。
class Person {
|
class Person (
|
辅助构造函数
public class Person {
|
class Person (
|
默认情况下,类是封闭的
“计划继承或禁止继承。”
final class Person
|
类 Person
|
一个开放扩展的类
类 Person
|
开放类 Person
|
单行方法
public int add(int a, int b) {
|
def add(a: Int, b: Int): Int = a + b
|
多行方法
public void walkThenRun() {
|
def walkThenRun() =
|
不可变字段
final int i = 1;
|
val i = 1
|
可变字段
int i = 1;
|
var i = 1
|
接口、特征和继承
本节比较了 Java 接口和 Scala 特征,包括类如何扩展接口和特征。
接口/特征
public interface Marker {};
|
特征 Marker
|
简单接口
public interface Adder {
|
特征 Adder
|
具有具体方法的接口
public interface Adder {
|
特征 Adder
|
继承
类 Dog 扩展 Animal 实现 HasLegs、HasTail
|
类 Dog 扩展 Animal、HasLegs、HasTail
|
扩展多个接口
这些接口和特征具有具体实现的方法(默认方法)
接口 Adder {
|
特征 Adder
|
混合
不适用 |
class DavidBanner
|
控制结构
本节比较了 Java 和 Scala 中的 控制结构。
if
语句,单行
if (x == 1) { System.out.println(1); }
|
if x == 1 then println(x)
|
if
语句,多行
if (x == 1) {
|
if x == 1 then
|
if、else if、else
if (x < 0) {
|
if x < 0 then
|
if
作为方法体
public int min(int a, int b) {
|
def min(a: Int, b: Int): Int =
|
从 if
返回值
在 Java 中称为三元运算符
int minVal = (a < b) ? a : b;
|
val minValue = if a < b then a else b
|
while
循环
while (i < 3) {
|
while i < 3 do
|
for
循环,单行
for (int i: ints) {
|
//首选
|
for
循环,多行
for (int i: ints) {
|
for
|
for
循环,多个生成器
for (int i: ints1) {
|
for
|
带守卫 (if
) 表达式的生成器
List ints =
|
for
|
for
推导
不适用 |
val list =
|
switch/match
String monthAsString = "";
|
val monthAsString = day match
|
switch/match,每个 case 多个条件
String numAsString = "";
|
val numAsString = i match
|
try/catch/finally
try {
|
try
|
集合类
本节比较了 Java 和 Scala 中可用的集合类。
不可变集合类
有关如何创建不可变集合实例的示例。
序列
List strings = List.of("a", "b", "c");
|
val strings = List("a", "b", "c")
|
集合
Set set = Set.of("a", "b", "c");
|
val set = Set("a", "b", "c")
|
映射
Map map = Map.of(
|
val map = Map(
|
可变集合类
Scala 具有可变集合类,例如其 scala.collection.mutable 包中的 ArrayBuffer
、Map
和 Set
。将它们导入当前作用域后,它们将像刚才所示的不可变 List
、Vector
、Map
和 Set
示例一样创建。
Scala 还具有一个 Array
类,你可以将其视为对 Java array
原始类型的包装。Scala Array[A]
映射到 Java A[]
,因此你可以将此 Scala Array[String]
val a = Array("a", "b")
视为由这个 Java String[]
String[] a = {"a", "b"};
支持。但是,Scala Array
还具有 Scala 集合中预期的所有函数方法,包括 map
和 filter
val nums = Array(1, 2, 3, 4, 5)
val doubledNums = nums.map(_ * 2)
val filteredNums = nums.filter(_ > 2)
由于 Scala Array
的表示方式与 Java array
相同,因此你可以在 Scala 代码中轻松使用返回数组的 Java 方法。
尽管讨论了
Array
,但请记住,在 Scala 中通常有更适合Array
的替代方案。数组对于与其他语言(Java、JavaScript)进行交互很有用,并且在编写需要从底层平台中挤出最大性能的低级代码时也可能很有用。但通常,当你需要使用序列时,Scala 的惯用法是优先使用不可变序列,如Vector
和List
,然后在真正需要可变序列时使用ArrayBuffer
。
你还可以使用 Scala CollectionConverters
对象在 Java 和 Scala 集合类之间进行转换。在不同的包中有两个对象,一个用于从 Java 转换为 Scala,另一个用于从 Scala 转换为 Java。此表显示了可能的转换
Java | Scala |
---|---|
java.util.Collection | scala.collection.Iterable |
java.util.List | scala.collection.mutable.Buffer |
java.util.Set | scala.collection.mutable.Set |
java.util.Map | scala.collection.mutable.Map |
java.util.concurrent.ConcurrentMap | scala.collection.mutable.ConcurrentMap |
java.util.Dictionary | scala.collection.mutable.Map |
集合类上的方法
由于能够将 Java 集合视为流,因此 Java 和 Scala 现在拥有许多相同的常见函数方法
map
filter
forEach
/foreach
findFirst
/find
reduce
如果你习惯在 Java 中使用 lambda 表达式来使用这些方法,那么你就会发现很容易在 Scala 的 集合类 上使用相同的方法。
Scala 还有许多其他 集合方法,包括 head
、tail
、drop
、take
、distinct
、flatten
等等。起初你可能会疑惑为什么有这么多方法,但在使用 Scala 后你会意识到,由于 这些方法,你几乎不需要再编写自定义 for
循环了。
(这也意味着你也很少需要阅读自定义 for
循环。由于开发人员往往将十分之一的时间花在阅读代码上,而不是编写代码上,因此这一点很重要。)
元组
Java 元组的创建方式如下
Pair<String, Integer> pair =
new Pair<String, Integer>("Eleven", 11);
Triplet<String, Integer, Double> triplet =
Triplet.with("Eleven", 11, 11.0);
Quartet<String, Integer, Double,Person> triplet =
Quartet.with("Eleven", 11, 11.0, new Person("Eleven"));
其他 Java 元组名称包括 Quintet、Sextet、Septet、Octet、Ennead、Decade。
Scala 中任何大小的元组都可以通过将值放在括号内创建,如下所示
val a = ("eleven")
val b = ("eleven", 11)
val c = ("eleven", 11, 11.0)
val d = ("eleven", 11, 11.0, Person("Eleven"))
枚举
本节比较了 Java 和 Scala 中的枚举。
基本枚举
enum Color {
|
enum Color
|
参数化枚举
enum Color {
|
enum Color(val rgb: Int)
|
用户定义的枚举成员
enum Planet {
|
enum Planet(
|
异常和错误处理
本节介绍了 Java 和 Scala 中异常处理之间的差异。
Java 使用检查异常
Java 使用检查异常,因此在 Java 代码中,您历来编写 try
/catch
/finally
块,以及方法上的 throws
子句
public int makeInt(String s)
throws NumberFormatException {
// code here to convert a String to an int
}
Scala 不使用检查异常
Scala 惯用语是不使用这种检查异常。在使用可能引发异常的代码时,可以使用 try
/catch
/finally
块来捕获引发异常的代码中的异常,但从那里继续执行的方式不同。
解释这一点的最佳方法是,Scala 代码由返回值的表达式组成。因此,最终会将代码编写为一系列代数表达式
val a = f(x)
val b = g(a,z)
val c = h(b,y)
这很好,这只是代数。创建方程式来解决小问题,然后组合方程式来解决大问题。
而且非常重要的是——正如你从代数课程中所记得的——代数表达式不会短路——它们不会引发会炸毁一系列方程式的异常。
因此,在 Scala 中,我们的方法不会引发异常。相反,它们返回 Option
等类型。例如,此 makeInt
方法捕获可能的异常并返回 Option
值
def makeInt(s: String): Option[Int] =
try
Some(s.toInt)
catch
case e: NumberFormatException => None
Scala Option
类似于 Java Optional
类。如所示,如果字符串到整数的转换成功,则 Int
会在 Some
值中返回,如果失败,则会返回 None
值。 Some
和 None
是 Option
的子类型,因此该方法声明返回 Option[Int]
类型。
当你有 Option
值(例如 makeInt
返回的值)时,有许多方法可以处理它,具体取决于你的需求。此代码显示了一种可能的方法
makeInt(aString) match
case Some(i) => println(s"Int i = $i")
case None => println(s"Could not convert $aString to an Int.")
Option
通常在 Scala 中使用,并且内置于标准库中的许多类中。其他类似的类集,如 Try/Success/Failure 和 Either/Left/Right,提供了更大的灵活性。
有关处理 Scala 中的错误和异常的更多信息,请参阅 函数式错误处理 部分。
Scala 独有的概念
这总结了 Java 和 Scala 语言的比较。
Scala 中还有其他概念,目前在 Java 11 中没有同等概念。这包括