Scala面向对象编程(高级部分)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1. 静态属性和静态方法

1回顾Java中的静态概念

public static 返回值类型 方法名(参数列表) {方法体} 静态属性… 说明:
Java中静态方法并不是通过对象调用的而是通过类对象调用的所以静态操作并不是面向对象的。

2Scala中静态的概念-伴生对象

①Scala语言是完全面向对象(万物皆对象)的语言所以并没有静态的操作(即在Scala中没有静态的概念)。也没有static关键字, 但是为了能够和Java语言交互(因为Java中有静态概念)就产生了一种特殊的对象来模拟类对象我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
  ②伴生对象的小结

1. Scala中伴生对象采用object关键字声明伴生对象中声明的全是 "静态"内容
    可以通过伴生对象名称直接调用。
2. 伴生对象对应的类称之为伴生类伴生对象的名称应该和伴生类名一致。
3. 伴生对象中的属性和方法都可以通过伴生对象名直接调用访问    
4. 从语法角度来讲所谓的伴生对象其实就是类的静态方法和静态变量的集合
5. 从技术角度来讲scala还是没有生成静态的内容只不过是将伴生对象生成了一个新的类
    实现属性和方法的调用。
6. 从底层原理看伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。
7. 伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误!)
    但是如果没有伴生类也就没有所谓的伴生对象了所以放在哪里就无所谓了。
8. 如果 class A 独立存在那么A就是一个类 如果 object A 独立存在那么A就是一个"
静态"性质的对象[即类对象], 在 object A中声明的属性和方法可以通过 A.属性 和 A.方法
来实现调用

3伴生对象apply方法

在伴生对象中定义apply方法可以实现 类名(参数) 方式来创建对象实例.

class Child private (var name: String, var age: Int, var address: String) {
Child.nums += 1
}
object Child {
var nums: Int = _
// apply方法会通过类(实参列表)调用到
def apply(name: String, age: Int, address: String): Child = {
println("apply()...")
new Child(name, age, address)
}
}

object Test {
def main(args: Array[String]): Unit = {
//加入apply方法后可以将主构造器加上private修饰这样就
//只能通过apply方法调用来生成对象了
//val child1 = new Child("小花1", 10, "昌平")
//println(child1.name)
val array1 = new Array[Int](8)
val array2 = Array(1, 3, 8, 7)

val child2 = Child("小明", 5, "朝阳")// 自动调用apply方法
println(child2.name)
}
}

2. 单例模式

定义保证在整个的软件系统中某个类只能存在一个对象实例。
应用场景

比如Hibernate的SessionFactory它充当数据存储源的代理并负责创建Session对象。SessionFactory并不是轻量级的一般情况下一个项目通常只需要一个SessionFactory就够这是就会使用到单例模式。
Akka [ActorySystem 单例] AKKA

Scala中没有静态的概念所以为了实现Java中单例模式的功能可以直接采用类对象(即伴生对象)方式构建单例对象

方式一懒汉式

object TestSingleTon extends App{
val singleTon = SingleTon.getInstance
val singleTon2 = SingleTon.getInstance
println(singleTon.hashCode() + " " + singleTon2.hashCode())
}
//将SingleTon的构造方法私有化
class SingleTon private() {}

object SingleTon {
private var s:SingleTon = null
def getInstance = {
if(s == null) {
s = new SingleTon
}
s
}
}
object TestSingleTon extends App {
val singleTon = SingleTon.getInstance
val singleTon2 = SingleTon.getInstance
println(singleTon.hashCode() + " ~ " + singleTon2.hashCode())
println(singleTon == singleTon2)
}
//将SingleTon的构造方法私有化
class SingleTon private() {
println("~~~")
}
object SingleTon {
private val s: SingleTon = new SingleTon
def getInstance = {
s
}
}
//说明饿汉式

3.接口

1回顾Java接口

声明接口
   interface 接口名
实现接口
   class 类名 implements 接口名1接口2Java, 一个类可以实现多个接口。
在Java中接口之间支持多继承
接口中属性都是常量
接口中的方法都是抽象的

2Scala接口的介绍

从面向对象来看接口并不属于面向对象的范畴Scala是纯面向对象的语言在Scala中没有接口, 也没有implements关键字。Scala语言中采用trait特质特征来代替接口的概念也就是说多个类具有相同的特征特征时就可以将这个特质特征独立出来采用关键字trait声明。

3特质

trait 的声明
    trait 特质名 {
       trait体
     }**

