scala语法
Java developers who are curious about Scala might find the differences in syntax to be daunting. So below is a guide to some of the common Scala syntax rules that differ from Java’s. At the end, we’ll walk through a comprehensive example of Scala code.
对Scala感到好奇的Java开发人员可能会发现语法差异令人生畏。 因此,以下是一些不同于Java的常见Scala语法规则的指南。 最后,我们将逐步介绍Scala代码的综合示例。
Note that we won’t explore the advanced, powerful features of Scala here. For-comprehensions, implicits, pattern-matching, and other features are better discussed in dedicated articles. Instead, this article will help any Java developers who are interested in exploring Scala, but are simply having a hard time understanding the code examples they’re seeing.
请注意,这里我们不会探讨Scala的高级功能。 对于理解 , 隐式 , 模式匹配和其他功能,可以在专门的文章中进行更好的讨论。 取而代之的是,本文将为所有对Scala感兴趣的Java开发人员提供帮助,但他们只是很难理解他们所看到的代码示例。
Let’s dive in.
让我们潜入。
类型声明位于变量名之后 (Type declarations come after variable names)
In Java, we declare types, then variable names, like so:Animal a = getAnimal();
在Java中,我们先声明类型,然后声明变量名,如下所示: Animal a = getAnimal();
In Scala, the type comes afterwards:val a: Animal = getAnimal();
在Scala中,类型随后出现: val a: Animal = getAnimal();
变量声明为不可变(val)或可变(var) (Variables are declared as either immutable (val) or mutable (var))
Rather than beginning a variable declaration with the class name, as we do in Java, we begin them with either val
or var
in Scala.
与其像在Java中那样用类名开始变量声明,不如在Scala中以val
或var
开头。
val
(short for value) indicates that the variable is immutable (similar to final
in Java):val a: Animal = getAnimal(); // `a` cannot be subsequently reassigned
val
( value的缩写)表示变量是不可变的(类似于Java中的final
): val a: Animal = getAnimal(); // `a` cannot be subsequently reassigned
val a: Animal = getAnimal(); // `a` cannot be subsequently reassigned
var
(short for variable) indicates that the variable is mutable:var a: Animal = getAnimal(); // `a` can be subsequently reassigned
var
( 变量的缩写)表示变量是可变的: var a: Animal = getAnimal(); // `a` can be subsequently reassigned
var a: Animal = getAnimal(); // `a` can be subsequently reassigned
Scala favors immutability, so most often, you’ll see val
used.
Scala赞成不变性,因此大多数情况下,您会看到使用了val
。
通常可以省略类型声明 (Type declarations can usually be omitted)
Scala has very strong type inference. So we rarely need to explicitly declare the type. That’s one reason why the class comes last when declaring a variable; it’s easy to simply omit it that way:var a = getAnimal(); // `a` is still an instance of Animal
Scala具有非常强的类型推断。 因此,我们很少需要显式声明类型。 这就是为什么在声明变量时类排在最后的原因。 可以很容易地以这种方式省略它: var a = getAnimal(); // `a` is still an instance of Animal
var a = getAnimal(); // `a` is still an instance of Animal
不需要分号来表示行尾 (Semicolons are not required to denote the end of a line)
Semicolons are really only needed when multiple statements occur in the same line:var a = getAnimal(); println(a)
实际上,仅当在同一行中出现多个语句时才需要分号: var a = getAnimal(); println(a)
var a = getAnimal(); println(a)
We can, and almost always do, omit semicolons at the end of lines:var a = getAnimal()
我们可以并且几乎总是这样做,在行尾省略分号: var a = getAnimal()
方法和函数用'def'关键字声明 (Methods and functions are declared with the ‘def’ keyword)
Methods and functions (a function is like a method, but doesn’t belong to any class) are declared with the def
keyword. Their return types are declared after the function name and a colon. And their bodies typically come after an equals sign:
方法和函数( 函数类似于方法 ,但不属于任何类)使用def
关键字声明。 它们的返回类型在函数名称和冒号之后声明。 他们的身体通常在等号后出现:
def getAnimal(): Animal = {
// body goes here ...
}
调用无参数方法或函数时,可以省略括号 (When calling a no-argument method or function, the parenthesis can be omitted)
In Java, we always provide the parentheses when invoking a method:
在Java中,我们总是在调用方法时提供括号:
Animal a = getAnimal();
When calling a Scala function or method that takes no arguments, the parenthesis can be omitted:
当调用不带参数的Scala函数或方法时,可以省略括号:
val a = getAnimal()
// or
val a = getAnimal
方法和函数无需显式返回任何内容 (Methods and functions do not need to explicitly return anything)
The last value or expression in a method or function becomes the implicit return value:
方法或函数中的最后一个值或表达式成为隐式返回值:
def add(x: Int, y: Int): Int = {
x + y // the sum of x and y will be implicitly be returned
}
So while the return keyword exists in Scala, it is almost never used.
因此,尽管return关键字存在于Scala中,但几乎从未使用过。
单位表示无效 (Unit means void)
If a Java method has no return value, then it is declared as returning void:
如果Java方法没有返回值,则将其声明为返回void :
public void output(String s) {
System.out.println(s);
}
In Scala, the return type would be declared as Unit:
在Scala中,返回类型将声明为Unit :
def output(s: String): Unit = {
println(s)
}
方法和函数很少需要声明其返回类型 (Methods and functions rarely need to declare their return type)
Scala’s strong type inference means that we rarely need to declare the return type of our functions/methods:
Scala的强类型推断意味着我们几乎不需要声明函数/方法的返回类型:
def add(x: Int, y: Int) = {
x + y // The compiler infers that an Int will be returned
}
Note that we still need to declare the return type in cases where:
请注意,在以下情况下,我们仍然需要声明返回类型:
- the method is abstract 该方法是抽象的
- we want to return a superclass of the inferred return type 我们要返回推断的返回类型的超类
- we simply want to be more clear 我们只是想更清楚
def getAnimal(): Animal {
// if we omitted the return type, Scala would infer Dog
new Dog()
}
方法和功能不需要大括号 (Methods and functions do not require braces)
If the body of a method or function does not span more than one line (or expression), then the braces can be omitted:
如果方法或函数的主体不超过一行(或表达式),则可以省略花括号:
def add(x: Int, y: Int) =
x + y// ordef add(x: Int, y: Int) = x + y
可以使用命名参数调用方法和函数 (Methods and functions can be invoked with named arguments)
In Java, when invoking a method that accepts multiple parameters, we cannot include the parameter names. Instead, we simply pass the values, in the order in which they are declared in the method:
在Java中,当调用接受多个参数的方法时,我们不能包含参数名称。 相反,我们只是按照在方法中声明它们的顺序传递值:
int i = add(6, 8);
In Scala, we can choose to either call a method/function the “Java way”, or we can include all, or some, of the parameter names:
在Scala中,我们可以选择以“ Java方式”调用方法/函数,也可以包含全部或部分参数名称:
val i = add(6, 8)
// or
val i = add(x = 6, y = 8)
// or
val i = add(x = 6, 8)
// note: below will not compile:
val i = add(6, y = 8)
特性而不是接口 (Traits instead of interfaces)
While Java offers interfaces, Scala offers traits. Like Java interfaces, traits can include abstract methods. And like Java interfaces’ default methods, traits can offer implemented methods. Unlike interfaces, traits can also include variables.
Java提供接口,而Scala提供traits 。 像Java接口一样,特征可以包含抽象方法。 与Java接口的默认方法一样,特征可以提供已实现的方法。 与接口不同,特征还可以包含变量。
trait Shape {
val point: Point
def print() // no implementation, so implicitly abstract
}
`extends`和`with` (`extends` and `with`)
As in Java, Scala classes cannot extend multiple parent classes. Also similar to Java, Scala classes can implement (or in Scala parlance, extend) multiple traits.
与Java中一样,Scala类不能扩展多个父类。 同样类似于Java,Scala类可以实现(或以Scala的话来说, extend )多个特征。
When a Scala class extends either a class or a trait, the extends
keyword is used. If the class then extends any additional traits, then the with
keyword is used:
当Scala类扩展类或特征时,将使用extends
关键字。 如果该类随后扩展了任何其他特征,则使用with
关键字:
class Shape { ... }trait Drawable { ... }trait Movable { ... }class Circle extends Shape with Drawable with Movableclass Raster extends Drawable with Movable
声明类时,其成员立即在括号中列出 (Classes are declared with their members immediately listed in parenthesis)
Whereas we might define a simple class in Java like this:
而我们可能会像这样在Java中定义一个简单的类:
public class Foo {
private String a;
private int b;
// getters, setters, constructors, etc
}
We would typically define a Scala class all in one line, like so:
我们通常会在一行中全部定义一个Scala类,如下所示:
class Foo(a: String, b: Int) { }
Brackets are optional, unless additional members, methods, etc, need to be declared:
括号是可选的,除非需要声明其他成员,方法等:
class Foo(a: String, b: Int)
案例类代表专门的数据类 (Case classes represent specialized data classes)
Java 14 introduced the concept of records. If you’re familiar with records, then you’ll quickly become familiar with case classes. Otherwise, case classes are special data classes. Their syntax is similar to regular classes:
Java 14引入了记录的概念。 如果您熟悉记录,那么您将很快熟悉案例类。 否则,案例类是特殊的数据类。 它们的语法类似于常规类:
case class Foo(a: String, b: Int)
However, the compiler auto-generated the standard methods required of a data class, such as equals()
, hashCode()
and toString()
methods, getters, etc.
但是,编译器会自动生成数据类所需的标准方法,例如equals()
, hashCode()
和toString()
方法,getter等。
It also generates a “companion object” with an apply()
and unapply()
method (we’ll discuss these a bit later).
它还会使用apply()
和unapply()
方法生成一个“伴侣对象”(我们将在后面讨论)。
泛型类型用方括号( []
)表示,而不用尖括号( <>
)表示 (Generic types are denoted by square brackets ([]
), rather than angle brackets (<>
))
In Java, we denote a generic type using angle brackets:List<String> list;
在Java中,我们使用尖括号表示通用类型: List<String> list;
In Scala, square brackets are used to denote generic types:List[String] list
在Scala中,方括号用于表示通用类型: List[String] list
字符串插值是通过以`s'为前缀的字符串文字完成的 (String interpolation is done with String literals prefixed by `s`)
In Java, to quickly concatenate Strings, we use the plus (+) symbol:System.out.println("Hello, "+ firstName + " "+ lastName + "!");
在Java中,为了快速连接字符串,我们使用加号(+): System.out.println("Hello, "+ firstName + " "+ lastName + "!");
In Scala, we perform String interpolation by prefixing a String literal with s, and by referring to variables with the dollar sign ($):println(s"Hello, $firstName $lastName!")
在Scala中,我们通过在字符串文字前加上s并引用带有美元符号( $ )的变量来执行字符串插值: println(s"Hello, $firstName $lastName!")
To access a nested value, or an expression, curly braces are used:
要访问嵌套值或表达式,请使用花括号:
println(s"Hello, ${name.first} ${name.middle.getOrElse("")} ${name.last}!")
匿名函数看起来类似于Java lambda,但带有=>符号 (Anonymous functions look similar to Java lambdas, but with the => symbol)
Java provides syntactic sugar when constructing lambda functions:list.foreach(item -> System.out.println("* " + item));
Java在构造lambda函数时提供了语法糖: list.foreach(item -> System.out.println("* " + item));
In Scala, we use the =>
symbol with anonymous functions:list.foreach(item => println(s"* $item"))
在Scala中,我们使用=>
符号和匿名函数: list.foreach(item => println(s"* $item"))
下划线充当占位符 (Underscores serve as a placeholder)
Underscores are used by Scala as placeholders. You’ll see underscores in a few different use cases, including:
下划线由Scala用作占位符。 您将在一些不同的用例中看到下划线,包括:
Anonymous functions:Rather than declaring a variable in the anonymous function:list.foreach(item => println(item))
匿名函数:而不是在匿名函数中声明变量: list.foreach(item => println(item))
We can omit the item variable and replace it with an underscore:list.foreach(println(_))
我们可以省略item变量,并用下划线替换: list.foreach(println(_))
Note, similar to Java, we can simplify the line above by providing a reference to the println
function (which will implicitly take the current variable as an argument):list.foreach(println)
注意,类似于Java,我们可以通过提供对println
函数的引用(将隐式地将当前变量作为参数)引用来简化上面的代码: list.foreach(println)
Pattern matching:When we perform pattern matching, we are attempting to match an instance with its type. Say we have a case class like so:
模式匹配:执行模式匹配时,我们试图将实例与其类型匹配。 假设我们有一个案例类,如下所示:
case class Dog(name: String) extends Animal
If we have an instance a of an Animal, we may try to determine if it is a Dog with a particular name:
如果我们有动物的实例a ,我们可以尝试确定它是否是具有特定名称的狗:
a match {
case Dog(name) => println(s"Found a dog named $name");
...
}
Or, we might not care about the Dog’s name. Dog
’s constructor still takes an argument, so we can simply use the underscore as a placeholder:
或者,我们可能不在乎狗的名字。 Dog
的构造方法仍然需要一个参数,因此我们可以简单地使用下划线作为占位符:
a match {
case Dog(_) => println(s"Found some dog");
...
}
Likewise, we might want to know if we’ve found a Dog
, or anything other than a Dog
. Again we’d use an underscore:
同样,我们可能想知道是否找到了Dog
或除Dog
以外的任何东西。 同样,我们将使用下划线:
a match {
case Dog(_) => println(s"Found a dog");
case _ => println("Found something other than a dog")
}
随播对象而不是静态对象 (Companion objects instead of statics)
Java has the static keyword, which describes variables and methods that are tied to a particular class, rather than instances of an object.
Java具有static关键字,该关键字描述与特定类 (而不是对象的实例)相关联的变量和方法。
public class UserManager {
public static void save(User u) {
//...
}
}// we can invoke that static method via:UserManager.save(new User("Pat"));
Scala does not offer the static keyword. However, with Scala, we can create objects, which effectively offer the same thing:
Scala不提供static关键字。 但是,使用Scala,我们可以创建对象,有效地提供相同的功能:
object User {
def save(u: User) = {
// ...
}
}// we can invoke that object's method via:User.save(new User("Pat"))
Moreover, Scala allows us to create “companion objects”; that is, objects that correspond with identically-named classes:
而且,Scala允许我们创建“伴侣对象”。 也就是说,与名称相同的类相对应的对象 :
class User(name: String) {
}// This is the companion object to the User class
object User {
def apply() {
new User("Noname")
}
def apply(name: String) = {
new User(name)
}
def unapply(u: User) = {
Some(u.name)
}
def save(u: User) = {
// ...
}
}
While we can define any functions in a class’ companion object, the apply()
and unapply()
methods have special meaning.
虽然我们可以在类的伴随对象中定义任何函数,但是apply()
和unapply()
方法具有特殊的含义。
If we were to call the object’s name, followed by parenthesis with any number of arguments, the corresponding apply(…)
method in the object would be invoked.
如果要调用对象的名称,然后调用带有任意数量参数的括号,则将调用对象中的相应apply(…)
方法。
In the above example, invoking User()
would, under the covers, cause User.apply()
to be invoked; invoking User("Chris")
would actually invoke User.apply("Chris")
. Commonly, an instance of the companion class is constructed and returned. In this manner, a companion object’s apply()
method(s) can — and typically are — used as factory methods.
在上面的示例中,在User.apply()
调用User()
将导致User.apply()
被调用; 调用User("Chris")
实际上会调用User.apply("Chris")
。 通常,将构造并返回伴随类的实例。 通过这种方式,伴侣对象的apply()
方法可以(通常是)用作工厂方法。
For this reason, we will typically see objects being created like this:
因此,我们通常会看到这样创建的对象:
val u = User("Suzie")
instead of this:
代替这个:
val u = new User("Suzie")
On the other hand, unapply()
is used as a deconstructor. Whenever the individual components of a class are needed (such as during pattern matching), its unapply()
method is invoked behind the scenes.
另一方面, unapply()
用作解构函数。 每当需要类的各个组件时(例如在模式匹配期间),都会在后台调用其unapply()
方法。
一个综合的例子 (A comprehensive example)
Let’s analyze a block of Scala code based on what we’ve just gone over:
让我们根据刚刚介绍的内容来分析Scala代码块:
case class Point(x: Int, y: Int) // 1trait Drawable { // 2
def draw(): Unit // 3
}abstract class Shape(val center: Point) { // 4
def area(): Double
}case class Circle(override val center: Point, radius: Int) extends Shape(center) with Drawable { // 5
override def draw(): Unit = { // 6
println(s"⊙ ")
}
override def area() = Math.PI * (radius * radius)
def growBy(add: Int) = Circle(center, radius + add) // 7
}case class Square(override val center: Point, side: Int) extends Shape(center) with Drawable {
override def draw(): Unit = {
println("◼︎")
}
override def area() = Math.PI * (side * side)
}object Canvas { // 8
def main(args: Array[String]): Unit = { // 9
val circle1 = Circle(Point(50, 25), radius = 10) // 10
val circle2 = circle1.growBy(5)
val square = Square(Point(100, 150), side = 30)
val l: List[Drawable] = List(circle1, circle2, square) // 11
l.foreach(_.draw()) // 12
l.map(d => d match { // 13
case Circle(_, r) => s"Circle of radius $r" // 14
case Square(_, s) => s"Square of side length $s"
case _ => "Unknown shape" // 15
}).foreach(println) // 16
}
}
We define a case class,
Point
. The compiler creates a bunch of boilerplate for us behind the scenes, including aPoint
companion class.我们定义一个案例类
Point
。 编译器在幕后为我们创建了一堆样板,包括Point
伴随类。We define a trait,
Drawable
, with…我们用…定义一个特征
Drawable
。…an implicitly abstract method,
draw()
. Since the method is abstract, we specify the return type asUnit
(which means nothing will be returned)…一个隐式抽象的方法
draw()
。 由于该方法是抽象方法,因此我们将返回类型指定为Unit
(这意味着将不返回任何内容)We define an abstract class,
Shape
, which declares aval
,center
, and an implicitly abstract method,area()
我们定义一个抽象类
Shape
,它声明一个val
,center
和一个隐式抽象方法area()
We define a case class,
Circle
, which extends theShape
class and theDrawable
trait.我们定义了一个Case类
Circle
,它扩展了Shape
类和Drawable
特征。We use the
override
keyword to implement the abstractdraw()
method of the parentShape
class. Here, we choose to enclose the method’s body in curly brackets.我们使用
override
关键字来实现父Shape
类的抽象draw()
方法。 在这里,我们选择将方法的主体括在大括号中。We then define two more methods: the
area()
method overridden from the parentShape
class, and a newgrowBy()
method that returns a newCircle
instance. In both bases, we omit the curly brackets from the method body.然后,我们定义另外两个方法:从父
Shape
类重写的area()
方法和一个返回新Circle
实例的新growBy()
方法。 在这两个基础中,我们都省略了方法主体中的花括号。We create a
Canvas
object. All of its functions can be thought of as similar to Java static methods.我们创建一个
Canvas
对象。 可以认为它的所有功能都类似于Java 静态方法。We can define a
main
method, just like in Java. Being in an object, this method is static and could be invoked likeCanvas.main(Array())
.就像在Java中一样,我们可以定义一个
main
方法。 在对象中,此方法是静态的,可以像Canvas. main ( Array ())
一样调用Canvas. main ( Array ())
Canvas. main ( Array ())
。We create a
Circle
instance using the syntactic sugar which invokes theCircle
companion object’sapply()
method. Recall that theCircle
companion object, and itsapply()
method, are generated by the compiler becauseCircle
is a case class.我们使用语法糖创建一个
Circle
实例,该语法糖调用Circle
伴侣对象的apply()
方法。 回想一下,Circle
伴随对象及其apply()
方法是由编译器生成的,因为Circle
是一个case类。We create a
List
ofDrawable
s, using square brackets to indicate theList
’s generic type.我们创建一个
Drawable
的List
,使用方括号指示List
的泛型类型。We use the
List
’sforeach
method to iterate over itsDrawable
s, calling each’sdraw()
method. For brevity, we use the underscore character to represent the currentDrawable
.我们使用
List
的foreach
方法遍历其Drawable
,调用它们的draw()
方法。 为简洁起见,我们使用下划线字符表示当前的Drawable
。We then use the
List
’smap
method to convert eachDrawable
into aString
value.然后,我们使用
List
的map
方法将每个Drawable
转换为String
值。We use pattern matching to determine the specific type of
Drawable
we’re iterating over. On this line, we’re looking for aCircle
with any center value (represented by the underscore character), and are capturing the radius value with the r variable. We then use String interpolation to create aString
containing that r value. We do something similar when matching aSquare
on the line below.我们使用模式匹配来确定要迭代的
Drawable
的特定类型。 在这条线上,我们正在寻找一个具有任何中心值的Circle
(用下划线字符表示),并使用r变量捕获半径值。 然后,我们使用字符串插值创建包含该r值的String
。 当匹配下面一行的Square
时,我们会做类似的事情。We capture cases where we match a
Drawable
that is neither aCircle
or aSquare
by using the underscore character.通过使用下划线字符,我们捕获了匹配不为
Circle
或Square
的Drawable
情况。We then call
foreach
on the resultingList
ofStrings
, and reference theprintln()
function, which is implicitly invoked with theString
being currently iterated over.然后,我们在生成的
Strings
List
上调用foreach
,并引用println()
函数,该函数通过当前正在迭代的String
隐式调用。
翻译自: https://medium.com/the-innovation/scala-syntax-for-java-developers-69734ce17cdf
scala语法