• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Scala入门六面向对象

武飞扬头像
Silver Star
帮助1

总目录


Scala 的面向对象思想和 Java 的面向对象思想和概念是一致的。
Scala 中语法和 Java 不同,补充了更多的功能。

1.Scala包

1)基本语法

package 包名

2)Scala 包的三大作用(和 Java 一样)

(1)区分相同名字的类

(2)当类很多时,可以很好的管理类

(3)控制访问范围

1.1 包的命名

1)命名规则

只能包含数字、字母、下划线、小圆点.,但不能用数字开头,也不要使用关键字。

2)案例

demo.class.exec1 //错误,因为 class 关键字
demo.12a //错误,数字开头

3)命名规范

一般是小写字母 小圆点

com.公司名.项目名.业务模块名

4)案例

com.test.oa.model 
com.test.oa.controller
com.test.bank.order

1.2 包说明(包语句)

1)说明

Scala 有两种包的管理风格,一种方式和 Java 的包管理风格相同,每个源文件一个包(包名和源文件所在路径不要求必须一致),包名用"."进行分隔以表示包的层级关系,如com.test.scala。另一种风格,通过嵌套的风格表示层级关系,如下

package com {
    package test {
        package scala {
        }
    }
}

第二种风格有以下特点:

(1)一个源文件中可以声明多个 package

(2)子包中的类可以直接访问父包中的内容,而无需导包

2)案例

// 用嵌套风格定义包
package com {

    import com.test.scala.Inner // 父包访问子包需要导包

    // 在外层包中定义单例对象
    object Outer {
        var out: String = "out"

        def main(args: Array[String]): Unit = {
            println(Inner.in)
        }
    }

    package test {
        package scala {
            // 内层包中定义单例对象
            object Inner {
                val in: String = "in"
                def main(args: Array[String]): Unit = {
                    println(Outer.out)
                    Outer.out = "outer"
                    println(Outer.out)
                }
            }
        }
    }
}

1.3 包对象

在 Scala 中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有 class 和 object

的共享变量,可以被直接访问。但是如果是在其下定义嵌套包,即上一点第二种风格,是访问不到其内的变量和方

法的,除非第一层包名即为所在包的包名,那么内部的对象才能访问到。

1)定义

package object com{
    val shareValue="share"
    def shareMethod()={}
} 

2)说明

(1)若使用 Java 的包管理风格,则包对象一般定义在其对应包下的 package.scala文件中,包对象名与包名保持一致。

(2)如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中。

如果package test内定义一个package test1,然后将package object test改为package object test1,package test1内的对象访问不了package object test1,必须要在同一作用域才能访问;如果按照以下代码块方式定义,然

后再在package test内定义一个package test1,package test1内对象可以访问package object test。

package test {
    object Outer {
        val out: String = "out"
        def main(args: Array[String]): Unit = {
            println(name)
        }
    }
}

package object test {
    val name: String = "test"
}

1.4 导包说明

1)和 Java 一样,可以在顶部使用 import 导入,在这个文件中的所有类都可以使用。

2)局部导入:什么时候使用,什么时候导入。在其作用范围内都可以使用

3)通配符导入:import java.util._

4)给类起名:import java.util.{ArrayList=>JL}

5)导入相同包的多个类:import java.util.{HashSet, ArrayList}

6)屏蔽类:import java.util.{ArrayList =>_,_}(ArrayList =>_表示屏蔽掉ArrayList,以逗号为分隔_

示导入所有的类,出屏蔽类以外)

7)导入包的绝对路径:new _root_.java.util.HashMapnew _root_.加包的绝度路径 )

package java {
    package util {
        class HashMap {
        }
    }
}

案例如下:

import com.test.Fruit // 引入 com.test 包下 Fruit(class 和 object)
import com.test._ // 引入 com.test 下的所有成员
import com.test.Fruit._ // 引入 Fruit(object)的所有成员
import com.test.{Fruit,Vegetable} // 引入 com.test 下的 Fruit 和 Vegetable
import com.test.{Fruit=>Shuiguo} // 引入 com.test 包下的 Fruit 并更名为 Shuiguo
import com.test.{Fruit=>Shuiguo,_} // 引入 com.test 包下的所有成员,并将 Fruit 更名为 Shuiguo
import com.test.{Fruit=>_,_} // 引入 com.test 包下屏蔽 Fruit 类
new _root_.java.util.HashMap // 引入的 Java 的绝对路径