trait 命名 一般首字母大写. Cloneable , Serializable
object T1 extends object T1 extends Serializable { } 其中 Serializable 就是scala的一个特质。
在scala中java中的接口可以当做特质使用

一个类具有某种特质特征就意味着这个类满足了这个特质特征的所有要素所以在使用时也采用了extends关键字如果有多个特质或存在父类那么需要采用with关键字连接

没有父类
    class 类名 extends 特质1 with 特质2 with 特质3 ..
有父类 
   class 类名 extends 父类 with 特质1 with 特质2 with 特质3

特质的再说明

Scala提供了特质trait特质可以同时拥有抽象方法和具体方法一个类可以实现/继承多个特质。
特质中没有实现的方法就是抽象方法。类通过extends继承特质通过with可以继承多个特质
所有的java接口都可以当做Scala特质使用

abstract class Pet(var name:String, var age: Int, var weight: Double) {
var id: Int // 抽象的属性
// 普通方法
// 抽象方法, 没有=和方法体
def eat
override def toString(): String = {
name + "," + age + "," + weight
}
}
class Dog extends Pet("小黄", 2, 2.5) {
    // Pet(实参列表)作用是调用父类构造器,完成属性的初始化
var id = 20
override def eat = println("吃狗粮")
     // 如果是方法的实现, 可以省略override
def kanjia(): Unit = {
println("看家狗")
}
}
class Bird(name:String, age: Int, weight: Double)
                        extends Pet(name, age, weight) {
var id = 30
def eat = println("吃虫子")
}

object PetTest {
def main(args: Array[String]): Unit = {
//new Pet("小黑", 2, 20)
val dog = new Dog
val bird = new Bird("小飞", 1, 0.2)
println(dog)
println(bird)

var v : Pet = new Dog() // 多态
v = bird
//((Dog)v).kanjia
if (v.isInstanceOf[Dog]) {
v.asInstanceOf[Dog].kanjia // 造型
}
}
}

5带有特质的对象动态混入
1.除了可以在类声明时继承特质以外还可以在构建对象时混入特质扩展对象的功能
2.此种方式也可以应用于对抽象类功能进行扩展
3.动态混入是Scala特有的方式java没有动态混入可在不修改类声明/定义的情况下扩展类的功能非常的灵活耦合性低 。
4.动态混入可以在不影响原有的继承关系的基础上给指定的类扩展功能。

//写一个特质Flyer, 写两个抽象方法takeOff, fly, 具体方法land
trait Flyer {
var name:String // 这是抽象属性, 在特质中变成抽象方法
var speed: Int = 2000 // 普通属性, 在特质中变成抽象方法

def takeOff():Unit
def fly
def land = println("我要着陆了....")
}
//写一个子类Plane, 再混入其他特质 with后面只允许特质, extends后面可以是特质也可以是类
//在类中使用with是静态混入, 只要创建本类对象,都具备这些特质
class Plane extends Flyer with Serializable with Comparable[Plane] {
var name = "大飞机" // 覆盖属性
// 在子类中重新定义
// 特质中的普通属性, 在子类中也仍然需要重新定义, 提供了set方法, 会被特质的特殊类调用.
def takeOff(): Unit = println("飞机起飞中, 推背感强")
override def fly: Unit = println("平稳飞行中...")
override def compareTo(o: Plane) = 0
}

class Man {
type T
}

class Chinese extends Man {
override type T = String
}
class English extends Man {
override type T = Double
}

object TraitExer {

def main(args: Array[String]): Unit = {
type P = Plane
var p: P = new P
println(p)
p.fly
}

def main3(args: Array[String]): Unit = {
val man1 = new Man
// 在创建对象时使用with特质, 也可以实现混入,称为动态混入, 混入的特质只对当前对象有效
val man2 = new Man with Flyer with Serializable {
var name = "abc"
override def takeOff(): Unit = println("不知怎么就会飞了")
override def fly: Unit = println("引力对我没用")
}
man2.fly

val man3 = new Man{} // 匿名内部类

}

def main2(args: Array[String]): Unit = {
val plane = new Plane
plane.takeOff()
plane.land

val flyer2 = new Flyer {
var name = "yyy"
override def takeOff(): Unit = println("lll")
override def fly: Unit = println("fly")
}

}
}

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

