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
25
26
27
class Person {
val id="9527"
var age:Int=18
private var name : String = "唐伯虎"
private[this] var pet = "小强"
}

//伴生对象(这个名字和类名相同,叫伴生对象)
object Person{
def main(args: Array[String]): Unit = {
val p=new Person
println(p.id)
println(p.age)0
println(p.name)
}
}

/**
* 在Scala中,类并不用声明为public类型的。
* Scala源文件中可以包含多个类,所有这些类都具有共有可见性。
* 相对于伴生对象,这个是伴生类
*/

//用val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
//用var修饰的变量都既有getter,又有setter
//类私有字段,只能在类的内部使用或者伴生对象中访问
//类私有字段,访问权限更加严格的,该字段在当前类中被访问,在伴生对象里面也不可以访问

二、构造器

  1. 每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起
  2. 主构造器中的参数会成类中的字段
  3. 主构造器会执行类定义的所有语句,但是并不包括方法,只会执行语句
  4. 每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
  5. 类里面每个元素好像都得初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student(val name:String,var age:Int) {
println("执行主构造器")
private var gender="male"
def this(name:String,age:Int,gender:String){
this(name,age)
println("执行辅助构造器")
this.gender=gender
}
}

object Student {
def main(args: Array[String]): Unit = {
val s1=new Student("zhangsan",20)
val s2=new Student("zhangsan",20,"female")
}
}

三、面向对象

1)、单例模式

object 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;

在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的。
object作用:

   1. 存放工具方法和常量
   2. 高效共享单个不可变的实例
   3. 单例模式 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//要被单例的类
class Session{}
object SessionFactory{
//该部分相当于java中的静态块,将要被单例的类new出来
val session=newSession
//在object中的方法相当于java中的静态方法
def getSession():Session={
session
}
}
object SingletonDemo{
def main(args:Array[String]){
//单例对象,不需要new,用【单例对象名称.方法】调用对象中的方法
val session1 = SessionFactory.getSession()
//单例对象,不需要new,用【单例对象名称.变量】调用对象中成员变量
val session2 = SessionFactory.session
}
}

2)、伴生对象

  • 如果有一个class文件,还有一个与class同名的object文件,那么就称这个object是class的伴生对象,class是object的伴生类;
  • 伴生类和伴生对象必须存放在一个.scala文件中;
  • 伴生类和伴生对象的最大特点是,可以相互访问;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//伴生类
class Dog {
private var name = "itcast"
def printName(): Unit ={
//在Dog类中可以访问伴生对象Dog的私有属性
println(Dog.CONSTANT + name )
}
}
//伴生对象
object Dog {
//伴生对象中的私有属性
private val CONSTANT = "汪汪汪 : "
def main(args: Array[String]) {
val p = new Dog
p.name = "123"
p.printName()
}
}

9.2 private[this]访问权限

如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问.

示例

示例说明

  • 定义一个Person类,包含一个name字段, 该字段用private[this]修饰
  • 定义Person类的伴生对象,定义printPerson方法
  • 测试伴生对象是否能访问private[this]权限的成员

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//案例: 测试private[this]的访问权限
object ClassDemo13 {

//1. 定义一个Person类, 属性为: name
class Person(private[this] var name: String)

//2. 定义Person类的伴生对象.
object Person {
//3. 定义一个方法printPerson, 用来打印Person#name属性值.
def printPerson(p:Person) = println(p.name)
}

//定义main函数, 它是程序的主入口
def main(args: Array[String]) = {
//4. 创建Person类型的对象.
val p = new Person("张三")
//5. 调用Person伴生对象中的printPerson方法
Person.printPerson(p)
}
}

注意: 上述代码,会编译报错。但移除掉[this]就可以访问了

3)、apply方法和main方法

一、apply方法

object 中非常重要的一个特殊方法,就是apply方法;
apply方法通常是在伴生对象中实现的,其目的是,通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,…参数n)时apply方法会被调用;
在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用 class(),隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;