2)注意

Scala 中的三个默认导入分别是

import java.lang._

import scala._

import scala.Predef._

2.类和对象

类:可以看成一个模板

对象:表示具体的事物

2.1 定义类

1)回顾:Java 中的类

如果类是 public 的,则必须和文件名一致。

一般,一个.java 有一个 public 类

注意:Scala 中没有 public,一个.scala 中可以写多个类。

2)基本语法

[修饰符] class 类名 {

类体

}

说明

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

性,可以声明为private,使外部不能访问,但不能是public。

(2)一个 Scala 源文件可以包含多个类

3)案例

//(1)Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是 public)
class Person {
}
//(2)一个 Scala 源文件可以包含多个类
class Teacher{
}

2.2 属性

属性是类的一个组成部分

1)基本语法

[修饰符] var|val 属性名称 [:类型] = 属性值

Bean 属性(@BeanPropetry),可以自动生成规范的 setXxx/getXxx 方法(类似于Java的@Data)

2)案例

class Test {
    private val t1 = 1
    @BeanProperty
    var t2 = ""
}

object Test {
    def main(args: Array[String]): Unit = {
        val test1 = new Test()
        println(test1.t1)	// 内部,可以直接访问
        // get/set方法
        test.setT2("t2")
        println(test.getT2)
    }
}

object Test1 {
  def main(args: Array[String]): Unit = {
    val test1 = new Test()
    println(test1.t1)	// 会报错,不可访问,若去除private则可以访问
  }
}

注意: 在scala中赋初始值时,可以直接赋下划线,对于引用对象表示空,对于Int表示0,对于Boolean表示false

// 必须要声明为var且此处必须明确指明变量类型
var test1: Int = _

3.封装

封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作

(成员方法),才能对数据进行操作。Java 封装操作如下,

(1)将属性进行私有化

(2)提供一个公共的 set 方法,用于对属性赋值

(3)提供一个公共的 get 方法,用于获取属性的值

Scala 中的 public 属性,底层实际为 private,获取或修改属性实际上是通过 get 方法(obj.field())和 set 方法

(obj.field_=(value))对其进行操作。所以 Scala 并不推荐将属性设为 private,再为其设置public 的 get 和 set

方法的做法。但由于很多 Java 框架都利用反射调用 getXXX 和 setXXX 方法,有时候为了和这些框架兼容,也会为

Scala 的属性设置 getXXX 和 setXXX 方法(通过@BeanProperty 注解实现)。

3.1 访问权限

1)说明

在 Java 中,访问权限分为:public,private,protected 和默认。在 Scala 中,可以通过类似的修饰符达到同样

的效果。但是使用上有区别。

(1)Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。

(2)private 为私有权限,只在类的内部和伴生对象中可用。

(3)protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问。

(4)private[包名]增加包访问权限,包名下的其他类也可以使用

class Person {
    private var name: String = "test"
    protected var age: Int = 18
    // 即增加scala_start包的访问权限
    private[scala_start] var sex: String = "男"
    def say(): Unit = {
        println(name)
    }
}

object Person {
    def main(args: Array[String]): Unit = {
        val person = new Person()
        person.say()
        println(person.name)
        println(person.age)
    }
}


class Teacher extends Person {
    def test(): Unit = {
        // 访问不了name
        println(this.age)
        println(this.sex)
    }
}

3.2 方法

1)基本语法

def 方法名(参数列表) [:返回值类型] = {

方法体

}

2)案例

class Person {
    private var name: String = "test"
    protected var age: Int = 18
    // 即增加scala_start包的访问权限
    private[scala_start] var sex: String = "男"
    def test(): Unit = {
        println(name)
    }
}

object Person {
    def main(args: Array[String]): Unit = {
        val person = new Person()
        person.test()
    }
}


class Teacher extends Person {
    // 重写需要加override关键字
    override def test(): Unit = {
        println(this.age)
        println(this.sex)
    }
}

3.3 创建对象

1)基本语法

val | var 对象名 [:类型] = new 类型()

2)案例

(1)val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。

(2)var 修饰对象,可以修改对象的引用和修改对象的属性值

(3)自动推导变量类型不能多态,所以多态需要显示声明