trait Operate4 {
println("Operate4...")
def insert(id : Int)
}
trait Data4 extends Operate4 {
println("Data4")
override def insert(id : Int): Unit = {
println("插入数据 = " + id)
}
}
trait DB4 extends Data4 {
println("DB4")
override def insert(id : Int): Unit = {
print("向数据库")
super.insert(id)
}
}
trait File4 extends Data4 {
println("File4")
override def insert(id : Int): Unit = {
print("向文件")
super.insert(id)
}}
class MySQL4 {}
// 1.Scala在叠加特质的时候会首先从后面的特质开始执行
// 2.Scala中特质中如果调用super并不是表示调用父特质的方法而是向前面左边继续查找特质如果找不到才会去父特质查找
val mysql = new MySQL4 with DB4 with File4
//val mysql = new MySQL4 with File4 with DB4
mysql.insert(888)

上述程序执行结果为

Operate4...
Data4
DB4
File4
向文件向数据库插入数据 = 888

debug整个过程发现最后一行程序执行时先到了trait File4中的insert方法然后又去了DB4的insert方法最后去了Data4的insert方法执行打印语句完毕后按逆顺序出栈

叠加特质
叠加特质注意事项和细节
1.特质声明顺序从左到右。
2.Scala在执行叠加对象的方法时会首先从后面的特质(从右向左)开始执行
3.Scala中特质中如果调用super并不是表示调用父特质的方法而是向前面左边继续查找特质如果找不到才会去父特质查找
4.如果想要调用具体特质的方法可以指定super[特质].xxx().其中的泛型必须是该特质的直接超类类型

将上述程序中Trait File 改为以下内容

trait File4 extends Data4 {
println("File4")
override def insert(id : Int): Unit = {
print("向文件")
super[Data4].insert(id)
}
}

此时执行结果为

Operate4...
Data4
DB4
File4
向文件插入数据 = 888

7在特质中重写抽象方法
方式1 : 去掉 super()…
方式2: 调用父特质的抽象方法那么在实际使用时没有方法的具体实现无法编译通过为了避免这种情况的发生。可重写抽象方法这样在使用时就必须考虑动态混入的顺序问题。

理解 abstract override 的小技巧分享可以这里理解当我们给某个方法增加了abstract override
后就是明确的告诉编译器该方法确实是重写了父特质的抽象方法但是重写后该方法仍然是一个抽象方法因为没有完全的实现需要其它特质继续实现[通过混入顺序]

trait Operate5 {
def insert(id : Int)
}
trait File5 extends Operate5 {
abstract override def insert( id : Int ): Unit = {
println("将数据保存到文件中..")
super.insert(id)
}
}

trait DB5 extends Operate5 {
def insert( id : Int ): Unit = {
println("将数据保存到数据库中..")
}
}
class MySQL5 {}
val mysql5 = new MySQL5 with DB5 with File5

重写抽象方法时需要考虑混入特质的顺序问题和完整性问题 看4个案例并判断结果。
var mysql2 = new MySQL5 with DB5 // ok
mysql2.insert(100)
var mysql3 = new MySQL5 with File5 // error
mysql2.insert(100)
var mysql4 = new MySQL5 with File5 with DB5// error
mysql4.insert(100)
var mysql4 = new MySQL5 with DB5 with File5// ok
mysql4.insert(100)

富接口概念即该特质中既有抽象方法又有非抽象方法

trait Operate {
def insert( id : Int ) //抽象
def pageQuery(pageno:Int, pagesize:Int): Unit = { //实现
println("分页查询")
}
}

特质中的具体字段

特质中可以定义具体字段如果初始化了就是具体字段如果不初始化就是抽象字段。混入该特质的类就具有了该字段字段不是继承而是直接加入类成为自己的字段。

特质中的抽象字段

特质中未被初始化的字段在具体的子类中必须被重写。

特质构造顺序

特质也是有构造器的构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”

第一种特质构造顺序(声明类的同时混入特质)
调用当前类的超类构造器
第一个特质的父特质构造器
第一个特质构造器
第二个特质构造器的父特质构造器, 如果已经执行过就不再执行
第二个特质构造器
…重复4,5的步骤(如果有第3个第4个特质)
当前类构造器
第2种特质构造顺序(在构建对象时动态混入特质)
调用当前类的超类构造器
当前类构造器
第一个特质构造器的父特质构造器
第一个特质构造器.
第二个特质构造器的父特质构造器, 如果已经执行过就不再执行
第二个特质构造器
…重复5,6的步骤(如果有第3个第4个特质)
当前类构造器
分析两种方式对构造顺序的影响
第1种方式实际是构建类对象, 在混入特质时该对象还没有创建。
第2种方式实际是构造匿名子类可以理解成在混入特质时对象已经创建了。

扩展类的特质

特质可以继承类以用来拓展该类的一些功能(和java的不同)

trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage()) // 方法来自于Exception类
}
}

