您的位置:首页 > 其它

Scala 优雅入门 (五) 面向对象

2019-03-09 17:02 85 查看
版权声明:All right reserved 未经允许,禁止转载 https://blog.csdn.net/a308601801/article/details/88368238

语法

Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是public)

一个 Scala 源文件可以包含多个类, 每个类默认都是 public

Scala 中声明一个属性,必须显式的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略

如果赋值为 null, 则一定要加类型,因为不加类型, 那么该属性的类型就是 Null 类型.

如果在定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值, 这时要求属性必须给定数据类型

创建对象 val | var 对象名 [:类型]  = new 类型 xxx

如果我们不希望改变对象的引用(即:内存地址), 应该声明为 val 性质的,否则声明为 var, Scala 设计者推荐使用 val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用

当类型和后面 new 对象类型有继承关系即多态时,必须写类型

递归方法的返回值不能进行类型推断, 必须写类型

 

构造器

Scala 中构造器支持重载

Scala 类的构造器包括: 主构造器(只能有一个)

辅助构造器(可以有多个), 编译器通过不同参数(个数或类型)来区分

只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器

[code]class 类名(形参列表) {  //括号内主构造器
// 类体
def this(形参列表) {  // 辅助构造器
}
def this(形参列表) {  //辅助构造器可以有多个...
}
}

//示例1
class Person { // 会生成一个默认的无参构造器

def this(name: String) {
//所有的辅助构造器都会先调用主构造器
//为了底层能够实现对父类的初始化
this
}

def this(name: String, age: Int) {
this
}
}

//示例2
class Person(pName: String, pAge: Int) { //带参主构造器
var name = pName
var age = pAge

def show(): Unit = {
println("name=" + this.name + " age=" + this.age)
}
}

//调用
object ConstractorDemo {
def main(args: Array[String]): Unit = {
val person = new Person("jack", 18) //传入参数
person.show()
}
}
  1. Scala 构造器作用是完成对新对象的初始化,构造器没有返回值
  2. 主构造器的声明直接放置于类名之后
  3. 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略  val dog = new Dog
  4. 多个辅助构造器通过不同参数列表进行区分, 在底层就是构造器重载 辅助构造器无论是直接或间接最终都一定要调用主构造器,执行主构造器的逻辑, 而且调用主构造器语句一定要放在辅助构造器的第一行
  5. 如果想让主构造器变成私有的,可以在 () 之前加上 private,这样用户不能直接通过主构造器来构造对象了  class C private() { }
  6. 辅助构造器的声明不能和主构造器的声明一致

构造器参数

Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。不可读\写  class Monster(mName:String,mAge:Int)

如果参数使用 val 关键字声明,那么Scala会将参数作为类的私有的只读属性使用  class Cat(val cName:String, val cAge:Int)

如果参数使用 var 关键字声明,那么那么Scala会将参数作为类的成员属性使用,并会提供属性对应的 xxx() [类似getter]/xxx_$eq()[类似setter] 方法,即这时的成员属性是私有的,但是可读写。 class Sheep(var sName:String)

@BeanProperty

将 Scala 字段加 @BeanProperty 时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性, 许多 Java 工具(框架)都依赖这个命名习惯, 为了互操作性。也可以在构造器中使用

[code]class Car{
  //默认生成 public name() 和 public name_$eq
  //如果加入了@BeanProperty 注解,就会再生成  public getName() 和  public setName()
  @BeanProperty var name:String = "宝马700"
}

 

 

Scala会自动引入的常用包

  1. java.lang.* 
  2. scala包
  3. Predef包

在scala中一个文件可以同时创建多个包

包也可以像嵌套类那样嵌套使用(包中有包)

作用域原则:可以直接向上访问。即: Scala 中子包中直接访问父包中的内容, 大括号体现作用域

在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可

父包要访问子包的内容时,需要 import 对应的类等

[code]object Test{
def main(args: Array[String]): Unit = {
//使用com.rayfun.scala.Person
import com.rayfun.scala.Person
val person = new Person
}
}

在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理

 

包对象

包可以包含类、对象和特质 trait,但不能包含函数/方法或变量的定义

在底层包对象(package object scala) 会生成两个类 class package / public final class package$

[code]package object scala {
var name = "jack" //变量
def sayHi(): Unit = { //方法
println("package object scala sayHI()")
}
}

package scala {
// 整个包中的类/对象/子包都可以调用包对象中的属性和方法
…
}

每个包都可以有一个包对象。需要在父包中定义它