class Person {
    private var name: String = "test"
    protected var age: Int = 18
    // 即增加scala_start包的访问权限
    private[scala_start] var sex: String = "男"
    def test(): Unit = {
        println(name)
    }
}

object Person {
    def main(args: Array[String]): Unit = {
        // val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
        val person = new Person()
        person.name = "test"
        //    person = new Person() // 错误的
        var person1 = new Person()
        person1 = new Person()  // 可以改变引用
        println(person.name)
    }
}

3.4 构造器

和 Java 一样,Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法。

Scala 类的构造器包括:主构造器和辅助构造器

1)基本语法

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

说明:

(1)辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型来区分。

(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。

(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明。

object Test_Constructor {
  def main(args: Array[String]): Unit = {
    val student1 = new Student1
    student1.Student1()

    val student2 = new Student1("test")
    val student3 = new Student1("test1", 18)
  }
}


// 定义一个类
// 不加括号或者空括号代表主构造器没有参数
class Student1 {
  // 定义属性
  var name: String = _
  var age: Int = _

  println("1. 主构造方法被调用")

  // 声明辅助构造方法
  def this(name: String) {
    // 必须直接或间接的调用主构造器
    this()  // 直接调用主构造器(主构造器空参)
    println("2. 辅助构造方法一被调用")
    this.name = name
    println(s"name: $name age: $age")
  }

  def this(name: String, age: Int) {
    this(name)  // 间接调用主构造器
    println("3. 辅助构造方法二被调用")
    this.age = age
    println(s"name: $name age: $age")
  }

  // 在scala中,可以定义与类名相同的方法,但并非构造方法,而是一个一般方法
  // 此方法默认返回为Unit,代表一个过程
  def Student1(): Unit = {
    println("一般方法被调用")
  }
}

3.5 构造器参数

1)说明

Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰

(1)未用任何修饰符修饰,这个参数就是一个局部变量

(2)var 修饰参数,作为类的成员属性使用,可以修改

(3)val 修饰参数,作为类只读属性使用,不能修改

2)案例

object Test_ConstructorParams {
    def main(args: Array[String]): Unit = {
        val student2 = new Student2
        println(s"student2: name = ${student2.name} age = ${student2.age}")

        val student3 = new Student3("test", 18)
        println(s"student3: name = ${student3.name} age = ${student3.age}")

        //    访问不到,表示name与age不是Student4的属性,实际上不加修饰的参数只是主构造器的形参
        //    若要实现相应功能,需要在类中实现相应方法
        val student4 = new Student4("test1", 18)
        //    println(s"student4: name = ${student4.name} age = ${student4.age}")
        student4.printInfo()

        val student5 = new Student5("test2", 18)
        println(s"student5: name = ${student5.name} age = ${student5.age}")

        val student6 = new Student6("test3", 18, "test_school")
        student6.printInfo()
    }
}


// 定义类
// 无参构造器
class Student2 {
    // 由于主构造器无参,故属性要单独定义
    // 且如果要创建时初始化属性,那么必须要定义辅助构造器
    var name: String = _
    var age: Int = _
}

// 上述类定义等价于以下形式
// 在此处name与age相当于类的属性
class Student3(var name: String, var age: Int)

// 主构造器参数无修饰
class Student4(name: String, age: Int) {
    def printInfo(): Unit = {
        println(s"student4: name = ${name} age = ${age}")
    }
}

// 使用val修饰,初始化后属性不可再更改
class Student5(val name: String, val age: Int)

class Student6(var name: String, var age: Int) {
    // 可选属性,需要时通过辅助构造器添加
    var school: String = _

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

    def printInfo(): Unit = {
        println(s"student6: name = $name age = $age school = ${school}")
    }
}

4.继承和多态

1)基本语法

class 子类名 extends 父类名 { 类体 }

(1)子类继承父类的属性方法

(2)scala 是单继承

2)案例

注意:(1)子类继承父类的属性方法

(2)继承的调用顺序:父类构造器->子类构造器

object Test_Inherit {
    def main(args: Array[String]): Unit = {
        val student1 = new Student7("test", 18)
        val student2 = new Student7("test1", 18, "std001")
    }
}

// 定义一个父类
class Person7 {
    var name: String = _
    var age: Int = _

    println("1. 父类主构造器调用")