所有混入该特质的类会自动成为那个特质所继承的超类的子类

trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage()) // 方法来自于Exception类
}
}
//UnhappyException 就是Exception的子类.
class UnhappyException extends LoggedException{
// 已经是Exception的子类了所以可以重写方法
override def getMessage = "错误消息"
}

如果混入该特质的类已经继承了另一个类(A类)则要求A类是特质超类的子类否则就会出现了多继承现象发生错误。

自身类型

自身类型(self-type)主要是为了解决特质的循环依赖问题同时可以确保特质在不扩展某个类的情况下依然可以做到限制混入该特质的类的类型。
应用案例举例说明自身类型特质以及如何使用自身类型特质

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

4. type关键字

使用type关键字可以定义新的数据类型名称
本质上就是类型的一个别名

type S = String
var v : S = “abc”
def test() : S = “xyz”

5. 嵌套类

在Scala中你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类这样的类是嵌套类其他语法结构也是一样。
嵌套类类似于Java中的内部类。

public class TestJavaClass {
public static void main(String[] args) {
//创建一个外部类对象
OuterClass outer1 = new OuterClass();
//创建一个外部类对象
OuterClass outer2 = new OuterClass();
// 创建Java成员内部类
// 说明在Java中将成员内部类当做一个属性因此使用下面的方式来创建 outer1.new InnerClass().
OuterClass.InnerClass inner1 = outer1.new InnerClass();
OuterClass.InnerClass inner2 = outer2.new InnerClass();

//下面的方法调用说明在java中内部类只和类型相关也就是说,只要是
//OuterClass.InnerClass 类型的对象就可以传给 形参 InnerClass ic
inner1.test(inner2);
inner2.test(inner1);

// 创建Java静态内部类
// 因为在java中静态内部类是和类相关的使用 new OuterClass.StaticInnerClass()
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
}}
class OuterClass { //外部类
class InnerClass { //成员内部类
public void test( InnerClass ic ) {
System.out.println(ic);
}}

static class StaticInnerClass { //静态内部类
}}

scala嵌套类的使用1:

class ScalaOuterClass {
class ScalaInnerClass { //成员内部类
}
}
object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}
val outer1 : ScalaOuterClass = new ScalaOuterClass();
val outer2 : ScalaOuterClass = new ScalaOuterClass();

// Scala创建内部类的方式和Java不一样将new关键字放置在前使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
//创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)

scala嵌套类的使用2:

请编写程序在内部类中访问外部类的属性和方法两种方法。
方式一

class ScalaOuterClass {
var name : String = "scott"
private var sal : Double = 1.2
class ScalaInnerClass { //成员内部类
def info() = {
// 访问方式外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass
        这个外部类的一个实例,
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别学习java的同学可能更容易理解
         ScalaOuterClass.class 的写法.
println("name = " + ScalaOuterClass.this.name
+ " age =" + ScalaOuterClass.this.sal)
}
}
}
object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}
//调用成员内部类的方法
inner1.info()

方式二
内部类如果想要访问外部类的属性也可以通过外部类别名访问(推荐)。即访问方式外部类名别名.属性名 【外部类名.this 等价 外部类名别名】

class ScalaOuterClass {
myOuter => //这样写你可以理解成这样写myOuter就是代表外部类的一个对象.
class ScalaInnerClass { //成员内部类
def info() = {
println("name = " + ScalaOuterClass.this.name
+ " age =" + ScalaOuterClass.this.sal)
println("-----------------------------------")
println("name = " + myOuter.name
+ " age =" + myOuter.sal)
}}
// 当给外部指定别名时需要将外部类的属性放到别名后.
var name : String = "scott"
private var sal : Double = 1.2
}

object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}
inner1.info()类型投影
先看一段代码引出类型投影


class ScalaOuterClass3 {
myOuter =>
class ScalaInnerClass3 { //成员内部类
def test(ic: ScalaInnerClass3): Unit = {
System.out.println(ic)
}
}
}

分析以下代码正确/错误

object Scala01_Class {
    def main(args: Array[String]): Unit = {
        val outer1 : ScalaOuterClass3 = new ScalaOuterClass3();
        val outer2 : ScalaOuterClass3 = new ScalaOuterClass3();
        val inner1 = new outer1.ScalaInnerClass3()
        val inner2 = new outer2.ScalaInnerClass3()
        inner1.test(inner1) // ok
        inner1.test(inner2) // error 原因是scala内部类对象和外部类对象相关.
        //这时可以使用类型投影来屏蔽类型不同
}
}

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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6