包对象名称需要和包名一致,一般用来对包的功能补充

 

包可见性

java提供四种访问控制修饰符号控制方法和变量的访问权限(范围):

  1. 公开级别:用public 修饰,对外公开
  2. 受保护级别:用protected修饰,对子类和同一个包中的类公开
  3. 默认级别:没有修饰符号,向同一个包的类公开.
  4. 私有级别:用private修饰,只有类本身可以访问,不对外公开.

scala 设计者将访问的方式分成三大类: (1) 处处可以访问public (2) 子类和伴生对象能访问protected (3) 本类和伴生对象访问 private

包访问权限 (表示属性有了限制。同时包也有了限制), 这点和 Java 不一样,体现出 Scala 包使用的灵活性。 当然,也可以将可见度延展到上层包 private[rayfun] val description="zhangsan" 说明:protected 也可以变化,比如 protected[rayfun], 非常的灵活.

 

包的引入

Scala中,import 语句可以出现在任何地方, 同时他的作用范围就是 {} 块中

在需要时在引入包,缩小 import 包的作用范围,提高效率, 如果使用到 3 次及以上,则可以放在文件前面,否则可以使用就近引入

如果想要导入包中所有的类,Java 通过通配符 *,Scala 中采用下 _

如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器,使用 {} 括起来即可

import scala.collection.mutable.{HashMap,HashSet}

如果引入的多个包中含有相同的类,那么可以将类进行重命名

import java.util.{ HashMap=>JavaHashMap, List}

如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉

[code]import java.util.{ HashMap=>_, _} // 含义为 引入 java.util 包的所有类,但是忽略 HahsMap 类

 

 

继承

修改父类时,对应的子类就会继承相应的方法和属性的修改

子类继承了所有的属性(通过方法),但私有的属性不能直接访问,需要通过公共的方法访问

重写一个非抽象方法必须显式的声明,调用超类的方法使用super关键字

要测试某个对象是否属于某个给定的类,可以用 isInstanceOf 方法。用 asInstanceOf 方法将引用转换为子类的引用。classOf 获取对象的类名 s.getClass.getName(getSampleName)

 

覆写字段

Java 中只有方法的重写,没有属性/字段重写,

Scala 中有,子类改写父类的字段,我们称为覆写/重写字段。覆写字段需使用 override 修饰

  1. def 只能重写另一个 def (即:方法只能重写另一个方法)
  2. val 只能重写另一个 val 属性 或 重写不带参数的 def
  3. var 只能重写另一个抽象的 var 属性

抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类

  1. 一个属性没有初始化,那么这个属性就是抽象属性
  2. 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
  3. 如果是覆写一个父类的抽象属性,那么 override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此 override 可省略]

 

 

抽象类

在 Scala 中,通过 abstract关 键字标记不能被实例化的类。方法不用标记 abstract,只要省掉方法体即可。抽象类可以拥有抽象字段,抽象字段/属性就是没有初始值的字段

[code]//抽象类
abstract class Animal {
var name: String //抽象字段
var age: Int // 抽象字段
var color: String = "black" //普通字段

def cry() //抽象方法

def sayOk(): Unit = { //普通方法
println("ok")
}
}

//快捷键 alt+enter
class Mouse extends Animal {
//override 有两层含义 1. 重写(override 必须保留) 2. 实现 (可以省略)
override var name: String = _
override var age: Int = _
override def cry(): Unit = {
}
}

注意

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法
  3. 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为 abstract
  4. 抽象方法不能有主体,不允许使用 abstract 修饰。
  5. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为 abstract 类。
  6. 抽象方法和抽象属性不能使用 private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
  7. 抽象类中可以有实现的方法.
  8. 子类重写抽象方法不需要 override,也可以写

 

继承层级

  1. Scala 中,所有其他类都是 AnyRef 的子类,类似 Java 的 Object。
  2. AnyVal 和 AnyRef 都扩展自 Any 类。Any 类是根节点/根类型
  3. Any 中定义了 isInstanceOf、asInstanceOf 方法,以及哈希方法等。
  4. Null 类型的唯一实例就是 null 对象。可以将 null 赋值给任何引用,但不能赋值给值类型的变量
  5. Nothing 类型没有实例。它对于泛型结构是有用处的,举例:空列表 Nil 的类型是 List[Nothing],它是 List[T] 的子类型,T可以是任何类 

 

 

伴生对象

类 class 的 object

在伴生对象中定义 apply 方法,可以实现: 类名(参数) 方式来创建对象实例, 而不用 new