    def this(name: String, age: Int) {
        this()
        println("2. 父类的辅助构造器调用")
        this.name = name
        this.age = age
    }

    def printInfo(): Unit = {
        println(s"Person: $name $age")
    }
}

// 定义子类
// extends时实际是调用了父类的主构造器
// 在此处直接extends Person是由于父类主构造器无参
// 若有参,应该extends Person(参数列表),例如:若Person主构造器有参数name,则定义如下注释所示
// class Student7(name: String, age: Int) extends Person(name, age),可以添加一下测试理解
// 由此就变为了调用父类的辅助构造器(辅助构造器才有这两个参数)
class Student7(name: String, age: Int) extends Person7 {
    var stdNo: String = _

    println("3. 子类的主构造器被调用")

    def this(name: String, age: Int, stdNo: String) {
        this(name, age)
        println("4. 子类的辅助构造器调用")
        this.stdNo = stdNo
    }

    override def printInfo(): Unit = {
        println(s"Student: $name $age $stdNo")
    }
}

3)动态绑定

Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定。

案例实操(对比 Java 与 Scala 的重写)

Java

public class TestDynamicBind {
    public static void main(String[] args) {
        Worker worker = new Worker();
        System.out.println(worker.name);
        worker.hello();
        worker.hi();

        System.out.println("---------------------------");

        // 多态
        Person person = new Worker();
        System.out.println(person.name);    // 在Java中,属性是静态绑定,方法是动态绑定
        person.hello(); // 只有在运行时才去判定具体对象示例
        //        person.hi();  // Person类中无此方法
    }
}

class Person {
    String name = "person";

    public void hello() {
        System.out.println("hello person");
    }
}

class Worker extends Person {
    String name = "worker";

    public void hello() {
        System.out.println("hello worker");
    }

    public void hi() {
        System.out.println("hi worker");
    }
}

Scala

object Test_DynamicBind {
    def main(args: Array[String]): Unit = {
        val student: Person8 = new Student8
        // 在scala中,方法和属性全部都是动态绑定的
        println(student.name) // student
        student.hello() // hello student
    }
}

class Person8 {
    val name: String = "person"

    def hello(): Unit = {
        println("hello person")
    }
}

class Student8 extends Person8 {
    // 在scala中,如果明确要重写父类某属性,必须要添加override关键字
    override val name: String = "student"

    override def hello(): Unit = {
        println("hello student")
    }
}

5.抽象类

5.1 抽象属性和抽象方法

1)基本语法

(1)定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类

(2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性

(3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法

2)继承&重写

(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类

(2)重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。

(3)子类中调用父类的方法使用 super 关键字

(4)子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;

子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var(会报错)。

因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写

3)案例

object Test_AbstractClass {
    def main(args: Array[String]): Unit = {
        val student = new Student9
        student.test()
        student.test1()
    }
}

// 定义抽象类
abstract class Person9 {
    // 非抽象属性
    val name: String = "person"

    // 抽象属性
    var age: Int

    // 非抽象方法
    def test(): Unit = {
        println("person test")
    }

    // 抽象方法
    def test1(): Unit
}

// 定义具体的实现类
class Student9 extends Person9 {
    // 实现抽象属性和抽象方法 可以不加override
    var age: Int = 18

    def test1(): Unit = {
        println("student test1")
    }

    // 重写非抽象属性和方法
    override val name: String = "student"

    override def test(): Unit = {
        // 可以使用super关键字调用父类的方法
        // super.test()
        println("student test")
    }
}

5.2 匿名子类

1)说明

和 Java 一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。

2)案例

object Test_AnnoymousClass {
    def main(args: Array[String]): Unit = {
        // 由于抽象类不能直接创建对象,必须要实现抽象属性和抽象方法,由此即实现了匿名子类
        val person: Person10 = new Person10 {
            override var name: String = "test"

            override def test(): Unit = println("person test")
        }
        println(person.name)
        person.test()
    }
}

// 定义抽象类
abstract class Person10 {
    var name: String
    def test(): Unit
}

6.单例对象(伴生对象)

Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java

语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名

与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声

明。

6.1 单例对象语法

1)基本语法

object Person{

val country:String=“China”

}

2)说明

(1)单例对象采用 object 关键字声明

(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。

(3)单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。

object Test_Object {
    def main(args: Array[String]): Unit = {
        val student = new Student11("test", 18)
        student.printInfo()
    }
}

// 定义类
class Student11(val name: String, val age: Int) {
    def printInfo(): Unit = {
        println(s"student: name = $name, age = $age, school = ${Student11.school}")
    }
}

// 伴生对象
object Student11 {
    // 在伴生对象中定义的属性相当于静态属性
    // 在伴生对象中定义的方法相当于Java中static修饰的方法
    val school: String = "test"
}

6.2 apply方法

1)说明

(1)通过伴生对象的 apply 方法,实现不使用 new 方法创建对象。

(2)如果想让主构造器变成私有的,可以在()之前加上 private。

(3)apply 方法可以重载。

(4)Scala 中 **obj(arg)**的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和

函数式编程的风格。

(5)当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时

伴生对象的 apply 方法。

object Test_Object {
    def main(args: Array[String]): Unit = {
        //    val student = new Student11("test", 18)
        //    student.printInfo()

        val student =  Student11.newStudent("test", 18)
        student.printInfo()

        val student1 = Student11.apply("test1", 18)
        student1.printInfo()

        // 在Scala底层对伴生对象的apply方法有一定的简化,即可以直接省略apply而使用名称(参数列表)
        val student2 = Student11("test1", 18)
        student2.printInfo()
    }
}

// 定义类
// 构造器私有化
class Student11 private(val name: String, val age: Int) {
    def printInfo(): Unit = {
        println(s"student: name = $name, age = $age, school = ${Student11.school}")
    }
}

// 伴生对象
object Student11 {
    // 在伴生对象中定义的属性相当于静态属性
    // 在伴生对象中定义的方法相当于Java中static修饰的方法
    val school: String = "test"

    // 定义一个类的对象实例的创建方法
    def newStudent(name: String, age: Int): Student11 = new Student11(name, age)

    def apply(name: String, age: Int): Student11 = new Student11(name, age)
}

单例设计模式实现

object Test_Singleton {
    def main(args: Array[String]): Unit = {
        val student1 = Student12.getInstance()
        student1.printInfo()

        val student2 = Student12.getInstance()
        student2.printInfo()

        // 引用相同,说明是同一个对象
        println(student1)
        println(student2)
    }
}

class Student12 private(val name: String, val age: Int) {
    def printInfo(): Unit = {
        println(s"student: name = $name, age = $age, school = ${Student11.school}")
    }
}

// 单例设计模式1
//object Student12 {
//  private val student: Student12 = new Student12("test", 18)
//
//  def getInstance(): Student12 = student
//}

// 单例设计模式2
object Student12 {
    private var student: Student12 = _

    def getInstance(): Student12 = {
        if(student == null) {
            // 如果没有对象实例,那么就创建一个
            student = new Student12("test", 18)
        }
        student
    }
}

7.特质(Trait)

Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以

将这个特质(特征)独立出来,采用关键字 trait 声明。Scala 中的 trait 中即可以有抽象属性和方法,也可以有

具体的属性和方法一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。Scala 引入 trait 特

征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充。

7.1 特质声明

1)基本语法

trait 特质名 {
    trait 主体
}

2)案例

/ 定义一个特质
trait Young {
    // 声明抽象和非抽象的属性
    var age: Int
    val name: String = "young"

    // 声明抽象和非抽象的方法
    def dating(): Unit

    def play(): Unit = {
        println("young people is playing")
    }
}

7.2 特质基本语法

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了

extends 关键字,如果有多个特质或存在父类,那么需要采用 with关键字连接。

1)基本语法:

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

有父类:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…

2)说明

(1)类和特质的关系:使用继承的关系。

(2)当一个类去继承特质时,第一个连接词是 extends,后面是 with。

(3)如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。

注意:(1)特质可以同时拥有抽象方法和具体方法

(2)一个类可以混入(mixin)多个特质

(3)所有的 Java 接口都可以当做 Scala 特质使用

(4)动态混入:可灵活的扩展类的功能

(4.1)动态混入:创建对象时混入 trait,而无需使类混入该 trait

(4.2)如果混入的 trait 中有未实现的方法,则需要实现

3)案例

object Test_Trait {
    def main(args: Array[String]): Unit = {
        val student: Student13 = new Student13
        student.sayHello()
        student.study()
        student.dating()
        student.play()
    }
}

// 定义一个父类
class Person13 {
    val name: String = "person"
    var age: Int = 18
    def sayHello(): Unit = {
        println("hello from: "   name)
    }
}

// 定义一个特质
trait Young {
    // 声明抽象和非抽象的属性
    var age: Int
    val name: String = "young"

    // 声明抽象和非抽象的方法
    def dating(): Unit

    def play(): Unit = {
        println(s"young people $name is playing")
    }
}

class Student13 extends Person13 with Young {
    // 重写冲突属性(Person13与Young都包含,若不重写则会报错)
    override val name: String = "student"

    // 实现抽象方法
    def dating(): Unit = println(s"student $name is dating")

    def study(): Unit = println(s"student $name is studying")

    // 重写父类方法
    override def sayHello(): Unit = {
        println(s"hello from: student $name")
    }
}

动态混入案例

object Test_TraitMixin {
    def main(args: Array[String]): Unit = {
        val student = new Student14
        student.study()
        student.increase()

        student.play()
        student.increase()

        student.dating()
        student.increase()

        println("------------------------")
        // 动态混入
        val studentWithTalent = new Student14 with Talent {
            override def singing(): Unit = println("student is good at singing")

            override def dancing(): Unit = println("student is good at dancing")
        }

        studentWithTalent.sayHello()
        studentWithTalent.play()
        studentWithTalent.dating()
        studentWithTalent.singing()
        studentWithTalent.dancing()
    }
}

trait Talent {
    def singing(): Unit
    def dancing(): Unit
}

// 定义一个特质
trait Knowledge {
    var amount: Int = 0

    def increase(): Unit
}

class Student14 extends Person13 with Young with Knowledge {
    // 重写冲突属性(Person13与Young都包含,若不重写则会报错)
    override val name: String = "student"

    // 实现抽象方法
    def dating(): Unit = println(s"student $name is dating")

    def study(): Unit = println(s"student $name is studying")

    // 重写父类方法
    override def sayHello(): Unit = {
        println(s"hello from: student $name")
    }

    // 实现特质中的抽象方法
    override def increase(): Unit = {
        amount  = 1
        println(s"student $name knowledge increased: $amount")
    }
}

7.3 特质叠加

1)说明

由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法

(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:

第一种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。

第二种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略,所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来。

2)案例

object Test_TraitOverlying {
    def main(args: Array[String]): Unit = {
        val student = new Student15
        // extends Person13 with Talent15 with Knowledge15
        // 三者方法冲突时,会调用后面的特质的方法
        // 三者是平等的,在叠加时,从后往前叠加,故在调用super时,即使有父类也会调用最后一个特质的方法
        student.increase()
    }
}

trait Talent15 {
    def singing(): Unit
    def dancing(): Unit

    def increase(): Unit = {
        println("talent increased")
    }
}

// 定义一个特质
trait Knowledge15 {
    var amount: Int = 0

    def increase(): Unit = {
        println("knowledge increased")
    }
}

class Student15 extends Person13 with Talent15 with Knowledge15 {
    override def singing(): Unit = println("singing")

    override def dancing(): Unit = println("dancing")

    override def increase(): Unit = {
        super.increase()
    }
}

7.4 特质叠加执行顺序

当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序

排序规则如下:

学新通

结论:

(1)案例中的 super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass 中的 super指代Color,Color中的super指代Category,Category中的super指代Ball。

(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如super[Category].describe()。

案例如下:

object Test_TraitOverlying {
    def main(args: Array[String]): Unit = {
        val student = new Student15
        // extends Person13 with Talent15 with Knowledge15
        // 三者方法冲突时,会调用后面的特质的方法
        // 三者是平等的,在叠加时,从后往前叠加,故在调用super时,即使有父类也会调用最后一个特质的方法
        // 上述情况是在三者无关联的情况下(即不存在共同的父特质等)
        student.increase()

        // 钻石问题特征叠加
        val footBall = new FootBall
        // 由于ColorBall与CategoryBall有共同的父特质,再根据extends CategoryBall with ColorBall的顺序
        // 根据叠加原则,其实FootBall的super指代ColorBall
        // ColorBall的super指代CategoryBall
        // CategoryBall的super指代Ball
        // 故而调用顺序即FootBall、ColorBall、CategoryBall、Ball
        // 故输出为the ball is a   red-   foot-   ball
        println(footBall.describe())  // the ball is a red-foot-ball
    }
}

// 定义球类特质
trait Ball {
    def describe(): String = "ball"
}

// 定义颜色特征
trait ColorBall extends Ball {
    val color: String = "red"
    override def describe(): String = s"$color-${super.describe()}"
}

// 定义种类特征
trait CategoryBall extends Ball {
    var category: String = "foot"

    override def describe(): String = s"$category-${super.describe()}"
}

// 定义一个自定义球的类
class FootBall extends CategoryBall with ColorBall {
    override def describe(): String = s"the ball is a ${super.describe()}"
}

trait Talent15 {
    def singing(): Unit
    def dancing(): Unit

    def increase(): Unit = {
        println("talent increased")
    }
}

// 定义一个特质
trait Knowledge15 {
    var amount: Int = 0

    def increase(): Unit = {
        println("knowledge increased")
    }
}

class Student15 extends Person13 with Talent15 with Knowledge15 {
    override def singing(): Unit = println("singing")

    override def dancing(): Unit = println("dancing")

    override def increase(): Unit = {
        super.increase()
    }
}

7.5 特质自身类型

1)说明

自身类型可实现依赖注入的功能。

2)案例

object Test_TraitSelfType {
    def main(args: Array[String]): Unit = {
        val user = new RegisterUser("test", "123456")
        user.insert()
    }
}

// 用户类
class User(val name: String, val password: String)

trait UserDao {
    // UserDao需要使用User的一些属性,又不想二者之间存在继承关系
    // 即可指定一个自身类型,就相当于特质内已含有了User
    // 类似于依赖注入
    _: User =>

    // 向数据插入数据
    def insert(): Unit = {
        println(s"insert into db: ${this.name}")
    }
}

// 定义注册用户类
class RegisterUser(name: String, password: String) extends User(name, password) with UserDao

7.6 特质和抽象类的区别

1.优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。

2.如果需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。

8.拓展

8.1 类型检查和转换

1)说明

(1)obj.isInstanceOf[T]:判断 obj 是不是 T 类型。

(2)obj.asInstanceOf[T]:将 obj 强转成 T 类型。

(3)classOf 获取对象的类名。

2)案例

object Test_Extends {
    def main(args: Array[String]): Unit = {
        // 类型的检测和转换
        val student: Student17 = new Student17("test", 18)
        student.sayHi()
        student.study()

        val person: Person17 = new Student17("test1", 18)
        person.sayHi()

        println(s"student is Student17: ${student.isInstanceOf[Student17]}")
        println(s"student is Person17: ${student.isInstanceOf[Person17]}")
        println(s"person is Person17: ${person.isInstanceOf[Person17]}")
        println(s"person is Student17: ${person.isInstanceOf[Student17]}")

        val person2: Person17 = new Person17("test2", 18)
        println(s"person2 is Student17: ${person2.isInstanceOf[Student17]}")

        // 类型转换
        if(person.isInstanceOf[Student17]) {
            val newStudent = person.asInstanceOf[Student17]
            newStudent.study()
        }

        println(classOf[Student17])
    }
}

class Person17(val name: String, val age: Int) {
    def sayHi(): Unit = {
        println(s"hi from person: $name")
    }
}

class Student17(name: String, age: Int) extends Person17(name, age) {
    override def sayHi(): Unit = {
        println(s"hi from student: $name")
    }

    def study(): Unit = {
        println("student study")
    }
}

8.2 枚举类和应用类

1)说明

枚举类:需要继承 Enumeration

应用类:需要继承 App

2)案例

object Test_Extends_2 {
    def main(args: Array[String]): Unit = {
        // 测试枚举类
        println(WorkDay.MONDAY)
    }
}

// 定义枚举类对象
object WorkDay extends Enumeration {
    val MONDAY = Value(1, "Monday")
    val TuesDay = Value(2, "TuesDay")
}

// 定义应用类对象
// 可以直接运行
object TestApp extends App {
    println("app start")
}

8.3 Type定义新类型

1)说明

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

2)案例

type String_2 = String
val test: String_2 = "test"

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgkbaae
系列文章
更多 icon
同类精品
更多 icon
继续加载