Scala编程语言入门
Scala进阶
函数式编程
-
我们将来使用Spark/Flink的大量业务代码都会使用到函数式编程。
-
下面的这些操作是学习的重点,先来感受下如何进行函数式编程以及它的强大。
1. 遍历 foreach
- 方法描述
foreach(f: (A) ⇒ Unit): Unit
- 方法说明
foreach | API | 说明 |
---|---|---|
参数 | f: (A) ⇒ Unit | 接收一个函数对象作为参数 函数的输入参数为集合的元素 返回值为空 |
返回值 | Unit | 空 |
- 示例:
scala> val list = List(1, 2, 3, 4)
list: List[Int] = List(1, 2, 3, 4)
// 定义一个匿名函数传入到foreach方法中
scala> list.foreach((x: Int) => println(x))
// 匿名函数的输入参数类型可以省略,由编译器自动推断
scala> list.foreach(x => println(x))
// 当函数参数,只在函数体中出现一次,而且函数体没有嵌套调用时,可以使用下划线来简化函数定义
scala> list.foreach(println(_))
// 最简写,直接给定println
scala> list.foreach(println)
1
2
3
4
// 很神奇的语法,别害怕,盘它就可以了,后期通过scala语言开发spark、Flink程序非常简洁方便
2. 映射 map
-
集合的映射操作是将来在编写Spark/Flink用得最多的操作,是我们必须要掌握。
-
方法描述
def map[B](f: (A) ⇒ B): TraversableOnce[B]
- 方法说明
map方法 | API | 说明 |
---|---|---|
泛型 | [B] | 指定map方法最终返回的集合泛型 |
参数 | f: (A) ⇒ B | 传入一个函数对象作为参数 该函数接收一个类型A(要转换的集合的元素类型) 返回值为类型B |
返回值 | TraversableOnce[B] | B类型的集合 |
- 示例:
// 定义一个 list 集合,实现把内部每一个元素乘以10,生成一个新的 list 集合
scala> val list = List(1, 2, 3, 4)
list: List[Int] = List(1, 2, 3, 4)
// 定义一个匿名函数
scala> list.map((x: Int) => x * 10)
// 省略匿名函数参数类型
scala> list.map(x => x * 10)
// 最简写法:用下划线
scala> list.map(_ * 10)
res1: List[Int] = List(10, 20, 30, 40)
3. 扁平化映射 flatMap
-
映射扁平化也是将来用得非常多的操作,也是必须要掌握的。
-
方法描述
def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): TraversableOnce[B]
- 方法说明
flatmap方法 | API | 说明 |
---|---|---|
泛型 | [B] | 最终要转换的集合元素类型 |
参数 | f: (A) ⇒ GenTraversableOnce[B] | 传入一个函数对象作为参数 函数的参数是集合的元素 函数的返回值是一个集合 |
返回值 | TraversableOnce[B] | B类型的集合 |
- 示例:
// 定义一个List集合,每一个元素中就是一行数据,有很多个单词
scala> val list = List("Hadoop HDFS MapReduce Yarn", "Spark Flink")
list: List[String] = List(Hadoop HDFS MapReduce Yarn, Spark Flink)
// 使用flatMap进行偏平化处理,获取得到所有的单词
scala> list.flatMap(x => x.split(" "))
// 简写
scala> list.flatMap(_.split(" "))
res1: List[String] = List(Hadoop, HDFS, MapReduce, Yarn, Spark, Flink)
// flatMap该方法其本质是先进行了map, 然后又调用了flatten
scala> list.map(_.split(" ")).flatten
res3: List[String] = List(Hadoop, HDFS, MapReduce, Yarn, Spark, Flink)
4. 过滤 filter
-
过滤符合一定条件的元素
-
方法描述
def filter(p: (A) ⇒ Boolean): TraversableOnce[A]
- 方法说明
filter方法 | API | 说明 |
---|---|---|
参数 | p: (A) ⇒ Boolean | 传入一个函数对象作为参数 函数的参数是集合中的元素 此函数返回布尔类型,满足条件返回true, 不满足返回false |
返回值 | TraversableOnce[A] | 列表 |
- 示例:
// 定义一个 list 集合
scala> val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 过滤出集合中大于 5 的元素
scala> list.filter(x => x > 5)
res4: List[Int] = List(6, 7, 8, 9, 10)
// 把集合中大于5的元素取出来乘以10生成一个新的list集合
scala> list.filter(_ > 5).map(_ * 10)
res5: List[Int] = List(60, 70, 80, 90, 100)
// 通过这个案例,应该是可以感受到scala比java的强大了...
5. 排序 sort
- 在scala集合中,可以使用以下几种方式来进行排序
- sorted默认排序
- sortBy指定字段排序
- sortWith自定义排序
- sorted默认排序
// 定义一个 list 集合
scala> val list = List(5, 1, 2, 4, 3)
list: List[Int] = List(5, 1, 2, 4, 3)
// 默认是升序
scala> list.sorted
res6: List[Int] = List(1, 2, 3, 4, 5)
-
sortBy指定字段排序:根据传入的函数转换后,再进行排序
-
方法描述
def sortBy[B](f: (A) ⇒ B): List[A]
- 方法说明
sortBy方法 | API | 说明 |
---|---|---|
泛型 | [B] | 按照什么类型来进行排序 |
参数 | f: (A) ⇒ B | 传入函数对象作为参数 函数接收一个集合类型的元素为参数 返回B类型的元素进行排序 |
返回值 | List[A] | 返回排序后的列表 |
- 示例:
// 定义一个 list 集合
scala> val list = List("1 hadoop", "2 spark", "3 flink")
list: List[String] = List(1 hadoop, 2 spark, 3 flink)
// 按照单词的首字母进行排序
scala> list.sortBy(x => x.split(" ")(1))
res7: List[String] = List(3 flink, 1 hadoop, 2 spark)
-
sortWith自定义排序:根据一个函数来进行自定义排序
-
方法描述
def sortWith(lt: (A, A) ⇒ Boolean): List[A]
- 方法说明
sortWith方法 | API | 说明 |
---|---|---|
参数 | lt: (A, A) ⇒ Boolean | 传入一个比较大小的函数对象作为参数 函数接收两个集合类型的元素作为参数 返回两个元素大小,小于返回true,大于返回false |
返回值 | List[A] | 返回排序后的列表 |
- 示例:
scala> val list = List(2, 3, 1, 6, 4, 5)
list: List[Int] = List(2, 3, 1, 6, 4, 5)
// 降序
scala> list.sortWith((x, y) => x > y)
res0: List[Int] = List(6, 5, 4, 3, 2, 1)
// 简写
scala> list.sortWith(_ > _)
res1: List[Int] = List(6, 5, 4, 3, 2, 1)
// 升序
scala> list.sortWith(_ < _)
res2: List[Int] = List(1, 2, 3, 4, 5, 6)
6. 分组 groupBy
-
我们如果要将数据按照分组来进行统计分析,就需要使用到分组方法,groupBy表示按照函数将列表分成不同的组。
-
方法描述
def groupBy[K](f: (A) ⇒ K): Map[K, List[A]]
- 方法说明
groupBy方法 | API | 说明 |
---|---|---|
泛型 | [K] | 分组字段的类型 |
参数 | f: (A) ⇒ K | 传入一个函数对象作为参数 函数接收集合元素作为参数 返回一个K类型的key,这个key会用来进行分组,相同的key放在一组中 |
返回值 | Map[K, List[A]] | 返回一个映射,K为分组字段,List为这个分组字段对应的一组数据 |
- 示例:
scala> val a = List("zhangsan" -> "M", "lisi" -> "F", "wangwu" -> "M")
a: List[(String, String)] = List((zhangsan,M), (lisi,F), (wangwu,M))
// 按照性别分组
scala> a.groupBy((kv: (String, String)) => {kv._2})
res0: scala.collection.immutable.Map[String,List[(String, String)]] = Map(M -> List((zhangsan,M), (wangwu,M)), F -> List((lisi,F)))
// 简写
scala> a.groupBy(_._2)
res1: scala.collection.immutable.Map[String,List[(String, String)]] = Map(M -> List((zhangsan,M), (wangwu,M)), F -> List((lisi,F)))
// 将分组后的映射转换为性别/人数元组列表
scala> res1.map(x => x._1 -> x._2.size)
res2: scala.collection.immutable.Map[String,Int] = Map(M -> 2, F -> 1)
7. 聚合 reduce
-
reduce表示将列表,传入一个函数进行聚合计算
-
方法描述
def reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1
- 方法说明
reduce方法 | API | 说明 |
---|---|---|
泛型 | [A1 >: A] | (下界)A1必须是集合元素类型的子类 |
参数 | op: (A1, A1) ⇒ A1 | 传入函数对象,用来不断进行聚合操作 第一个A1类型参数为:当前聚合后的变量 第二个A1类型参数为:当前要进行聚合的元素 |
返回值 | A1 | 列表最终聚合为一个元素 |
- 示例:
scala> val a = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> a.reduce((x, y) => x + y)
res3: Int = 55
// 第一个下划线表示第一个参数,就是历史的聚合数据结果;第二个下划线表示第二个参数,就是当前要聚合的数据元素
scala> a.reduce(_ + _)
res4: Int = 55
// 与reduce一样,从左往右计算
scala> a.reduceLeft(_ + _)
res5: Int = 55
// 从右往左计算
scala> a.reduceRight(_ + _)
res6: Int = 55
8. 折叠 fold
-
fold与reduce很像,但是多了一个指定初始值参数
-
方法描述
def fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1
- 方法说明
reduce方法 | API | 说明 |
---|---|---|
泛型 | [A1 >: A] | (下界)A1必须是集合元素类型的子类 |
参数1 | z: A1 | 初始值 |
参数2 | op: (A1, A1) ⇒ A1 | 传入函数对象,用来不断进行折叠操作 第一个A1类型参数为:当前折叠后的变量 第二个A1类型参数为:当前要进行折叠的元素 |
返回值 | A1 | 列表最终折叠为一个元素 |
- 示例:
// 定义一个 list 集合
scala> val a = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 求和
scala> a.sum
res0: Int = 55
// 给定一个初始值,折叠求和
scala> a.fold(0)(_ + _)
res1: Int = 55
//
scala> a.fold(10)(_ + _)
res2: Int = 65
// 从左往右
scala> a.foldLeft(10)(_ + _)
res3: Int = 65
// 从右往左
scala> a.foldRight(10)(_ + _)
res4: Int = 65
高阶函数
- 使用函数值作为参数,或者返回值为函数值的“函数”和“方法”,均称之为“高阶函数”。
1. 函数值作为参数
// 定义一个数组
scala> val array = Array(1, 2, 3, 4, 5)
array: Array[Int] = Array(1, 2, 3, 4, 5)
// 定义一个函数
scala> val func = (x: Int) => x * 10
func: Int => Int = <function1>
// 函数作为参数传递到方法中
scala> array.map(func)
res5: Array[Int] = Array(10, 20, 30, 40, 50)
2. 匿名函数
// 定义一个没有名称的函数----匿名函数
array.map(x => x * 10)
res6: Array[Int] = Array(10, 20, 30, 40, 50)
3. 柯里化
- 方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。
scala> def getAddress(a: String): (String, String) => String = {
| (b: String, c: String) => a + "-" + b + "-" + c
| }
getAddress: (a: String)(String, String) => String
scala> val f1 = getAddress("china")
f1: (String, String) => String = <function2>
scala> f1("shenzhen", "nanshan")
res7: String = china-shenzhen-nanshan
// 这里就可以用柯里化去定义方法
scala> def getAddress(a: String)(b: String, c: String): String = {
| a + "-" + b + "-" + c
| }
getAddress: (a: String)(b: String, c: String)String
// 调用
scala> getAddress("china")("shenzhen", "nanshan")
res0: String = china-shenzhen-nanshan
scala> val func1 = getAddress("a") _
func1: (String, String) => String = <function2>
scala> func1("c", "d")
res1: String = a-c-d
// 之前学习使用的下面这些操作就是使用到了柯里化
List(1,2,3,4).fold(0)(_ + _)
List(1,2,3,4).foldLeft(0)(_ + _)
List(1,2,3,4).foldRight(0)(_ + _)
4. 闭包
- 函数里面引用外面类成员变量叫作闭包
scala> var factor = 1
factor: Int = 1
scala> val f1 = (x: Int) => x * factor
f1: Int => Int = <function1>
scala> f1(2)
res2: Int = 2
scala> factor = 5
factor: Int = 5
scala> f1(2)
res3: Int = 10
// 定义的函数f1,它的返回值是依赖于不在函数作用域的一个变量,后期必须要要获取到这个变量才能执行
// spark和flink程序的开发中大量的使用到函数,函数的返回值依赖的变量可能都需要进行大量的网络传输获取得到。这里就需要这些变量实现序列化进行网络传输。
scala> def multiply(x: Double) = (y: Double) => x * y
multiply: (x: Double)Double => Double
scala> val doubleFunc = multiply(2)
doubleFunc: Double => Double = <function1>
scala> val tripleFunc = multiply(3)
tripleFunc: Double => Double = <function1>
scala> doubleFunc(10)
res4: Double = 20.0
scala> tripleFunc(10)
res5: Double = 30.0
类
- scala是支持面向对象的,也有类和对象的概念。
1. 类的定义
import java.util.Date
class Customer {
// _表示使用默认值进行初始化
// String类型默认值是null,Int类型默认值是0,Boolean类型默认值是false
var name: String = _
var sex: String = _
// val变量不能使用_来进行初始化,因为val是不可变的,所以必须手动指定一个默认值
val registerDate: Date = new Date
def sayHi(msg: String) = {
println(msg)
}
}
object Main {
// main方法必须要放在一个scala的object(单例对象)中才能执行
def main(args: Array[String]): Unit = {
val customer = new Customer
customer.name = "张三"
customer.sex = "男"
println(s"姓名: ${customer.name}, 性别: ${customer.sex}, 注册时间: ${customer.registerDate}")
// 对象调用方法
customer.sayHi("你好!")
}
}
2. 类的构造器
- 主构造器:指在类名的后面跟上一系列参数,例如
class 类名(var/val 参数名: 类型 = 默认值, var/val 参数名: 类型 = 默认值){
// 构造代码块
}
- 辅助构造器:在类中使用this来定义,例如
def this(参数名: 类型, 参数名: 类型) {
...
}
- 示例:
class Student(val name: String, val age: Int) {
val address: String = "shenzhen"
// 定义一个参数的辅助构造器
def this(name: String) {
// 辅助构造器的第一行必须调用主构造器或其他辅助构造器或者super父类的构造器
this(name, 20)
}
def this(age: Int) {
this("xxx", age)
}
}
对象
1. scala 中的 object
- scala中是没有Java中的静态成员的。如果将来我们需要用到static变量、static方法,就要用到scala中的单例对象object
- 定义 object:定义单例对象和定义类很像,就是把class换成object
- 示例:
import java.text.SimpleDateFormat
import java.util.Date
object DateUtils {
// 在object中定义的成员变量,相当于Java中定义一个静态变量
// 定义一个SimpleDateFormat日期时间格式化对象
val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")
// 构造代码
println("构造代码")
// 相当于Java中定义一个静态方法
def format(date: Date) = simpleDateFormat.format(date)
// main是一个静态方法,所以必须要写在object中
def main(args: Array[String]): Unit = {
println {
DateUtils.format(new Date())
}
}
}
- 说明:
- 使用object 单例对象名定义一个单例对象,可以用object作为工具类或者存放常量
- 在单例对象中定义的变量,类似于Java中的static成员变量
- 在单例对象中定义的方法,类似于Java中的static方法
- object单例对象的构造代码可以直接写在花括号中
- 调用单例对象的方法,直接使用单例对象名.方法名,访问单例对象的成员变量也是使用单例对象名.变量名
- 单例对象只能有一个无参的主构造器,不能添加其他参数
2. scala 中的伴生对象
- 在同一个scala文件,有一个class和object具有同样的名字,那么就称这个object是class的伴生对象,class是object的伴生类;伴生类和伴生对象的最大特点是,可以相互访问。
- 示例:
class Dog {
val id = 1
private var name = "Tom"
def printName(): Unit = {
println(Dog.CONSTANT + name)
}
}
object Dog {
// 伴生对象中的私有属性
private val CONSTANT = "汪汪汪 : "
def main(args: Array[String]): Unit = {
val dog = new Dog
// 访问私有的字段name
dog.name = "123"
dog.printName()
}
}
- 说明:
- 伴生类和伴生对象的名字必须是一样的
- 伴生类和伴生对象需要在一个scala源文件中
- 伴生类和伴生对象可以互相访问private的属性
3. scala 中 object 的 apply 方法
- 我们之前使用过这种方式来创建一个Array对象:
val a = Array(1, 2, 3, 4)
,这种写法非常简单,不需要写一个 new,然后敲一个空格,再写类名。如何直接使用类名来创建对象呢? - 查看 scala 源代码
-
答案就是:实现伴生对象Array的apply方法
-
伴生对象的apply方法用来快速地创建一个伴生类的对象。
-
示例:
class Person(var name: String, var age: Int) {
override def toString: String = s"Person($name, $age)"
}
object Person {
// 实现apply方法,返回的是伴生类的对象
def apply(name: String, age: Int): Person = new Person(name, age)
// apply方法支持重载
def apply(name: String): Person = new Person(name, 20)
def apply(age: Int): Person = new Person("xx", age)
def apply(): Person = new Person("xxx", 30)
}
object Main {
def main(args: Array[String]): Unit = {
val p1 = Person("张三", 23)
val p2 = Person("李四")
val p3 = Person(100)
val p4 = Person()
println(p1)
println(p2)
println(p3)
println(p4)
}
}
- 说明:当遇到类名(参数1, 参数2…)会自动调用伴生对象相应的apply方法,在apply方法中来创建对象
- 定义apply时,如果参数列表是空,也不能省略括号(),否则引用的是伴生对象
4. scala 中 object 的 main 方法
- scala和Java一样,如果要运行一个程序,必须有一个main方法,而在Java中main方法是静态的,而在scala中没有静态方法。
- 在scala中,这个main方法必须放在一个object中
object Main{
def main(args: Array[String]) = {
println("hello, scala")
}
}
- 也可以继承自App Trait(特质),然后将需要编写在main方法中的代码,写在object的构造方法体内。其本质是调用了Trait这个特质中的main方法。
object Main extends App {
println("hello, scala")
}
继承
1. extends
- scala和Java一样,使用extends关键字来实现继承。可以在子类中定义父类中没有的字段和方法,或者重写父类的方法。
- 示例一:实现简单继承
class Person1 {
var name = "super"
def getName = this.name
}
class Student1 extends Person1
object Main1 {
def main(args: Array[String]): Unit = {
val p1 = new Person1()
val p2 = new Student1()
p2.name = "张三"
println(p1.getName)
println(p2.getName)
}
}
- 示例二:单例对象实现继承
class Person2 {
var name = "super"
def getName = this.name
}
object Student2 extends Person2
object Main2 {
def main(args: Array[String]): Unit = {
println(Student2.getName)
}
}
2. override 和 super
- 如果子类要覆盖父类中的一个非抽象方法,必须要使用override关键字
- 可以使用override关键字来重写一个val字段
- 可以使用super关键字来访问父类的成员
class Person3 {
val name = "super"
def getName = name
}
class Student3 extends Person3 {
// 重写 val 字段
override val name: String = "child"
// 重写 getName 方法
override def getName: String = "hello, " + super.getName
}
object Main3 {
def main(args: Array[String]): Unit = {
println(new Student3().getName)
}
}
3. isInstanceOf 和 asInstanceOf
-
我们经常要在代码中进行类型的判断和类型的转换。在Java中,我们可以使用instanceof关键字、以及(类型)object来实现,在scala中如何实现呢?
-
scala中对象提供isInstanceOf和asInstanceOf方法。
- isInstanceOf判断对象是否为指定类的对象
- asInstanceOf将对象转换为指定类型
Java | Scala | |
---|---|---|
判断对象是否是C类型 | obj instanceof C | obj.isInstanceof[C] |
将对象强转成C类型 | (C ) obj | obj.asInstanceof[C] |
获取类型为T的class对象 | C.class | classOf[C] |
- 示例:
class Person4
class Student4 extends Person4
object Main4 {
def main(args: Array[String]): Unit = {
val s1: Person4 = new Student4
// 判断 s1 是否为 Student4 类型
if (s1.isInstanceOf[Student4]) {
// 将s1转换为Student4类型
val s2 = s1.asInstanceOf[Student4]
println(s2)
}
}
}
4. getClass 和 classOf
-
isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 。
- 对象.getClass可以精确获取对象的类型
- classOf[x]可以精确获取类型
- 使用==操作符就可以直接比较
-
示例:
class Person5
class Student5 extends Person5
object Main5 {
def main(args: Array[String]): Unit = {
val p: Person5 = new Student5
println(p.isInstanceOf[Person5]) // true
println(p.getClass == classOf[Person5]) // false
println(p.getClass == classOf[Student5]) // true
}
}
5. 访问修饰符
-
Java中的访问控制,同样适用于scala,可以在成员前面添加private/protected关键字来控制成员的可见性。但在scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的。
-
private[this]修饰符:被修饰的成员只能在当前类中被访问,或者可以理解为:
只能通过this.来访问
(在当前类中访问成员会自动添加this.)。
class Person6 {
private[this] var name = "super"
def getName = this.name // 正确
def sayHello(p: Person6): Unit = {
println("Hello " + p.name) // 报错,无法访问
}
}
object Main6 {
def showName(p: Person6) = println(p.name) // 报错,无法访问
}
- protected[this]修饰符:被修饰的成员只能在当前类和当前类的子类中被访问。也可以理解为:当前类通过**this.访问或者子类通过this.**访问
class Person7 {
protected[this] var name = "super"
def getName = {
this.name // 正确
}
def sayHello(p: Person7): Unit = {
println(p.name) // 编译错误
}
}
object Person7 {
def sayHello(p: Person7) = {
println(p.name) // 编译错误
}
}
class Student7 extends Person7 {
def showName = {
println(name) // 正确
}
def sayHello(p: Person7): Unit = {
println(p.name) // 编译错误
}
}
6. 调用父类的constructor
-
实例化子类对象,必须要调用父类的构造器,在scala中,只能在子类的
主构造器
中调用父类的构造器 -
示例:
class Person8(var name: String) {
println("name: " + name)
}
// 直接在子类的类名后面调用父类构造器
class Student8(name: String, var clazz: String) extends Person8(name)
object Main8 {
def main(args: Array[String]): Unit = {
val s1 = new Student8("zhangsan", "32")
println(s"${s1.name} - ${s1.clazz}")
}
}
7. 抽象类
- 如果类的某个成员在当前类中的定义是不包含完整的,它就是一个抽象类
- 不完整定义有两种情况:
- 方法没有方法体
- 变量没有初始化
- 没有方法体的方法称为抽象方法,没有初始化的变量称为抽象字段。定义抽象类和Java一样,在类前面加上abstract关键字就可以了
- 示例:
abstract class Person9(val name: String) {
// 抽象方法
def sayHello: String
def sayBye: String
// 抽象字段
def address: String
}
class Student9(name: String) extends Person9(name) {
// 重写抽象方法或字段,def前不必加override关键字
def sayHello: String = "Hello, " + name
def sayBye: String = "Bye, " + name
// 重写抽象字段
override def address: String = "shenzhen"
}
object Main9 {
def main(args: Array[String]): Unit = {
val s = new Student9("Tom")
println(s.sayHello)
println(s.sayBye)
println(s.address)
}
}
8. 匿名内部类
- 匿名内部类是没有名称的子类,直接用来创建实例对象。Spark的源代码中有大量使用到匿名内部类。
- 示例:
abstract class Person10 {
def sayHello: Unit
}
object Main10 {
def main(args: Array[String]): Unit = {
// 直接用new来创建一个匿名内部类对象
val p1 = new Person10 {
override def sayHello: Unit = println("这是一个匿名内部类")
}
p1.sayHello
}
}