在Scala中, 支持创建对象的时候, 免new的动作, 这种写法非常简便,优雅。要想实现免new, 我们就要通过伴生对象的apply方法来实现。

定义apply方法的格式

1
2
3
object 伴生对象名 {
def apply(参数名:参数类型, 参数名:参数类型...) = new 类(...)
}

创建对象

1
val 对象名 = 伴生对象名(参数1, 参数2...)

例如: val p = Person(“张三”, 23)

二、main方法

同Java一样,如果要运行一个程序,必须要编写一个包含 main 方法的类;
在 Scala 中,也必须要有一个 main 方法,作为入口;
Scala 中的 main 方法定义为 def main(args: Array[String]),而且必须定义在 object 中;
除了自己实现 main 方法之外,还可以继承 App Trait,然后,将需要写在 main 方法中运行的代码,直接作为 object 的 constructor 代码即可,而且还可以使用 args 接收传入的参数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//1.在object中定义main方法
object Main_Demo1 {
def main(args: Array[String]) {
if(args.length > 0){
println("Hello, " + args(0))
}else{
println("Hello World!")
}
}
}

//2.使用继承App Trait ,将需要写在 main 方法中运行的代码
// 直接作为 object 的 constructor 代码即可,
// 而且还可以使用 args 接收传入的参数。
object Main_Demo2 extends App{
if(args.length > 0){
println("Hello, " + args(0))
}else{
println("Hello World!")
}
}

四、继承

1)、概念,字段以及方法

  1. Scala 中,让子类继承父类,与 Java 一样,也是使用 extends 关键字;
  2. 继承就代表,子类可继承父类的 field 和 method ,然后子类还可以在自己的内部实现父类没有的,子类特有的 field 和method,使用继承可以有效复用代码;
  3. 子类可以覆盖父类的 field 和 method,但是如果父类用 final 修饰,或者 field 和 method 用 final 修饰,则该类是无法被继承的,或者 field 和 method 是无法被覆盖的。
  4. private 修饰的 field 和 method 不可以被子类继承,只能在类的内部使用;
  5. field 必须要被定义成 val 的形式才能被继承,并且还要使用 override 关键字。 因为 var 修饰的 field 是可变的,在子类中可直接引用被赋值,不需要被继承;即 val 修饰的才允许被继承,var 修饰的只允许被引用。继承就是改变、覆盖的意思。
  6. Java 中的访问控制权限,同样适用于 Scala
1
2
3
4
5
scala里面有属性有四种情况
1. val name = "ss"
2. var name2 = "aa"
3. private val age1 = 12
4. private var age2 = 12

四种属性被继承后的情况如下:

1
2
3
4
5
6
7
1. 第一第二种子类可以直接使用父类定义的变量值
2. val 不可以直接修改变量,需要把父类的覆盖,写法如下:
override
val name="sss"
3. var可以直接修改父类的变量值
name2 = "aaa"
4. private修饰的变量对于子类不可见,子类可以自由再定义同名变量没问题。

父类中定义了一个方法
两种情况,一种没有private修饰,一种用private修饰
1.没有用private修饰的方法,子类可以直接调用
2.没有用private修饰的方法,如果子类想重写父类的方法,需要在新方法上加入override参数,标明重写覆盖
3.如果父类的方法被private参数修饰,则子类无法调用,也无法得知该方法的存在,也可以任意自己再写同名方法。

2)、override 和 super

  1. Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用override关键字;子类可以覆盖父类的val修饰的field,只要在子类中使用override关键字即可。
  2. override关键字可以帮助开发者尽早的发现代码中的错误,比如,override修饰的父类方法的方法名拼写错误。
  3. 此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用super关键字,显示的指出要调用的父类方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student extends Person{
override
val name="ss" // 直接修改父类的val,var参数不可以,需要加上override

override
def getName:String ={
println("调用子类覆盖父类的方法")
this.name
}

def getPersonName:String = {
println("使用super关键字调用父类的方法,但是返回的name值还是子类的name值")
super.getName
}
}

