Scala编程语言入门(2)

Scala编程语言入门

Scala进阶

函数式编程

  • 我们将来使用Spark/Flink的大量业务代码都会使用到函数式编程。

  • 下面的这些操作是学习的重点,先来感受下如何进行函数式编程以及它的强大。

1. 遍历 foreach

  • 方法描述
foreach(f: (A)Unit): Unit
  • 方法说明
foreachAPI说明
参数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必须是集合元素类型的子类
参数1z: A1初始值
参数2op: (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中对象提供isInstanceOfasInstanceOf方法。

    • isInstanceOf判断对象是否为指定类的对象
    • asInstanceOf将对象转换为指定类型
JavaScala
判断对象是否是C类型obj instanceof Cobj.isInstanceof[C]
将对象强转成C类型(C ) objobj.asInstanceof[C]
获取类型为T的class对象C.classclassOf[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
  }
}
  • 0
    点赞
  • 4
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论

打赏作者

斗志昂-杨

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值