本文概述
诚然, ” Scala很难”这一说法确实有些道理, 但是学习曲线很值得投资。该语言的某些更复杂的功能(例如, Tuples, Functions, Macros等)最终使开发人员可以更轻松地编写更好的代码并通过在Scala中进行编程来提高性能。坦白地说, 我们是程序员, 如果我们不够聪明, 无法学习某种复杂的语言, 那么我们就做错了事。
Scala是一种类型安全的JVM语言, 它将面向对象的程序和函数式编程都结合到一种极其简洁, 逻辑且功能强大的语言中。有些人可能会惊讶地发现Scala并不像他们想象的那么新, 它于2003年首次推出。但是, 尤其是在过去的几年中, Scala才开始获得大量关注。这就引出了”为什么要使用Scala?”的问题。
本文研究了Scala的优势, 尤其是与Java相比(因为Scala编写为在JVM中运行)。 Scala不是创建”更好的Java”的唯一尝试。诸如Kotlin和Ceylon之类的替代方案也走了这条路, 但他们做出了一个基本决定, 即在语法上保持与Java语言本身非常接近, 以最大程度地减少学习时间。这似乎是一个不错的主意, 但最终还是有些自欺欺人, 因为它迫使你停留在许多完全相同的Java范式中, 这些范式就是首先要创建”更好的Java”的原因。
相比之下, Scala的创建目的是成为一种更好的语言, 以摆脱Java认为对开发人员来说过于严格, 过于繁琐或令人沮丧的那些方面。结果, 确实存在代码差异和范式转换, 这可能会使Scala编程的早期学习变得更加困难, 但是结果是一种更加简洁和井井有条的语言, 最终更易于使用并提高了生产率。
Scala与Java:哪个更复杂?
具有讽刺意味的是, 虽然Java语言的简单性是其成功的一部分, 但它也为其复杂性做出了贡献。当然, 你几乎可以用Java编写任何东西, 但是这样做所需的代码行可能令人生畏。另一方面, Scala中的编程结构稍微复杂一些。但是, 如果你可以编写稍微复杂一点的单行代码来代替Java的20条”更简单”的行, 那么哪一行真的更复杂?
事实是Java通常太冗长了。在Scala中, 编译器非常聪明, 因此避免了开发人员需要明确指定编译器可以推断的内容。比较一下这个简单的” Hello World!” Java vs. Scala中的程序:
Java中的Hello World:
public class HelloJava {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
斯卡拉的Hello World:
object HelloScala {
def main(args: Array[String]): Unit = {
println("Hello World!")
}
}
尽管此处的两种语言之间并没有很大的区别, 但即使在这个简单的示例中, Scala的详细程度也较低。
举一个更实际的例子, 让我们看一下创建一个简单的字符串列表:
Java:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
规模:
val list = List("1", "2", "3")
当然, Java中有一些技巧可以缩短代码, 但在标准用法中却不行。
现在考虑一种情况, 我们有一个数字字符串列表, 但我们希望将该列表转换为整数列表:
Java:
List<Integer> ints = new ArrayList<Integer>();
for (String s : list) {
ints.add(Integer.parseInt(s));
}
规模:
val ints = list.map(s => s.toInt)
借助Scala的功能特性, 这种转换变得非常简单。
一个类示例:Java vs. Scala
让我们更进一步, 比较Java和Scala中的标准Bean /普通旧Java对象(POJO)创建。
首先, Java版本:
public class User {
private String name;
private List<Order> orders;
public User() {
orders = new ArrayList<Order>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
}
public class Order {
private int id;
private List<Product> products;
public Order() {
products = new ArrayList<Product>();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
}
public class Product {
private int id;
private String category;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
ew Lotta代码。
现在是Scala版本:
class User {
var name: String = _
var orders: List[Order] = Nil
}
class Order {
var id: Int = _
var products: List[Product] = Nil
}
class Product {
var id: Int = _
var category: String = _
}
我们说哪种语言比较复杂?
我们公平吗?
如果你到现在为止都是Java程序员, 那么你可能会认为我在对代码进行不公平的比较。毕竟, 没有什么能阻止我使用Java创建公共变量, 然后摆脱getter和setter。
但是, 如果你回想一下Java中的getter和setter背后的原因, 那么它专门用于将来的验证。也就是说, 如果以后需要在获取或设置变量时添加一些逻辑, 则必须重新编写那些公共变量以使用方法(这就是Java中鼓励使用getter和setter开头的原因。 )。但是, 在Scala编程中并非如此。由于采用了语言设计, 因此抽象无需使用getter和setter即可保持完整。例如, 考虑一下Scala中经过修改的User类, 如果你尝试将名称设置为null, 则会抛出NullPointerException:
class User {
private var _name: String = _
var orders: List[Order] = Nil
def name = _name
def name_=(name: String) = {
if (name == null) {
throw new NullPointerException("User.name cannot be null!")
}
_name = name
}
你仍然可以像这样设置名称:
user.name = "John Doe"
请注意, 这完全消除了预先配置方法访问器的需要。
而且, 由于Scala更喜欢不变性, 因此我可以使用case类更简洁地在Scala中编写此代码:
case class User(name: String, orders: List[Order])
case class Order(id: Int, products: List[Product])
case class Product(id: Int, category: String)
太疯狂了, 我要写的代码少了多少。
进一步举例说明
现在考虑上述类的场景, 我想在User类中添加一个漂亮的小方法, 该方法返回用户订购的所有产品的列表:
在Java的冗长世界中:
public List<Product> getProducts() {
List<Product> products = new ArrayList<Product>();
for (Order order : orders) {
products.addAll(order.getProducts());
}
return products;
}
幸运的是, java.util.List有一个addAll方法, 否则在Java中getProducts()可能更长。
另一方面, 在Scala中, 我们需要做的是:
def products = orders.flatMap(o => o.products)
你会看到Scala语言实现的规模要小得多。是的, 对于Scala新手来说, 它似乎更加复杂, 但是一旦你真正理解了其背后的概念, Scala代码将比Java代码看起来更加简单。
让我们变得更加复杂。如果我们只想获取特定类别中的产品怎么办?
在这种情况下, 我们无法利用java.util.List中的addAll方法, 因此Java中的情况变得更糟:
public List<Product> getProductsByCategory(String category) {
List<Product> products = new ArrayList<Product>();
for (Order order : orders) {
for (Product product : order.getProducts()) {
if (category.equals(product.getCategory())) {
products.add(product);
}
}
}
return products;
}
但是, 在Scala中, 代码仍然相当简单。我们只需要使用flatMap即可将拼合在一起的每个Order的产品列表合并到一个列表中, 然后进行过滤以仅包括与类别匹配的产品:
def productsByCategory(category: String) = orders.flatMap(o => o.products).filter(p => p.category == category)
动态与静态
在过去的几年中, 当然不缺少新语言, 但是尽管最近出现的几乎所有其他语言都是动态的, 但是Scala是静态类型的。
作为专业的开发人员(尽管我知道并使用许多动态语言), 我认为编译时检查对于编写可靠的代码非常重要。在动态语言中, 除非你在各种情况下实际运行它, 否则你将永远无法确保自己的代码没有足够的错误和健壮性。这可能导致代码中潜在的严重缺陷, 直到代码投入生产后才可能实现。
本文总结
希望本文将Java与Scala进行了充分的融合, 以使你对Scala的功能和能力有初步的了解, 并激发了你学习该语言的兴趣。它不仅是一种很棒的语言, 它可以使编程变得不那么乏味并且更加有趣, 而且还被世界上一些最大的公司(LinkedIn, Twitter, FourSquare, The Guardian等)使用。
Scala开发人员的空缺职位数量不断增加, 证明了Scala的受欢迎程度和使用量正在迅速上升。如果你还没有这样做的话, 那么现在将是一个开始尝试并停止询问”为什么要学习Scala?”的好时机。