3)、isInstanceOfasInstanceOf

概述

  • isInstanceOf: 判断对象是否为指定类的对象
  • asInstanceOf: 将对象转换为指定类型

格式

1
2
3
4
5
// 判断对象是否为指定类型
val trueOrFalse:Boolean = 对象.isInstanceOf[类型]

// 将对象转换为指定类型
val 变量 = 对象.asInstanceOf[类型]

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
object ClassDemo05 {
//1. 定义一个Person类.
class Person

//2. 定义一个Student类, 继承Person.
class Student extends Person {
def sayHello() = println("Hello, Scala!...")
}

//main方法, 作为程序的主入口
def main(args: Array[String]): Unit = {
//3. 通过多态的形式创建Student类型的对象.
val p: Person = new Student
//s.sayHello() //这样写会报错, 因为多态的弊端是: 父类引用不能直接访问子类的特有成员.
//4. 判断其是否是Student类型的对象, 如果是, 将其转成Student类型的对象.
if (p.isInstanceOf[Student]) {
val s = p.asInstanceOf[Student]
//5. 调用Student#sayHello()方法
s.sayHello()
}
}
}

scala对象类型检查,判断该实例是否是某个类的子类,以及转换成该类:

如果实例化了子类的对象,但是将其赋予了父类类型的变量,在后续的过程中,又需要将父类类型的变量转换为子类类型的变量,应该如何做?

1
2
3
Class A
Class B extends A
B b=New B

首先,需要使用isInstanceOf 判断对象是否为指定类的对象,如果是的话,则可以使用asInstanceOf 将对象转换为指定类型;
注意:p.isInstanceOf[XX]判断 p 是否为 XX 对象的实例;p.asInstanceOf[XX]把 p 转换成 XX 对象的实例
注意:如果没有用 isInstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常;
注意:如果对象是 null,则一定返回 false, asInstanceOf 一定返回 null;
Scala与Java类型检查和转换
注意,子类对所有父类的isInstanceOf[任意父类]返回的都是true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person3 {}
class Student3 extends Person3
object Student3{
def main (args: Array[String] ) {
val p: Person3 = new Student3
var s: Student3 = null
//如果对象是 null,则 isInstanceOf 一定返回 false
println (s.isInstanceOf[Student3])
// 判断 p 是否为 Student3 对象的实例
if (p.isInstanceOf[Student3] ) {
//把 p 转换成 Student3 对象的实例
s = p.asInstanceOf[Student3]
}
println (s.isInstanceOf[Student3] )
}
}

scala和java的获取类对象区别

Scala Java
obj.isInstanceOf[C] obj instanceof C
obj.asInstanceOf[C] (C)obj
classOf[C] C.class

4)、getClass 和 classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出: 对象就是指定类的对象。如果要求精确地判断出对象的类型就是指定的数据类型,那么就只能使用 getClass 和 classOf 来实现.

用法

  • p.getClass可以精确获取对象的类型
  • classOf[类名]可以精确获取数据类型
  • 使用==操作符可以直接比较类型

实际判断该类是哪个类型的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 对象.getClass() 返回类类型
2. classOf[类名称] 返回类类型
3. 把上面返回的结果用等号比较

val person = new Student3
// 得到对象的类对象,class com.shuo.scala.extendsDemo.Student3
println(person.getClass)

// 判断对象是否是Student3 子类是实际类
println( person.isInstanceOf[Student3] ) // 返回 true

// 判断对象是否是 Person3 声明类类型
println( person.isInstanceOf[Person3] ) // 返回 true
println( person.getClass == classOf[Student3] ) // 返回 true
println( person.getClass == classOf[Person3] ) // 返回 false

5)、使用模式匹配进行类型判断

  1. 在实际的开发中,比如 spark 源码中,大量的地方使用了模式匹配的语法进行类型的判断,这种方式更加地简洁明了,而且代码的可维护性和可扩展性也非常高;
  2. 使用模式匹配,功能性上来说,与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断。
  3. 等同于 Java 中的 switch case 语法;
