Scala 与 Java:我为什么要学习 Scala?

已发表: 2022-03-11

诚然,“Scala 很难”的说法是有道理的,但学习曲线非常值得投资。 该语言的一些更复杂的特性(元组、函数、宏等等)最终使开发人员更容易编写更好的代码并通过在 Scala 中编程来提高性能。 坦率地说,我们是程序员,如果我们不够聪明,无法学习一种具有一定复杂性的语言,那么我们就做错了。

Scala 是一种类型安全的 JVM 语言,它将面向对象和函数式编程合并为一种极其简洁、合乎逻辑且非常强大的语言。 有些人可能会惊讶地发现,Scala 并不像他们想象的那么新,它于 2003 年首次推出。然而,特别是在过去的几年里,Scala 已经开始发展出大量的追随者。 这就引出了“为什么选择 Scala?”的问题。

本文探讨了 Scala 的优势,尤其是与 Java 相比(因为 Scala 是为在 JVM 中运行而编写的)。 Scala 并不是创建“更好的 Java”的唯一尝试。 Kotlin 和 Ceylon 等替代品也走上了这条道路,但他们做出了基本决定,即在语法上与 Java 语言本身保持非常接近,以最大限度地减少学习曲线。 这似乎是一个好主意,但它最终有点弄巧成拙,因为它迫使您停留在许多相同的 Java 范式中,而这些范式正是最初想要创建“更好的 Java”的原因。

相比之下,Scala 是专门为成为一种更好的语言而创建的,它摆脱了 Java 中那些它认为限制性、过于乏味或让开发人员感到沮丧的方面。 结果,确实存在代码差异和范式转换,这可能会使早期学习 Scala 编程变得更加困难,但结果是一种更清晰且组织良好的语言,最终更易于使用并提高了生产力。

当并排比较 Scala 和 Java 时,Scala 的效率是显而易见的。

Scala 与 Java:哪个更复杂?

尽管 Java 语言的简单性是其成功的一部分,但具有讽刺意味的是,它也导致了其复杂性。 当然,您几乎可以用 Java 编写任何东西,但是这样做所需的代码行数可能令人生畏。 另一方面,Scala 中的编程结构稍微复杂一些。 但是,如果您可以编写稍微复杂一点的单行代码来代替 20 行“更简单”的 Java 代码,那么哪一个更复杂呢?

事实上,Java 通常过于冗长。 在 Scala 中,编译器非常聪明,因此这避免了开发人员需要明确指定编译器可以推断的那些东西。 例如,比较这个简单的“Hello World!” Java 与 Scala 中的程序:

Java中的Hello World:

 public class HelloJava { public static void main(String[] args) { System.out.println("Hello World!"); } }

Scala 中的 Hello World:

 object HelloScala { def main(args: Array[String]): Unit = { println("Hello World!") } }

虽然这两种语言之间没有很大的区别,但即使在这个简单的示例中,Scala 也不那么冗长。

举一个更实际的例子,让我们看一下创建一个简单的字符串列表:

爪哇:

 List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3");

斯卡拉:

 val list = List("1", "2", "3")

当然,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 与 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; } }

呸。 洛塔代码。

现在是 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。 例如,考虑一下,如果您尝试将名称设置为 null,则 Scala 中的这个修改后的User类会引发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 更喜欢不变性,我可以在 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类中添加一个漂亮的小方法,该方法返回User订购的所有Products的列表:

在 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方法,或者getProducts()在 Java 中会更长。

另一方面,在 Scala 中,我们只需要:

 def products = orders.flatMap(o => o.products)

您可以看到 Scala 语言实现要小得多。 是的,对于 Scala 新手来说,它可能看起来更复杂,但是一旦你真正完全理解了它背后的概念,Scala 代码看起来会比 Java 代码简单得多。

让我们在这里变得更复杂一些。 如果我们只想获取特定Category中的Products怎么办?

在这种情况下,我们无法利用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 flattened 的产品列表组合在一个列表中,然后我们过滤以仅包含与类别匹配的产品:

 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?”的好时机。

相关:使用 Scala 宏和准引号减少样板代码