0. 前言 要想 30 分钟速成,必须要有面向对象的经验,不然可能得花一个小时来适应。这个教程将涵盖以下内容:
变量(Variables)
函数(Functions)
类(Classes)
继承(Inheritance)
方法(Methods)
对象(Objects)
伴生对象(Companion Objects)
特质(Traits)
每个部分都会包含简要的解释和示例代码,帮助你快速掌握 Scala 的基本语法和概念。
1. 变量(Variables) 在 Scala 中,变量分为两种类型:可变变量 和不可变变量 。
1.1 不可变变量 (val
) 使用 val
声明的变量是不可变 的,一旦赋值后不能再改变。
1 2 3 4 5 val name: String = "Alice" println(name)
1.2 可变变量 (var
) 使用 var
声明的变量是可变 的,可以在之后重新赋值。
1 2 3 4 5 var age: Int = 25 println(age) age = 30 println(age)
1.3 类型推断 Scala 具有强大的类型推断功能,可以根据赋值自动推断变量类型,无需显式声明。
1 2 3 4 5 val greeting = "Hello, World!" val number = 42 println(greeting) println(number)
2. 函数(Functions) 函数是 Scala 中的基本构建块,可以定义在类内部或外部。Scala 支持高阶函数 ,即可以将函数作为参数传递或返回。
2.1 定义函数 使用 def
关键字定义函数。
1 2 3 4 5 6 def add (a: Int , b: Int ): Int = { a + b }val result = add(3 , 5 ) println(result)
2.2 函数参数和返回类型 Scala 函数的参数和返回类型需要明确声明,但如果函数体非常简单,可以省略花括号。
1 2 3 def multiply (a: Int , b: Int ): Int = a * b println(multiply(4 , 6 ))
2.3 匿名函数(Lambda 表达式) Scala 支持匿名函数,可以在需要函数作为参数的地方使用。
1 2 val square = (x: Int ) => x * x println(square(5 ))
2.4 高阶函数 高阶函数是接受函数作为参数或返回函数的函数。
1 2 3 4 5 6 7 8 9 def applyFunction (f: Int => Int , value: Int ): Int = { f(value) }val increment = (x: Int ) => x + 1 println(applyFunction(increment, 10 )) println(applyFunction(x => x * 2 , 7 ))
3. 类(Classes) 类是面向对象编程的核心,用于定义对象的属性和行为。
3.1 定义类 1 2 3 4 5 6 7 8 class Person (val name: String , var age: Int ) { def greet (): Unit = { println(s"Hello, my name is $name and I am $age years old." ) } }val alice = new Person ("Alice" , 30 ) alice.greet()
3.2 构造器 Scala 的类可以有主构造器和辅助构造器。
主构造器 在类名后面的参数列表即为主构造器。
1 2 3 4 5 6 class Rectangle (val width: Double , val height: Double ) { def area (): Double = width * height }val rect = new Rectangle (5.0 , 3.0 ) println(rect.area())
辅助构造器 使用 def this(...)
定义辅助构造器。
1 2 3 4 5 6 7 8 9 class Point (val x: Int , val y: Int ) { def this (x: Int ) = this (x, 0 ) }val p1 = new Point (5 , 10 ) println(s"Point1: (${p1.x} , ${p1.y} )" ) val p2 = new Point (7 ) println(s"Point2: (${p2.x} , ${p2.y} )" )
4. 继承(Inheritance) Scala 支持类的继承,允许一个类继承自另一个类,获取其属性和方法。
4.1 基类和子类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Animal { def speak (): Unit = { println("Animal makes a sound." ) } }class Dog extends Animal { override def speak (): Unit = { println("Dog barks." ) } }val genericAnimal = new Animal () genericAnimal.speak() val dog = new Dog () dog.speak()
4.2 构造器和继承 子类在构造时需要调用基类的构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Vehicle (val brand: String , val model: String ) { def info (): Unit = { println(s"Vehicle: $brand $model " ) } }class Car (brand: String , model: String , val doors: Int ) extends Vehicle (brand, model ) { override def info (): Unit = { super .info() println(s"Number of doors: $doors " ) } }val car = new Car ("Toyota" , "Corolla" , 4 ) car.info()
5. 方法(Methods) 方法是在类或对象中定义的函数,用于描述对象的行为。
5.1 定义方法 1 2 3 4 5 6 7 8 class Calculator { def add (a: Int , b: Int ): Int = a + b def subtract (a: Int , b: Int ): Int = a - b }val calc = new Calculator () println(calc.add(10 , 5 )) println(calc.subtract(10 , 5 ))
5.2 方法的访问修饰符 Scala 的方法默认是 public
的,可以使用 private
和 protected
修饰符限制访问权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 class SecretAgent { private def secretMethod (): Unit = { println("This is a secret method." ) } def revealSecret (): Unit = { secretMethod() } }val agent = new SecretAgent () agent.revealSecret()
5.3 方法重载 在同一个类中,可以定义多个同名但参数不同的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Printer { def print (message: String ): Unit = { println(s"String: $message " ) } def print (number: Int ): Unit = { println(s"Int: $number " ) } }val printer = new Printer () printer.print("Hello" ) printer.print(100 )
6. 对象(Objects) 在 Scala 中,object
用于定义单例对象,即在整个程序中只有一个实例。对象通常用于存放工具方法或作为应用程序的入口点。
6.1 定义对象 1 2 3 4 5 6 7 object MathUtils { def square (x: Int ): Int = x * x def cube (x: Int ): Int = x * x * x } println(MathUtils .square(4 )) println(MathUtils .cube(3 ))
6.2 伴生对象(Companion Objects) 伴生对象是与类同名的对象,且必须在同一个源文件中定义。它们可以相互访问 private
成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person (private val name: String ) { private def greetPrivate (): Unit = { println(s"Hello, my name is $name ." ) } def greet (): Unit = { greetPrivate() } }object Person { def createPerson (name: String ): Person = { val person = new Person (name) person.greet() person } }val john = Person .createPerson("John" )
7. 伴生对象(Companion Objects) 伴生对象与伴生类共享名称,并且可以访问彼此的 private
成员。常用于定义工厂方法和静态成员。
7.1 定义伴生对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Rectangle (val width: Double , val height: Double ) { def area (): Double = width * height }object Rectangle { def apply (width: Double , height: Double ): Rectangle = new Rectangle (width, height) def unitSquare (): Rectangle = new Rectangle (1.0 , 1.0 ) }val rect1 = Rectangle (5.0 , 3.0 ) println(rect1.area()) val rect2 = Rectangle .unitSquare() println(rect2.area())
7.2 使用 apply
方法 apply
方法允许你像调用函数一样创建类的实例,而无需使用 new
关键字。
1 2 3 4 5 6 7 8 9 10 class Circle (val radius: Double ) { def area (): Double = math.Pi * radius * radius }object Circle { def apply (radius: Double ): Circle = new Circle (radius) }val circle = Circle (2.5 ) println(circle.area())
8. 特质(Traits) 特质类似于 Java 中的接口,可以包含抽象方法和具体方法。类可以通过 extends
和 with
关键字混入多个特质,实现多重继承的效果。
8.1 定义和使用特质 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 trait Greeter { def greet (name: String ): Unit = { println(s"Hello, $name !" ) } }trait Logger { def log (message: String ): Unit = { println(s"LOG: $message " ) } }class FriendlyPerson extends Greeter with Logger { def sayHello (name: String ): Unit = { greet(name) log(s"Said hello to $name ." ) } }val person = new FriendlyPerson () person.sayHello("Alice" )
8.2 抽象方法 特质可以包含抽象方法,子类需要实现这些方法。
1 2 3 4 5 6 7 8 9 10 11 12 trait Animal { def makeSound (): Unit }class Cat extends Animal { def makeSound (): Unit = { println("Meow" ) } }val kitty = new Cat () kitty.makeSound()
完整示例 下面是一个综合示例,展示了变量、函数、类、继承、方法、对象、伴生对象和特质的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 trait Logger { def log (message: String ): Unit = { println(s"LOG: $message " ) } }class Vehicle (val brand: String , val model: String ) { def info (): Unit = { println(s"Vehicle: $brand $model " ) } }class Car (brand: String , model: String , val doors: Int ) extends Vehicle (brand, model ) with Logger { override def info (): Unit = { super .info() println(s"Number of doors: $doors " ) log("Car info displayed." ) } }object Car { def apply (brand: String , model: String , doors: Int ): Car = new Car (brand, model, doors) def createDefaultCar (): Car = Car ("Toyota" , "Corolla" , 4 ) }object TestApp extends App { val myCar = Car ("Honda" , "Civic" , 2 ) myCar.info() val defaultCar = Car .createDefaultCar() defaultCar.info() def calculateArea (width: Double , height: Double ): Double = width * height println(s"Area: ${calculateArea(5.0, 3.0)} " ) }
总结 通过这个入门教程,你应该已经了解了 Scala 的以下基本概念:
变量 :val
(不可变)和 var
(可变)。
函数 :定义、参数、返回类型、匿名函数和高阶函数。
类 :定义类、构造器和实例化。
继承 :类的继承、方法重写和调用基类方法。
方法 :定义方法、访问修饰符和方法重载。
对象 :单例对象的定义和使用。
伴生对象 :与类同名的对象,用于工厂方法和静态成员。
特质 :类似于接口,可以混入多个特质实现多重继承。
一些问题 当然,我会认真回答你的问题,并尽量详细解释每个概念之间的关系和区别。
1. Scala 子类调用父类方法 vs Java 中的 super.func1()
问题 :Scala 的子类可以直接调用父类中的方法,这本质上是不是相当于 Java 中的 super.func1()
?
回答 :
在 Scala 中,子类确实可以直接调用父类的方法,这在功能上类似于 Java 中的 super.func1()
。然而,两者在语法和一些细节上有些不同。
Scala 中的父类方法调用 在 Scala 中,当子类继承自父类时,可以直接调用父类的方法,无需显式使用 super
关键字,除非你需要调用被子类重写的方法。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Animal { def speak (): Unit = { println("Animal makes a sound." ) } }class Dog extends Animal { override def speak (): Unit = { println("Dog barks." ) } def parentSpeak (): Unit = { super .speak() } }object TestApp extends App { val dog = new Dog () dog.speak() dog.parentSpeak() }
Java 中的父类方法调用 在 Java 中,子类调用父类的方法通常使用 super
关键字,特别是在方法被重写的情况下。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Animal { void speak () { System.out.println("Animal makes a sound." ); } }class Dog extends Animal { @Override void speak () { System.out.println("Dog barks." ); } void parentSpeak () { super .speak(); } }public class TestApp { public static void main (String[] args) { Dog dog = new Dog (); dog.speak(); dog.parentSpeak(); } }
总结
相似点 :
两者都允许子类调用父类的方法。
当方法被重写时,都需要使用 super
来调用父类的实现。
不同点 :
在 Scala 中,如果子类没有重写父类的方法,可以直接调用父类的方法,无需 super
。
在 Java 中,无论是否重写,通常推荐使用 super
来明确调用父类的方法。
2. Scala 的 Lambda 表达式 vs C++ 的 Lambda 表达式 问题 :Scala 支持了 Lambda 表达式,也就是匿名函数,这个用法和 C++ 有什么区别?
回答 :
Scala 和 C++ 都支持 Lambda 表达式(匿名函数),但在语法、功能和使用场景上有一些差异。
Scala 的 Lambda 表达式 Scala 的 Lambda 表达式语法简洁,功能强大,广泛应用于集合操作、高阶函数等场景。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def applyFunction (f: Int => Int , value: Int ): Int = { f(value) }def increment (x: Int ): Int = x + 1 println(applyFunction(increment, 10 )) println(applyFunction(x => x * 2 , 7 )) println(applyFunction(_ * 3 , 5 ))
特点 :
类型推断 :Scala 能够自动推断 Lambda 表达式的参数类型。
简洁语法 :支持多种简写形式,如 _ * 3
。
高阶函数支持 :函数可以作为参数传递或返回。
C++ 的 Lambda 表达式 C++ 在 C++11 引入了 Lambda 表达式,功能强大但语法相对复杂,尤其是在捕获列表和返回类型方面。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <functional> int applyFunction (std::function<int (int )> f, int value) { return f (value); }int main () { auto increment = [](int x) -> int { return x + 1 ; }; std::cout << applyFunction (increment, 10 ) << std::endl; std::cout << applyFunction ([](int x) -> int { return x * 2 ; }, 7 ) << std::endl; int factor = 3 ; std::cout << applyFunction ([factor](int x) -> int { return x * factor; }, 5 ) << std::endl; return 0 ; }
特点 :
捕获列表 :可以捕获外部变量,支持值捕获和引用捕获。
显式返回类型 :通常需要显式指定返回类型,尤其在复杂表达式中。
语法复杂度 :相比 Scala,C++ 的 Lambda 语法更为复杂,特别是在捕获和类型推断方面。
主要区别
语法简洁性 :
Scala 的 Lambda 表达式语法更简洁,支持多种简写形式,减少了代码量。
C++ 的 Lambda 语法相对复杂,尤其在处理捕获和返回类型时。
类型推断 :
Scala 的类型推断更强大,能自动推断大部分情况下的类型。
C++ 的类型推断在 Lambda 表达式中需要更多显式信息,尤其在复杂情况下。
捕获机制 :
C++ 支持多种捕获方式(值捕获、引用捕获、按需捕获等),适用于底层内存管理和性能优化。
Scala 的 Lambda 表达式不需要显式捕获列表,变量捕获通过闭包自动处理,更适合函数式编程。
应用场景 :
Scala 的 Lambda 表达式广泛用于函数式编程、高阶函数和集合操作。
C++ 的 Lambda 表达式主要用于回调函数、事件处理和算法中的自定义操作。
3. 高阶函数与匿名函数的联系 问题 :高阶函数中的参数可以是一个匿名函数,比如:
1 2 3 4 5 6 7 8 9 def applyFunction (f: Int => Int , value: Int ): Int = { f(value) }val increment = (x: Int ) => x + 1 println(applyFunction(increment, 10 )) println(applyFunction(x => x * 2 , 7 ))
这两者之间有什么联系?
回答 :
高阶函数(Higher-Order Functions) 和 匿名函数(Lambda 表达式) 在 Scala 中紧密结合,高阶函数是以函数作为参数或返回值的函数,而匿名函数则是没有名称的函数表达式。两者结合使得 Scala 支持强大的函数式编程范式。
高阶函数的定义 高阶函数是指以下两种情况之一的函数:
接受一个或多个函数作为参数 。
返回一个函数作为结果 。
示例 :
1 2 3 4 5 6 7 8 9 def applyFunction (f: Int => Int , value: Int ): Int = { f(value) }def makeIncrementer (): Int => Int = { x => x + 1 }
匿名函数的定义 匿名函数(Lambda 表达式)是没有名称的函数,可以直接在需要函数的地方定义和使用。
示例 :
1 2 3 4 5 val increment = (x: Int ) => x + 1 applyFunction(x => x * 2 , 7 )
联系与结合 高阶函数和匿名函数的结合允许你在调用高阶函数时,灵活地传递自定义的操作逻辑,而无需事先定义命名函数。
示例详解 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def applyFunction (f: Int => Int , value: Int ): Int = { f(value) }def increment (x: Int ): Int = x + 1 println(applyFunction(increment, 10 )) println(applyFunction(x => x * 2 , 7 )) println(applyFunction(_ * 3 , 5 ))
优点 :
代码简洁 :无需为简单的操作定义额外的命名函数。
灵活性 :可以根据需要在调用时动态定义函数逻辑。
可读性 :在某些情况下,使用匿名函数可以使代码更直观。
高阶函数的更多应用 高阶函数不仅可以接受匿名函数作为参数,还可以返回函数,支持更复杂的函数式编程模式。
示例 :
1 2 3 4 5 6 7 8 9 10 def multiplier (factor: Int ): Int => Int = { x => x * factor }val timesTwo = multiplier(2 ) println(timesTwo(5 )) val timesThree = multiplier(3 ) println(timesThree(5 ))
4. Scala 的辅助构造器(Auxiliary Constructors) 问题 :辅助构造器是不是为了实例化默认参数的对象?例如:
1 2 3 4 5 6 7 8 9 class Point (val x: Int , val y: Int ) { def this (x: Int ) = this (x, 0 ) }val p1 = new Point (5 , 10 ) println(s"Point1: (${p1.x} , ${p1.y} )" ) val p2 = new Point (7 ) println(s"Point2: (${p2.x} , ${p2.y} )" )
回答 :
辅助构造器(Auxiliary Constructors) 在 Scala 中用于提供类的多种构造方式,类似于 Java 中的重载构造函数。它们不仅限于实例化带有默认参数的对象,但确实可以用于这种场景。
辅助构造器的作用
提供多种实例化方式 :
封装初始化逻辑 :
可以在辅助构造器中添加额外的初始化步骤,确保对象的一致性。
实现默认参数 :
可以通过辅助构造器提供默认值,实现类似于默认参数的效果。
示例详解 1 2 3 4 5 6 7 8 9 10 class Point (val x: Int , val y: Int ) { def this (x: Int ) = this (x, 0 ) }val p1 = new Point (5 , 10 ) println(s"Point1: (${p1.x} , ${p1.y} )" ) val p2 = new Point (7 ) println(s"Point2: (${p2.x} , ${p2.y} )" )
在这个例子中:
主构造器 :接受两个参数 x
和 y
,用于初始化 Point
对象。
辅助构造器 :接受一个参数 x
,并通过调用主构造器 this(x, 0)
来为 y
提供默认值 0
。
使用辅助构造器的场景
简化对象创建 :
提供更简洁的实例化方式,尤其是当某些参数有常见的默认值时。
增强灵活性 :
允许用户根据不同需求创建对象,而不必提供所有参数。
辅助构造器 vs 默认参数 Scala 还支持默认参数 ,可以在参数列表中为参数提供默认值,从而减少对辅助构造器的需求。
示例 :
1 2 3 4 5 6 7 class Point (val x: Int , val y: Int = 0 )val p1 = new Point (5 , 10 ) println(s"Point1: (${p1.x} , ${p1.y} )" ) val p2 = new Point (7 ) println(s"Point2: (${p2.x} , ${p2.y} )" )
优点 :
简洁性 :无需定义辅助构造器,直接在参数列表中提供默认值。
灵活性 :用户可以选择传递或省略默认参数。
比较 :
辅助构造器 :
适用于更复杂的初始化逻辑。
可以有多个辅助构造器,提供多种实例化方式。
默认参数 :
更适用于简单的默认值需求。
语法更简洁,减少了代码量。
选择使用辅助构造器还是默认参数
使用辅助构造器 :
当对象初始化需要额外的逻辑步骤。
当需要提供多种不同的构造方式,涉及不同数量或类型的参数。
使用默认参数 :
当仅需要为某些参数提供默认值,且初始化过程简单。
推荐在大多数情况下优先使用默认参数,因为它们更简洁和易于维护。
5. Scala 的 object
vs Java 中的静态类 问题 :object 对象和 Java 中的静态类很像,可以在里面定义一些方法、属性,不用再继续实例化,对吗?
回答 :
object
在 Scala 中定义的是单例对象(Singleton Object) ,它与 Java 中的静态类 (static class
)有一定的相似性,但也存在一些关键区别和更强大的功能。
Scala 中的 object
单例模式 :object
定义的实体在整个程序运行期间只有一个实例,无需显式实例化。
伴生对象 :object
可以与同名的 class
形成伴生对象,允许它们互相访问 private
成员。
静态成员替代 :Scala 没有 static
关键字,object
提供了存放静态方法和字段的替代方案。
工厂方法和应用程序入口 :常用于定义工厂方法(如 apply
方法)和作为程序的入口点(继承 App
特质)。
示例 :
1 2 3 4 5 6 7 object MathUtils { def square (x: Int ): Int = x * x def cube (x: Int ): Int = x * x * x } println(MathUtils .square(4 )) println(MathUtils .cube(3 ))
Java 中的静态类
静态嵌套类 :Java 中的静态类通常指的是嵌套类使用 static
修饰的情况,允许在不依赖外部类实例的情况下创建嵌套类的实例。
静态方法和字段 :Java 使用 static
关键字来定义与类相关联而非实例相关联的方法和字段。
单例模式实现 :Java 没有内建的单例模式支持,通常通过私有构造函数和静态方法来实现。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MathUtils { public static int square (int x) { return x * x; } public static int cube (int x) { return x * x * x; } } System.out.println(MathUtils.square(4 )); System.out.println(MathUtils.cube(3 ));
主要区别
语法和概念 :
**Scala object
**:定义一个完整的单例对象,可以包含方法、字段、甚至继承其他类或特质。与伴生类形成紧密的伴生关系。
Java 静态类 :通常指静态嵌套类,用于在类内部定义独立的类。Java 没有直接的单例对象机制,静态方法和字段需要通过 static
关键字定义。
伴生对象 :
Scala 支持伴生对象,使得类和对象可以互相访问私有成员,增强了封装性和灵活性。
Java 不直接支持伴生对象,通常通过访问修饰符和嵌套类来实现类似功能。
单例实现 :
Scala 的 object
直接支持单例模式,无需额外代码。
Java 需要手动实现单例模式,通常通过私有构造函数和静态方法。
继承和多态 :
Scala 的 object
可以继承类和特质,实现多态和接口功能。
Java 的静态类(嵌套类)也可以继承,但由于静态类的性质,它们通常用于组织代码,而不是实现接口或多态。
总结
相似点 :
都可以用来定义与类相关联的静态方法和字段。
都可以在不实例化对象的情况下调用其中的方法和访问字段。
不同点 :
Scala 的 object
提供了内建的单例支持和伴生对象功能,语法更简洁且功能更强大。
Java 需要手动实现单例模式,并通过 static
关键字定义静态方法和字段,语法相对繁琐。
推荐使用方式 :
在 Scala 中,使用 object
来定义工具类、工厂方法、应用程序入口点等,不需要手动管理单例实例。
在 Java 中,使用静态方法和字段来实现工具类,必要时手动实现单例模式。
6. Scala 的 apply
方法及其自动调用 问题 :apply
方法好像是一个默认的方法,比如:
1 2 3 4 5 6 7 8 9 10 11 12 class Rectangle (val width: Double , val height: Double ) { def area (): Double = width * height }object Rectangle { def apply (width: Double , height: Double ): Rectangle = new Rectangle (width, height) def unitSquare (): Rectangle = new Rectangle (1.0 , 1.0 ) }val rect1 = Rectangle (5.0 , 3.0 ) println(rect1.area())
不显式调用它也会自动调用,对吗?
回答 :
是的,你的理解基本正确。在 Scala 中,apply
方法具有特殊的意义,允许你通过更简洁的语法调用它,而无需显式地使用方法名。这使得 object
内的 apply
方法可以像函数一样被调用,实现工厂模式和简化对象创建。
apply
方法的作用
简化对象创建 :通过定义 apply
方法,允许你像调用函数一样创建对象,无需使用 new
关键字。
工厂方法 :可以在 apply
方法中添加额外的逻辑,如参数验证、对象初始化等。
与伴生对象结合 :通常在伴生对象中定义 apply
方法,与类一起使用,提供便捷的实例化方式。
如何自动调用 apply
方法 当你在对象上使用函数调用语法(即在对象名后加括号并传递参数)时,Scala 会自动调用该对象的 apply
方法。
示例详解 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Rectangle (val width: Double , val height: Double ) { def area (): Double = width * height }object Rectangle { def apply (width: Double , height: Double ): Rectangle = new Rectangle (width, height) def unitSquare (): Rectangle = new Rectangle (1.0 , 1.0 ) }object TestApp extends App { val rect1 = Rectangle (5.0 , 3.0 ) println(rect1.area()) val rect2 = Rectangle .apply(4.0 , 2.5 ) println(rect2.area()) val unitRect = Rectangle .unitSquare() println(unitRect.area()) }
解释 :
定义类和伴生对象 :
Rectangle
类定义了矩形的 width
和 height
属性,以及计算面积的方法 area
。
Rectangle
伴生对象定义了 apply
方法,用于创建 Rectangle
实例,以及 unitSquare
方法,用于创建一个单位正方形。
创建实例 :
Rectangle(5.0, 3.0)
:使用函数调用语法,Scala 自动调用 Rectangle.apply(5.0, 3.0)
,创建一个新的 Rectangle
实例。
Rectangle.apply(4.0, 2.5)
:显式调用 apply
方法,效果与上述相同。
Rectangle.unitSquare()
:调用伴生对象中的其他方法,创建一个单位正方形。
优势 :
简洁性 :减少了使用 new
关键字的需要,使代码更简洁易读。
灵活性 :可以在 apply
方法中添加自定义逻辑,如参数验证、对象缓存等。
工厂模式支持 :允许根据不同参数创建不同类型的实例,实现工厂模式。
自动调用的更多示例 示例 1:单参数 apply
方法
1 2 3 4 5 6 7 8 9 10 11 12 class Circle (val radius: Double ) { def area (): Double = math.Pi * radius * radius }object Circle { def apply (radius: Double ): Circle = new Circle (radius) }object TestApp extends App { val circle = Circle (2.5 ) println(circle.area()) }
示例 2:多重 apply
方法
你可以在伴生对象中定义多个 apply
方法,以支持不同的实例化方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person (val name: String , val age: Int ) { def greet (): Unit = { println(s"Hello, my name is $name and I am $age years old." ) } }object Person { def apply (name: String ): Person = new Person (name, 0 ) def apply (name: String , age: Int ): Person = new Person (name, age) }object TestApp extends App { val person1 = Person ("Alice" ) person1.greet() val person2 = Person ("Bob" , 25 ) person2.greet() }
自动调用的工作机制 当你使用 ObjectName(args)
语法时,Scala 会隐式调用伴生对象中的 apply
方法。因此,不需要显式地调用 apply
,而直接使用类似函数调用的语法。
内部机制 :
1 2 val rect1 = Rectangle (5.0 , 3.0 )
背后的逻辑 :
语法糖 :Scala 的语法糖允许你通过 ObjectName(args)
语法自动调用 apply
方法。
编译器转换 :编译器会将这种语法转换为显式的 apply
方法调用。
便利性 :这种机制简化了对象创建过程,使代码更简洁和易读。
综合示例 为了更好地理解这些概念,我们来看一个综合的示例,涵盖了继承、Lambda 表达式、高阶函数、辅助构造器、object
和 apply
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 trait Greeter { def greet (name: String ): Unit = { println(s"Hello, $name !" ) } }trait Logger { def log (message: String ): Unit = { println(s"LOG: $message " ) } }class Animal { def speak (): Unit = { println("Animal makes a sound." ) } }class Dog extends Animal with Greeter with Logger { override def speak (): Unit = { super .speak() greet("Dog" ) log("Dog has spoken." ) } def sayHello (name: String ): Unit = { this .greet(name) this .log(s"Said hello to $name ." ) } }object Dog { def apply (name: String ): Dog = { val dog = new Dog () dog.sayHello(name) dog } }object TestApp extends App { val myDog = Dog ("Buddy" ) myDog.speak() def applyFunction (f: Int => Int , value: Int ): Int = { f(value) } val increment = (x: Int ) => x + 1 println(applyFunction(increment, 10 )) println(applyFunction(x => x * 2 , 7 )) }
解释 :
特质 :
Greeter
:提供 greet
方法,用于打招呼。
Logger
:提供 log
方法,用于记录日志。
类继承与混入特质 :
Animal
:基类,定义了 speak
方法。
Dog
:子类,继承自 Animal
,并混入 Greeter
和 Logger
特质。
重写 speak
方法,调用父类方法并使用 greet
和 log
方法。
定义 sayHello
方法,显式使用 this
调用 greet
和 log
。
伴生对象 :
Dog
对象定义了 apply
方法,用于创建 Dog
实例并调用 sayHello
方法。
应用程序入口 :
TestApp
对象继承自 App
,程序启动时自动执行对象体内的代码。
使用 Dog("Buddy")
语法自动调用 Dog.apply("Buddy")
方法,创建 Dog
实例并执行初始化逻辑。
演示了高阶函数和匿名函数的使用。
运行结果 :
1 2 3 4 5 6 7 Hello, Buddy!LOG : Said hello to Buddy. Animal makes a sound. Hello, Dog!LOG : Dog has spoken.11 14
总结
子类调用父类方法 :
Scala 中可以使用 super
关键字调用父类的方法,类似于 Java 的 super.func1()
。
在没有方法重写的情况下,可以直接调用父类的方法。
Lambda 表达式和匿名函数 :
Scala 的 Lambda 表达式语法更简洁,支持强大的类型推断和高阶函数。
C++ 的 Lambda 表达式功能强大,但语法更复杂,尤其在捕获和类型推断方面。
高阶函数与匿名函数 :
高阶函数可以接受匿名函数作为参数或返回函数,支持函数式编程的核心特性。
Scala 的高阶函数和匿名函数结合使用,使代码更灵活和简洁。
辅助构造器 :
用于提供类的多种实例化方式,不仅限于默认参数。
与默认参数相比,辅助构造器适用于更复杂的初始化逻辑。
object
vs Java 的静态类 :
Scala 的 object
定义单例对象,提供类似于静态方法和字段的功能,但功能更强大,支持伴生对象和工厂方法。
Java 需要手动实现单例模式,并使用 static
关键字定义静态成员,语法相对繁琐。
apply
方法 :
允许通过简洁的语法调用对象的 apply
方法,自动实例化对象。
当使用 ObjectName(args)
语法时,Scala 会自动调用 apply
方法,无需显式调用。