1
2
3
4
5
6
7
val p:Person5=new Student5
p match {
// 匹配是否为Person类或其子类对象
case per:Person5 => println("This is a Person5's Object!"+per)
// 匹配所有剩余情况
case _ =>println("Unknown type!")
}

6)、protected

  1. 跟Java一样,Scala中同样可使用protected关键字来修饰field和method。在子类中,可直接访问父类的field和method,而不需要使用super关键字;
  2. 还可以使用protected[this]关键字,访问权限的保护范围:只允许在当前子类中访问父类的field和method,不允许通过其他子类对象访问父类的field和method。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person6{
protected var name:String="tom"
protected[this] var hobby:String ="game"
protected def sayBye=println("再见...")
}
class Student6 extends Person6{
//父类使用protected 关键字来修饰 field可以直接访问
def sayHello =println("Hello "+name)
//父类使用protected 关键字来修饰method可以直接访问
def sayByeBye=sayBye
def makeFriends(s:Student6)={
println("My hobby is "+hobby+", your hobby is UnKnown")
}
}
object Student6{
def main(args: Array[String]) {
val s:Student6=new Student6
s.sayHello
s.makeFriends(s)
s.sayByeBye
}
}

7)、调用父类构造函数

  1. Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的;
  2. 只能在子类的主constructor中调用父类的constructor。
  3. 如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person7(val name:String,val age:Int){
var score :Double=0.0
var address:String="beijing"
def this(name:String,score:Double)={
//每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码
//主constructor代码
this(name,30)
this.score=score
}
//其他辅助constructor
def this(name:String,address:String)={
this(name,100.0)
this.address=address
}
}
class Student7(name:String,score:Double) extends Person7(name,score)

8)、抽象类

一、抽象类

  1. 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;
  2. 一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的;
  3. 在子类中覆盖抽象类的抽象方法时,可以不加override关键字;
  4. 抽象字段必须指出返回类型,要不然默认无返回值。
1
2
3
4
5
6
7
8
9
abstract class Person9(val name:String) {
//必须指出返回类型,不然默认返回为Unit
def sayHello:String
def sayBye:String
}
class Student9(name:String) extends Person9(name){
def sayHello: String = "Hello,"+name
def sayBye: String ="Bye,"+name
}

二、抽象字段

如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field;

1
2
3
4
5
6
7
abstract class Person10 (val name:String){
//抽象fields
val age:Int
}
class Student10(name: String) extends Person10(name) {
val age: Int = 50
}

三、匿名内部类

匿名内部类是继承了类的匿名的子类对象,它可以直接用来创建实例对象。Spark的源代码中大量使用到匿名内部类。学完这个内容, 对我们查看Spark的底层源码非常有帮助.

1
2
3
new 类名() {
//重写类中所有的抽象内容
}

需求

  1. 创建一个Person抽象类,并添加一个sayHello抽象方法
  2. 定义一个show()方法, 该方法需要传入一个Person类型的对象, 然后调用Person类中的sayHello()方法.
  3. 添加main方法,通过匿名内部类的方式来创建Person类的子类对象, 调用Person类的sayHello()方法.
  4. 调用show()方法.

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object ClassDemo09 {
//1. 定义Person类, 里边有一个抽象方法: sayHello()
abstract class Person{
def sayHello()
}

//2. 定义一个show()方法, 该方法需要传入一个Person类型的对象.
def show(p:Person) = p.sayHello()

//main方法是程序的主入口
def main(args: Array[String]): Unit = {
//3. 通过匿名内部类创建Person的子类对象, 并调用sayHello()方法.
new Person {
override def sayHello(): Unit = println("Hello, Scala, 当对成员方法仅调用一次的时候.")
}.sayHello()

//4. 演示: 匿名内部类可以作为方法的参数进行传递.
val p = new Person {
override def sayHello(): Unit = println("Hello, Scala, 可以作为方法的实际参数进行传递")
}
show(p)
}
}

五、接口