在伴生对象中定义 unapply 方法,可以实现:模式匹配的提取器

在伴生对象中定义 applySeq 方法,可以实现:模式匹配的序列提取器(一次提取多个)

 

特质 trait

[code]trait 特质名 {
    trait体
}
  1. trait 命名 一般首字母大写.
  2. Scala 中,java中的接口可以当做特质使用
  3. object T1 extends Serializable     Serializable: 就是 scala 的一个特质
  4. 如果有多个特质或存在父类,那么需要采用 with 关键字连接
  5. 没有父类
          class  类名   extends   特质1   with    特质2   with   特质3 .
  6. 有父类
           class  类名   extends   父类   with  特质1   with   特质2   with 特质3

动态混入

动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能, 解耦接入.

构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明及构建顺序从左到右,方法执行顺序从右到左。

  1. Scala 中特质中如果调用 super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
  2. 如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
[code]//在声明类的时候混入特质, 对该类所有的对象有效, 在混入特质时,该对象还没有创建
//在声明对象的时候使用with混入多个特质, 特质中的方法对该方法有效, 可以调用, 实际是构造匿名子类, 可
//以理解成在混入特质时,对象已经创建了

{
//1.E...
//2. A...
//3. B....
// 4. C....
// 5. D....
// 6. F....
val ff = new FF

//1. E...
//2. K....
//3. A
//4. B
//5. C
//6. D
println("第2种构建顺序...")
val ff2 = new KK with CC with DD
}

trait AA {
println("A...")
}

trait BB extends AA {
println("B....")
}

trait CC extends BB {
println("C....")
}

trait DD extends BB {
println("D....")
}

class EE {
println("E...")
}

class FF extends EE with CC with DD {
println("F....")
}

class KK extends EE {
println("K....")
}
  • 特质可以继承类,以用来拓展该类的一些功能
[code]trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage()) // 方法来自于Exception类
}
}
  • 所有混入该特质的类,会自动成为那个特质所继承的超类的子类
[code]trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage()) // 方法来自于Exception类
}
}//UnhappyException 就是Exception的子类.
class UnhappyException extends LoggedException{
// 已经是Exception的子类了,所以可以重写方法
override def getMessage = "错误消息!"
}
  • 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误

自身类型

自身类型(self-type):主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型

[code]//Logger 就是自身类型特质
trait Logger  {
// 明确告诉编译器,我就是 Exception,如果没有这句话,下面的 getMessage 不能调用
this: Exception =>
def log(): Unit ={
// 既然我就是 Exception, 那么就可以调用其中的方法
println(getMessage)
}
}

//因为 Logger 使用自身类型,要求继承(混入)Logger 必须是 Exception 子类
// class AAA extends Logger [错误]
class BBB extends Exception with Logger //ok

 

嵌套类

  • 类共有五大成员 属性 方法 构造器 代码块 内部类, 内部类最大的特点就是可以直接访问私有属性

 

  • 从定义在外部类的成员位置上来看,

1) 成员内部类(没用static修饰)

2) 和静态内部类(使用static修饰)

 

  • 定义在外部类局部位置上(比如方法内)来看:

分为局部内部类(有类名)

匿名内部类(没有类名)

[code]//创建内部类对象
val innerClass1 = new outer1.ScalaInnerClass

// 访问方式:外部类名.this.属性名
//可以访问方法
ScalaOuterClass.this.sayHi

在内部类中访问外部类的属性和方法两种方法:

  • 方式1
    内部类如果想要访问外部类的属性,可以通过外部类对象访问。
    即:访问方式:外部类名.this.属性名 
  • 方式2
    内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。
    即:访问方式:外部类名别名.属性名   【外部类名.this  等价 外部类名别名】
[code]class ScalaOuterClass {

myouter =>
class ScalaInnerClass2 { //成员内部类

def info() = {
//使用别名的方式来访问外部类的属性和方法,相当于myouter是一个外部类的实例ScalaOuterClass2.this
//这时需要将外部类的属性和方法的定义/声明放在别名后
println("name = " + myouter.name
+ " age =" + myouter.sal)
//可以访问方法
myouter.sayHi
}
}
var name: String = "scott"
private var sal: Double = 1.2

private def sayHi(): Unit = {
println("say hi~~~哈哈~~~")
}
}

类型投影 

在方法声明上,如果使用  外部类#内部类  的方式,表示忽略内部类的对象关系,等同于 Java 中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)

 

如何解决问题呢?

使用类型投影!

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: