因此,我正在尝试使用Scala,因为我想编写一个解析器,而Scala Parsers API似乎非常合适。 毕竟,我可以在Scala中实现解析器并将其包装在Java接口后面,因此除了附加的运行时依赖关系之外,不应该存在任何互操作性问题。
在几天后真正真正地习惯了Scala语法的强大功能之后,以下是我回到编写Java时最想错过的十件事:
1.多行字符串
这是我个人的最爱,也是一种非常棒的功能,应该使用任何语言。 甚至PHP都有:多行字符串。 就像写作一样简单:
println ("""Dear reader,If we had this feature in Java,
wouldn't that be great?Yours Sincerely,
Lukas""")
这在哪里有用? 使用SQL,当然! 这是使用jOOQ和Scala运行纯SQL语句的方法:
println(DSL.using(configuration).fetch("""SELECT a.first_name, a.last_name, b.titleFROM author aJOIN book b ON a.id = b.author_idORDER BY a.id, b.id""")
)
这不仅适用于静态字符串。 使用字符串插值,您可以轻松地将变量注入到这样的字符串中:
val predicate =if (someCondition)"AND a.id = 1"else""println(DSL.using(configuration)// Observe this little "s".fetch(s"""SELECT a.first_name, a.last_name, b.titleFROM author aJOIN book b ON a.id = b.author_id-- This predicate is the referencing the-- above Scala local variable. Neat!WHERE 1 = 1 $predicateORDER BY a.id, b.id""")
)
太棒了,不是吗? 对于SQL,Scala具有很大的潜力。
2.分号
我真心没有错过他们一点。 我构造代码的方式(可能也是大多数人构造代码的方式),Scala似乎根本不需要分号。 在JavaScript中,我不会说同样的话。 JavaScript的解释性和非类型安全性质似乎表明,放弃可选的语法元素可以保证您一臂之力。 但是Scala不支持。
val a = thisIs.soMuchBetter()
val b = no.semiColons()
val c = at.theEndOfALine()
这可能是由于Scala的类型安全性导致的,这将使编译器在那些罕见的模棱两可的情况下抱怨,但这只是有根据的猜测。
3.括号
这是一个雷区,在许多情况下,省略括号似乎很危险。 实际上,在调用方法时,您也可以忽略点:
myObject method myArgument
由于这种方法会产生大量歧义,尤其是在链接更多的方法调用时,我认为最好避免这种技术。 但是在某些情况下,“忘记”父母很方便。 例如
val s = myObject.toString
4.类型推断
在Java中,这真的很烦人,与此同时,似乎还有许多其他语言也做到了这一点。 Java仅具有有限的类型推断功能,而且事情并不尽如人意 。
在Scala中,我可以简单地写:
val s = myObject.toString
……而不关心s
是String类型的事实。 有时,但仅在某些情况下,我希望明确指定引用的类型。 在那种情况下,我仍然可以做到:
val s : String = myObject.toString
5.案例分类
我想我想写另一个POJO,它具有40个属性,构造函数,getter,setter,equals,hashCode和toString
-没人说。 曾经
Scala具有案例类。 用单线编写的简单不可变的pojos。 以Person案例类为例:
case class Person(firstName: String, lastName: String)
我同意,必须确实写下一次属性。 但是其他一切应该是自动的。
以及如何创建此类案例类的实例? 轻松,你甚至都不需要new
运营商(事实上,它完全逃脱了我的想象,为什么new
真正需要摆在首位):
Person("George", "Orwell")
而已。 您还想写些什么以符合企业标准?
边注
好的,现在有些人会争辩说要使用lombok项目 。 基于注释的代码生成是胡说八道,应最好避免。 实际上,Java生态系统中的许多注释简单地证明了Java语言的(而且将永远是)其演化能力非常有限这一事实。 以@Override
为例。 这应该是关键字,而不是注释。 您可能会认为这是表面上的区别,但是我说Scala已经证明注释几乎总是错误的工具。 还是您最近看过带有大量注释的 Scala代码?
6.到处都有方法(功能!)
我认为,这实际上是任何语言中最有用的功能之一。 为什么我们总是必须将方法链接到特定的类? 为什么我们不能简单地拥有任何作用域级别的方法? 因为我们可以,所以使用Scala:
// "Top-level", i.e. associated with the package
def m1(i : Int) = i + 1object Test {// "Static" method in the Test instancedef m2(i : Int) = i + 2def main(args: Array[String]): Unit = {// Local method in the main methoddef m3(i : Int) = i + 3println(m1(1))println(m2(1))println(m3(1))}
}
对? 为什么我不能在另一个方法中定义本地方法? 我可以使用Java中的类来做到这一点:
public void method() {class LocalClass {}System.out.println(new LocalClass());
}
局部类是方法局部的内部类。 这几乎没有用,但是真正有用的是局部方法。
JavaScript或REPL也支持这些功能。 这对于在应用程序范围之外测试小型算法或概念非常有用。
在Java中,我们通常倾向于这样做:
public class SomeRandomClass {// [...]public static void main(String[] args) {System.out.println(SomeOtherClass.testMethod());}// [...]
}
在Scala中,我将在REPL中编写以下代码:
println(SomeOtherClass.testMethod)
还请注意始终可用的println
方法。 纯金方面的高效调试。
8.数组不是(很多)特例
在Java中,除了基本类型以外,还有一些我们称为数组的怪异事物。 数组起源于一个完全独立的宇宙,在这里我们必须记住起源于柯克上尉(大约)时代的古怪规则:
是的,规则如下:
// Compiles but fails at runtime
Object[] arrrrr = new String[1];
arrrrr[0] = new Object();// This works
Object[] arrrr2 = new Integer[1];
arrrr2[0] = 1; // Autoboxing// This doesn't work
Object[] arrrr3 = new int[];// This works
Object[] arr4[] = new Object[1][];// So does this (initialisation):
Object[][] arr5 = { { } };// Or this (puzzle: Why does it work?):
Object[][] arr6 = { { new int[1] } };// But this doesn't work (assignment)
arr5 = { { } };
是的,清单可以继续。 从句法上讲,使用Scala,数组不再是一种特殊情况:
val a = new Array[String](3);
a(0) = "A"
a(1) = "B"
a(2) = "C"
a.map(v => v + ":")// output Array(A:, B:, C:)
如您所见,数组的行为与其他集合非常相似,包括可以在其上使用的所有有用方法。
9.符号方法名称
现在,这个话题更具争议性,因为它使我们想起了运算符重载的危险 。 但是,每隔一段时间,我们希望有类似的东西。 可以让我们写的东西:
val x = BigDecimal(3);
val y = BigDecimal(4);
val z = x * y
非常直观地,z的值应为BigDecimal(12)
。 那不能太难了,可以吗? 我不在乎*
的实现是否真的是一个称为multiply()
的方法。 写下该方法时,我想使用看起来很普通的运算符进行乘法。
顺便说一句,我也想用SQL做到这一点。 这是一个例子:
select ( AUTHOR.FIRST_NAME || " " || AUTHOR.LAST_NAME,AUTHOR.AGE - 10
)
from AUTHOR
where AUTHOR.ID > 10
fetch
那没有道理吗? 我们知道||
表示concat(在某些数据库中)。 我们知道-
(减号)和>
(大于)的含义。 为什么不写呢?
上面是在Scala中的jOOQ的编译示例。
注意:警告
允许操作符重载或符号方法名之类的东西总是存在缺点。 它可能(并将被)滥用。 图书馆与Scala语言本身一样多 。
10.元组
作为一个SQL人员,这再次是我在其他语言中最想念的功能之一。 在SQL中,所有内容都是TABLE或ROW。 实际上,很少有人知道这一点 ,并且很少有数据库实际上支持这种思维方式。
Scala没有ROW类型(实际上是记录),但是至少有匿名元组类型。 将行视为具有命名属性的元组,而案例类将命名为行:
- 元组:具有类型化和索引元素的匿名类型
- 行:具有类型化,命名和索引元素的匿名类型
- 案例类:带有类型化元素和命名元素的命名类型
在Scala中,我可以这样写:
// A tuple with two values
val t1 = (1, "A")// A nested tuple
val t2 = (1, "A", (2, "B"))
在Java中,可以完成类似的操作,但是您必须自己编写该库,并且不提供语言支持:
class Tuple2<T1, T2> {// Lots of bloat, see missing case classes
}class Tuple3<T1, T2, T3> {// Bloat bloat bloat
}
然后:
// Yikes, no type inference...
Tuple2<Integer, String> t1 = new Tuple2<>(1, "A");// OK, this will certainly not look nice
Tuple3<Integer, String, Tuple2<Integer, String>> t2 =new Tuple3<>(1, "A", new Tuple2<>(2, "B"));
jOOQ充分利用了上述技术,将SQL的行值表达式带到Java,而且令人惊讶的是,在大多数情况下,您可以在不丢失类型推断的情况下做到这一点,因为jOOQ是一种流利的API,您从未真正将值分配给局部变量。例:
DSL.using(configuration).select(T1.SOME_VALUE).from(T1).where(// This ROW constructor is completely type saferow(T1.COL1, T1.COL2).in(select(T2.A, T2.B).from(T2))).fetch();
结论
当然,这是一篇有关scala的文章,与Java稍有抵触。 不要误会我的意思。 我绝不希望完全迁移到Scala。 我认为Scala语言远远超出了任何有用软件中的合理范围。 有许多看起来不错的小功能和头,但不可避免地会炸掉您的脸,例如:
-
implicit
转换。 这不仅很难管理,而且还严重降低了编译速度。 此外,使用implicit
合理地实现语义版本控制可能完全是不可能的,因为不可能通过偶然的向后不兼容预见所有可能的客户端代码损坏。 - 本地导入乍一看似乎很棒,但是当人们开始部分导入或重命名本地范围的类型时,它们的功能很快使代码难以理解。
- 符号方法名称最常被滥用。 以解析器API为例,它具有诸如
^^
,^^^
,^?
等方法名称^?
或~!
尽管如此,我认为本文中列出的Scala与Java相比的优点也可以全部用Java实现:
- 几乎没有破坏向后兼容的风险
- 用(可能)不太大的努力,在JLS方面
- 对开发人员的生产力产生巨大影响
- 对Java的竞争力产生巨大影响
无论如何,Java 9将是另一个有前途的版本,其热门话题包括值类型, 声明站点差异 , 特殊化(非常有趣!)或ClassDynamic。
有了这些巨大的变化,我们希望上面的一些小改进还有一定的余地,这将为日常工作增加更多的直接价值。
翻译自: https://www.javacodegeeks.com/2014/08/the-10-most-annoying-things-coming-back-to-java-after-some-days-of-scala.html