1-trait

  1. Scala中的trait是一种特殊的概念;
  2. 首先先将trait作为接口使用,此时的trait就与Java中的接口 (interface)非常类似;
  3. 在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可;
  4. 类可以使用extends关键字继承trait,注意,这里不是 implement,而是extends ,在Scala中没有 implement 的概念,无论继承类还是trait,统一都是 extends;
  5. 类继承后,必须实现其中的抽象方法,实现时,不需要使用 override 关键字;
  6. Scala不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可。
  7. trait中可以有四种东西,具体的接口,抽象的接口,具体的字段,抽象的字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
trait HelloTrait {
def sayHello(): Unit
}
trait MakeFriendsTrait {
def makeFriends(c: Children): Unit
}
//多重继承 trait
class Children(val name: String) extends HelloTrait with MakeFriendsTrait {
def sayHello() =println("Hello, " + this.name)
def makeFriends(c: Children) = println("Hello, my name is " + this.name + c.name)
}
object Children{
def main(args: Array[String]) {
val c1=new Children("tom")
val c2=new Children("jim")
c1.sayHello()//Hello, tom
c1.makeFriends(c2)//Hello, my name is tom, your name is jim
}
}

2-四种数据

  1. 在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可;

  2. Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具,可以认为trait还包含了类的功能。

  3. Scala 中的 trait 可以定义具体的 field,此时继承 trait 的子类就自动获得了 trait 中定义的 field;
    但是这种获取 field 的方式与继承 class 的是不同的。 如果是继承 class 获取的 field ,实际上还是定义在父类中的;而继承 trait获取的 field,就直接被添加到子类中了。

  4. Scala中的trait也能定义抽象field, 而trait中的具体方法也能基于抽象field编写;
    继承trait的类,则必须覆盖抽象field,提供具体的值;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
trait PersonTrait{
//1.具体字段
var name:String = "啦啦啦";
//2.抽象字段
var age:Int;
//3.具体方法
def getName:String = this.name
//4.抽象方法
def getAge:Int;
}
class StudentTrait(nameTmp:String,ageTmp:Int) extends PersonTrait {
this.name = nameTmp
this.age = ageTmp
override var age: Int = 99
override def getAge: Int = this.age
}
object StudentTrait{
def main(args: Array[String]): Unit = {
val s = new StudentTrait("豆豆",128)
println(s.getName+" "+s.getAge)
}
}

3-指定混入某个trait

类原来继承一个父接口,可以混入某个父接口的子接口,没法混入爷爷接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


trait LoggedTrait {
def log(msg: String) = {println("LoggedTrait类中的方法:"+msg)}
}
trait MyLogger extends LoggedTrait{
override def log(msg: String) = println("MyLogger类中的方法: " + msg)
}
class StudentTrait extends LoggedTrait {
def sayHello = {
log("调用log方法!")
}
}
object StudentTrait{
def main(args: Array[String]) {
val s = new StudentTrait
s.sayHello // 打印的是:LoggedTrait类中的方法

val s2 = new StudentTrait with MyLogger
s2.sayHello // 打印的是:MyLogger类中的方法
}
}

4-调用链

  1. Scala中支持让类继承多个trait后,可依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行 super 关键字即可;
  2. 类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条;
  3. 这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现;
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
trait TraitList {
def logger(name:String) = println("traitList接口中的方法"+name)
}

trait TraitList2 extends TraitList {
override def logger(name:String) = {
println("traitList2接口中的方法"+name)
super.logger(name)
}
}

trait TraitList3 extends TraitList{
override def logger(name:String): Unit = {
println("traitList3接口中的方法"+name)
super.logger(name)
}
}

class TraitListc extends TraitList with TraitList2 with TraitList3 {
def func(name:String): Unit ={
this.logger(name)
}
}
object TraitListc{
def main(args: Array[String]): Unit = {
val listc = new TraitListc
listc.func("aaa")
}
}

traitList3接口中的方法aaa
traitList2接口中的方法aaa
traitList接口中的方法aaa