七、Scala从入门到精通一一面向对象编程(中级)

7、包

7.1、看一个应用场景

现在有两个程序员共同开发一个项目,程序员xiaoming希望定义一个类取名Dog,程序员xiaoqiang也想定义一个类也叫Dog。两个程序员为此还吵了起来,怎么办?==》使用包即可以解决这个问题.

7.2、回顾-Java包的三大作用

1、区分相同名字的类
2、当类很多时,可以很好的管理类
3、控制访问范围

7.3、回顾-Java打包命令

打包基本语法packagecom.包名;

打包的本质分析
实际上就是创建不同的文件夹来保存类文件,画出示意图。
在这里插入图片描述

7.4、快速入门

使用打包技术来解决上面的问题,不同包下Dog类

package com.test.chapter07.javapackage;

public class TestTiger{
	public static void main(String[]args){
//使用xm的Tiger
com.test.chapter07.javapackage.xm.Tiger tiger01=newcom.test.chapter07.javapackage.xm.Tiger();
//使用xh的Tiger
com.test.chapter07.javapackage.xh.Tiger tiger02=newcom.test.chapter07.javapackage.xh.Tiger();
	}
}

7.5、Scala包的基本介绍

和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项。

7.6、Scala包快速入门

使用打包技术来解决上面的问题,不同包下Dog类

packagecom.test.chapter07.scalapackage
object TestTiger{
def main(args:Array[String]):Unit={
//使用xh的Tiger
val tiger1=new com.test.chapter07.scalapackage.xh.Tiger
//使用xm的Tiger
val tiger2=newcom.test.chapter07.scalapackage.xm.Tigerprintln(tiger1+""+tiger2)
	}
}

7.7、Scala包的特点概述

基本语法
package 包名

Scala包的三大作用(和Java一样)
1、区分相同名字的类
2、当类很多时,可以很好的管理类
3、控制访问范围

Scala中包名和源码所在的系统文件目录结构要可以不一致,但是**编译后的字节码文件路径和包名会保持一致(**这个工作由编译器完成)。

packagecom.test.chapter07.scalapackage.hello2
object TestTiger{
defmain(args:Array[String]):Unit={
//使用xh的Tiger
val tiger1=newcom.atguigu.chapter07.scalapackage.xh.Tiger
//使用xm的Tiger
val tiger2=newcom.atguigu.chapter07.scalapackage.xm.Tigerprintln(tiger1+""+tiger2)
	}
}
class Employee{
}

7.8、scala包的命名

命名规则:

只能包含数字、字母、下划线、小圆点.,但不能用数字开头, 也不要使用关键字。
demo.class.exec1 //错误 , 因为class是关键字
demo.12a // 错误,因为不能以数字开头

命名规范:

一般是小写字母+小圆点一般是
com.公司名.项目名.业务模块名
比如:
com.sina.edu.user
com.sohu.bank.order

7.9、Scala会自动引入的常用包

在这里插入图片描述

7.10、Scala包注意事项和使用细节

1、scala进行package 打包时,可以有如下形式。

package com.test.scala
class Person{
  val name = "Nick"
  def play(message: String): Unit ={
  println(this.name + " " + message)
  }
}
//代码说明 传统的方式
package com.test
package scala
class Person{
  val name = "Nick"
  def play(message: String): Unit ={
  println(this.name + " " + message)
  }
}
//代码说明 :和第一种方式完全等价
package com.test{
  package scala{
    class Person{
      val name = "Nick"
      def play(message: String): Unit ={
      println(this.name + " " + message)
      }
    }
  }
} //代码说明

2、包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了

代码说明
//1.packagecom.test{}表示我们创建了包com.test,在{}中
//我们可以继续写它的子包scala//com.test.scala,还可以写类,特质trait,还可以写object
//2.即sacla支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait和object
package.com.test{//包com.test
	class User{ // 在com.test包下创建一个User类
	}
package.scala2{//创建包com.test.scala2
	class User{//在com.test.scala2包下创建个User类
	}
}
package.scala{//包com.test.scala
class Person{//表示在com.test.scala下创建类Person
val name="Nick"
def play(message:String):Unit{
	println(this.name+""+message)
	}
}
	object Test100{//表示在com.test.scala创建objectTest
		def main(args:Array[String]):Unit={
			println("ok")
			}
		}
	}
}

3、作用域原则:可以直接向上访问。即:Scala中子包中直接访问父包中的内容,大括号体现作用域。(提示:Java中子包使用父包的类,需要import)。在子包和父包类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可

package com.test{
  //这个类就是在com.test包下
  class User{
  }
  //这个类对象就是在Monster$ , 也在com.test包下
  object Monster {
  }
  class Dog {
  }
  package scala {
    //这个类就是在com.test.scala包下
    class User{
    }
    //这个Test 类对象
    object Test {
      def main(args: Array[String]): Unit = {
          //子类可以直接访问父类的内容
          var dog = new Dog()
          println("dog=" + dog)
          //在子包和父包 类重名时,默认采用就近原则.
          var u = new User()
          println("u=" + u)
          //在子包和父包 类重名时,如果希望指定使用某个类,则带上包路径
          var u2 = new com.test.User()
          println("u2=" + u2)
      }
    }
  }
}

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

package com.test{
  //引入在com.test包中希望使用到子包的类Tiger,因此需要引入.
  import com.test.scala.Tiger
  //这个类就是在com.test包下
  class User{
  }
  package scala {
    //Tiger 在 com.test.scala 包中
    class Tiger {}
    }
  object Test2 {
    def main(args: Array[String]): Unit = {
        //如果要在父包使用到子包的类,需要import
        val tiger = new Tiger()
        println("tiger=" + tiger)
    		}
    	}
    }

5、可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层)
6、包名可以相对也可以绝对,比如,访问BeanProperty的绝对路径是:root. scala.beans.BeanProperty ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理

7.11、包对象

基本介绍:包可以包含类、对象和特质trait但不能包含函数/方法或变量的定义。这是Java虚拟机的局限。为了弥补这一点不足,scala提供了包对象的概念来解决这个问题

7.12、包对象的应用案例

package com.test{  
  //每个包都可以有一个包对象。你需要在父包(com.test)中定义它,且名称与子包一样。
  package object scala {
    var name = "jack"
    def sayOk(): Unit = {
      println("package object sayOk!")
    }
  }
  package scala {
    class Test {
      def test() : Unit ={
        //这里的name就是包对象scala中声明的name
        println(name)
        sayOk()//这个sayOk 就是包对象scala中声明的sayOk
      }
    }
    object TestObj {
      def main(args: Array[String]): Unit = {
        val t  = new Test()
        t.test()
        //因为TestObje和scala这个包对象在同一包,因此也可以使用
        println("name=" + name)
      }}}}

7.13、包对象的底层实现机制分析(重点)

1、当创建包对象后,在该包下生成 public final class package 和 public final class package$
2、通过 package$ 的一个静态实例完成对包对象中的属性和方法的调用。
在这里插入图片描述
在这里插入图片描述

7.14、包对象的注意事项

1、每个包都可以有一个包对象。你需要在父包中定义它。如图
在这里插入图片描述
2、包对象名称需要和包名一致,一般用来对包的功能补充

8、包的可见性问题

8.1、Scala包的可见性介绍

在Java中,访问权限分为:public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别
代码案例:

object Testvisit {
  def main(args: Array[String]): Unit = {
    val  c = new Clerk()
    c.showInfo()
    Clerk.test(c)
  }}
class Clerk {
  var name : String = "jack"
  private var sal : Double = 9999.9
  def showInfo(): Unit = {
    println(" name " + name + " sal= " + sal)
  }}
object Clerk{
  def test(c : Clerk): Unit = {
    //这里体现出在伴生对象中,可以访问c.sal
    println("test() name=" + c.name + " sal= " + c.sal)
  }}

8.2、Scala中包的可见性和访问修饰符的使用

1、当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法,因此从使用效果看是任何地方都可以访问)
2、当方法访问权限为默认时,默认为public访问权限
3、private为私有权限,只在类的内部和伴生对象中可用
4、protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问
5、在scala中没有public关键字,即不能用public显式的修饰属性和方法。
6、包访问权限(表示属性有了限制。同时包也有了限制),这点和Java不一样,体现出Scala包使用的灵活性

package com.test.scala
class Person {
  private[scala] val pname="hello" // 增加包访问权限后,1.private同时起作用。不仅同类可以使用 2. 同时com.test.scala中包下其他类也可以使用
}
当然,也可以将可见度延展到上层包
private[test] val description="zhangsan"
说明:private也可以变化,比如protected[test], 非常的灵活。

7、整体的代码演示

package com.test.chapter07.visit
object TestVisit {
  def main(args: Array[String]): Unit = {
    val c = new Clerk()
    //    println(c.age) 无法访问
    c.showInfo()
    Clerk.test(c)

    // 创建Person对象
    val p1 = new Person
    println(p1.name)
  }
}

class Clerk {
  var name: String = "jack" //可读
  private var sal: Double = 9999.9 //可读可写
  protected var age: Int = 10
  var job: String = "大数据工程师"

  def showInfo(): Unit = {
    // 在奔雷可以使用私有的
    println("name=" + name + "\n" + "sal=" + sal)

  }
}

// 当一个文件中出现了 class Clerk 和 object Clerk
// 1、class Clerk 称为伴生类
// 2、object Clerk 的伴生对象
// 3、因为scala设计者将static拿掉,他就设计了 伴生类和伴生对象的概念
// 4、伴生类 写非静态的内容  伴生对象  就是静态内容
// 5、在伴生对象里可以访问私有对象
object Clerk {
  def test(c: Clerk): Unit = {
    // 这里体现出在伴生对象中,可以访问  C.sal
    println("test()name =" + c.name + "sal =" + c.sal)
  }

}

class Person {
  // 这里我们增加一个包访问权限
  // 下面private[visit] => 1、仍然是private 2、在visit包(包括子包)下也可以使用
  //name ,相当于扩大访问范围
  private[visit] val name = "jack"
}

9、包的引入

9.1、scala引入包的基本介绍

Scala引入包也是使用import,基本的原理和机制和Java一样,但是Scala中的import功能更加强大,也更灵活。

因为Scala语言源自于Java,所以java.lang包中的类会自动引入到当前环境中,而Scala中的scala包和Predef包的类也会自动引入到当前环境中,即起其下面的类可以直接使用

如果想要把其他包中的类引入到当前环境中,需要使用import语言

9.2、Scala引入包的细节和注意事项

1、在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import包的作用范围,提高效率。

class User{
  import scala.beans.BeanProperty // 在需要时引入,作用域在{}
  @BeanProperty var name : String = ""
class Dog{

  // @BeanProperty var name : String = ""  //error
}

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

import scala.beans._ // _表示将该包的所有内容引入,等价 *

3、如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)

  def test(): Unit ={
    // 我们可以使用选择题,选择引入包的内容 ,这里,我们只引入HashMap HashSet
    import scala.collection.mutable.{HashMap,HashSet}
    var map = new HashMap()
    var set = new HashSet()
}

4、如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名

  def test2(): Unit ={
    // 下面的含义是 将java.util.HashMap重命名为JavaHashMap
    import java.util.{HashMap=>JavaHashMap,List}
    import scala.collection.mutable._
    var map = new HashMap() // Scala
    var map1 = new JavaHashMap();  // Java
  }

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

importjava.util.{HashMap=>_,_}//含义为引入java.util包的所有类,但是忽略HahsMap类.
var map=new HashMap()//此时的HashMap指向的是scala中的HashMap,而且idea工具,的提示也不会显示java.util的HashMaple

10、面向对象编程方法-抽象

如何理解抽象

我们在前面去定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象
在这里插入图片描述

11、面对对象三大特征

11.1、基本介绍

面向对象编程有三大特征:封装、继承和多态。

11.2、封装介绍

封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
在这里插入图片描述

11.3、封装的理解和好处

1、隐藏实现细节
2、提可以对数据进行验证,保证安全合理
3、同时可以加入业务逻辑

11.4、如何体现封装

1、对类中的属性进行封装
2、通过成员方法,包实现封装

11.5、封装的实现步骤

1、将属性进行私有化
2、提供一个公共的set方法,用于对属性判断并赋值
def setXxx(参数名 : 类型) : Unit = {
//加入数据验证的业务逻辑
属性 = 参数名
}

3、提供一个公共的get方法,用于获取属性的值
def getXxx() [: 返回类型] = {
return 属性
}

11.6、快速入门案列

那么在Scala中如何实现这种类似的控制呢?
1、请大家看一个小程序(TestEncap.scala),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证[要求1-120之间]。
在这里插入图片描述

object Test extends App {
  val p = new Person

  p.setAge(10)

}

class Person {
  var name: String = _
  //var age ; //当是public时,可以随意的进行修改,不安全
  private var age: Int = _
  private var salary: Float = _
  private var job: String = _

  def setAge(age: Int): Unit = {
    if (age >= 0 && age <= 120) {
      this.age = age
      println(this.age)
    } else {
      println("输入的数据不合理");
      //可考虑给一个默认值
      this.age = 20
      println(this.age)
    }
  }
} //案例演示

11.7、Scala封装的注意事项的小结

前面讲的Scala的封装特性,大家发现和Java是一样的,下面我们看看Scala封装还有哪些特点。

1、Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的

在这里插入图片描述
2、因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的getset,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性

3、从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法, 看一下反编译的代码就明白
4、有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射

12、面向对象编程-继承

12.1、Java继承的简单回顾

class 子类名 extends 父类名 { 类体 }
子类继承父类的属性和方法

12.2、继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类即可。

和Java一样,Scala也支持类的单继承
在这里插入图片描述

12.3、Scala继承的基本语法

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

12.4、Scala继承快速入门


object Extend01 {
  def main(args: Array[String]): Unit = {
    // 使用
    val student = new Student
    student.name = "jack" // 调用了Student.name()
    student.studying()
    student.showInfo()
  }
}

class Person{ // Person类
  var name:String = _
  var age:Int = _
  def showInfo(): Unit ={
    println("学生的信息如下")
    println("名字:"+this.name)
  }
}

//Student类继承Person
class Student extends Person{
  def studying(): Unit ={
    // 这里可以使用父类的属性
    println(this.name+"学习Scala中····")
  }
}

12.5、Scala继承给编程带来的便利

代码的复用性提高了
代码的扩展性维护性提高了【面试官问:当我们修改父类时,对应的子类就会继承相应的方法和属性】

12.6、scala子类继承了什么,怎么继承了?

子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问【debug代码验证可以看到】

// 说明
// 1.在Scala中,子类继承了父类的所有属性
// 2.但是private的属性和访问无法访问
object Extend02 {
  def main(args: Array[String]): Unit = {
    val sub = new Sub
    sub.sayOk()
    //sub.test200()  编译器不让过

  }
}

// 父类(基类)
class Base {
  var n1: Int = 1 // public n1() , public n1_$eq()
  protected var n2: Int = 2
  private var n3: Int = 3 //private n1() , private n1_$eq()

  def test100(): Unit = { // 默认 public test100()
    println("base 100")
  }

  protected def test200(): Unit = { // public
    println("base 200")
  }

  private def test300(): Unit = { // private
    println("base 300")
  }

}

// Sub 继承 Base
class Sub extends Base {
  def sayOk(): Unit = {
    this.n1 = 20 // 这里访问本质 this.n1_$eq()
    this.n2 = 40
    println("范围=" + this.n1 + this.n2)

    test100()
    test200() // 子类中使用protected
  }
}

在这里插入图片描述

12.7、重写方法

说明: scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字

object MethodOverride01 {
  def main(args: Array[String]): Unit = {

    val emp = new Emp100
    emp.printName()
  }
}

// Person类
class Person100 {
  var name: String = "tom"
  def printName() { // 输出名字
    println("Person printName() " + name)
  }
  def sayHi(): Unit ={
      println("SayHI···")
  }
}

// 这里我们继承Person
class Emp100 extends Person100 {
  // 这里需要显示的使用override
  override def printName() {
    println("Emp printName()" + name)
    // 在子类中需要去调用父类的方法,使用super
    super.printName()
    sayHi()
  }

  def sayHello(): Unit ={

  }
}

12.8、Scala中类型检查和转换

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

1、classOf[String]就如同Java的 String.class 。
2、obj.isInstanceOf[T]就如同Java的obj instanceof T 判断obj是不是T类型。
3、obj.asInstanceOf[T]就如同Java的(T)obj 将obj强转成T类型

object TypeConvert {
  def main(args: Array[String]): Unit = {

    // classOf的使用,可以得到类名
    println(classOf[String]) //输出
    val s = "king"
    println(s.getClass.getName) // 使用反射机制

    // isInstanceOf asInstanceOf
    var p1 = new Person200
    var emp = new Emp200
    // 将子类引用给父类(向上转型,自动转换)
    p1 = emp
    // 将父类的引用重新转成子类引用(多态),即向下转型
    var emp2 = p1.asInstanceOf[Emp200]

    emp2.sayHello()


  }
}

// Person类
class Person200 {
  var name: String = "tom"

  def printName() { // 输出名字
    println("Person printName() " + name)
  }

  def sayHi(): Unit = {
    println("SayHI···")
  }
}

// 这里我们继承Person
class Emp200 extends Person200 {
  // 这里需要显示的使用override
  override def printName() {
    println("Emp printName()" + name)
    // 在子类中需要去调用父类的方法,使用super
    super.printName()
    sayHi()
  }

  def sayHello(): Unit ={

  }
}

最佳实践
类型检查和转换的最大价值在于:可以判断传入对象的类型,然后转成对应的子类对象,进行相关操作,这里也体现出多态的特点。
在这里插入图片描述

object TypeConvertCase {
  def main(args: Array[String]): Unit = {

    val stu = new Student400
    val emp = new Emp400
    test(stu)
    test(emp)
  }

  // 写了一个参数 多态代码
  // 因为在OOP中 一个父类的引用可以接收所有子类的引用,多态(多态参数)
  def test(p: Person400): Unit = {
    // 使用Scala类型检查和转换
    if (p.isInstanceOf[Emp400]) {
      p.asInstanceOf[Emp400].ShowInfo()
      // p.isInstanceOf[Emp400]对p的类型没有任何变化,而是返回的Emp400
    } else if (p.isInstanceOf[Student400]) {
      p.asInstanceOf[Student400].cry()
    }else{
      println("转换失败")
    }
  }
}

class Person400 {
  def printName(): Unit = {
    println("Person400 printName")
  }

  def SayOk(): Unit = {
    println("Person400 SayOk")
  }
}

class Student400 extends Person400 {
  val stuid = 100

  override def printName(): Unit = {
    println("Student400 printName")
  }

  def cry(): Unit = {
    println("学生的id=" + this.stuid)
  }
}

class Emp400 extends Person400 {
  val Empid = 100

  override def printName(): Unit = {
    println("Emp400 printName")
  }

  def ShowInfo(): Unit = {
    println("雇员的id=" + this.Empid)
  }
}

12.9、Scala中超类的构造

1、类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用.),这点在前面我们说过了。
2、只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params)
在这里插入图片描述

object ScalaBaseConstrator {
  def main(args: Array[String]): Unit = {
    // 分析一下他的执行流程
    // 1.因为scala仍然遵守先构建父类的部分  extends Person700()
    // 2. Person ...
    // 3. Emp ... (Emp700的主构造器)
//        val emp = new Emp700
    // 分析一下他的执行流程
    // 1.因为scala仍然遵守先构建父类的部分  extends Person700()
    // 2. Person ...
    // 3. Emp ... (Emp700的主构造器)
    // 4. Emp 辅助构造器···
    println("============")
    val emp2 = new Emp700("marry")
    println("************")
    val emp3 = new Emp700("smith")

    // Person... name = terry
    // Emp
    println("·················")
    val emp4 = new Emp700("terry", 400)
    emp4.showinfo() // terry
  }
}

// 父类 Person
class Person700(pName: String) {
  var name = pName
  println("Person...")

  def this() {
    this("默认的名字")
    println("默认的名字")
  }
}

// 子类 Emp 继承Person
class Emp700(eName: String, eage: Int) extends Person700(eName) {

  println("Emp .........")

  // 这是辅助构造器
  def this(name: String) {
    this(name,100) // 必须调用主构造器
    this.name = name
    println("Emp 辅助构造器")
  }

  def showinfo(): Unit = {
    println("雇员的名字是:" + name)
  }
}

12.10、覆写字段

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

我们看一个关于覆写字段的案例
在这里插入图片描述

object ScalaFiledOverrideDemo {
  def main(args: Array[String]): Unit = {

    val obj1: AAA = new BBB
    val obj2: BBB = new BBB
    //obj1.age => obj1.age() // 动态绑定机制
    //obj2.age => obj2.age()
    println("obj1.age"+obj1.age)
    println("obj2.age"+obj2.age)
  }
}


class AAA {
  val age: Int = 10 // 会生成 public Age()
}

class BBB extends AAA {
  override val age: Int = 20 //会生成 public Age()
}

在这里插入图片描述

覆写字段的注意事项和细节
1、def只能重写另一个def(即:方法只能重写另一个方法)
2、val只能重写另一个val 属性 或 重写不带参数的def
3、var只能重写另一个抽象的var属性
在这里插入图片描述
抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类

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

12.11、抽象类

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

快速入门案列
我们看看如何把Animal做成抽象类, 包含一个抽象的方法cry()

object AbstractDemo01 {
  def main(args: Array[String]): Unit = {
    println("xxx")
  }
}

abstract class Animal {
  var name: String // 抽象的字段
  var age: Int // 抽象的字段
  var color: String = "balck"

  def cry() // 抽象方法,不需要标记abstract
}

抽象类基本语法
abstract class Person() { // 抽象类
var name: String // 抽象字段, 没有初始化
def printName // 抽象方法, 没有方法体

}

说明:抽象类的价值更多是在于设计,是设计者设计好后,让子类继承并实现抽象类(即:实现抽象类的抽象方法)

12.12、Scala抽象类使用的注意事项和细节讨论:

1、抽象类不能被实例
2、抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
3、一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract

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

12.13、匿名子类

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

bject ScalaNoNameDemo02 {
  def main(args: Array[String]): Unit = {
    val monster = new Monster
    // 匿名子类
    {override def cry(): Unit = {
      println("ok···")
    }

      override var name: String = _
    }
    monster.cry()
  }
}


abstract class Monster{
  var name:String
  def cry()
}

12.14、继承层级

Scala继承层级一览图
在这里插入图片描述
对上图的一个小结:
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可以是任何类。

13、面向对象编程作业

练习1
编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性【品牌brand】
编写NotePad子类,继承Computer类,添加特有属性【颜色color】编写Test Object,在main方法中创建PC和NotePad对象,分别对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法并打印输出信息

object Exercise01 {
  def main(args: Array[String]): Unit = {
    val pc = new PC()
    val notePad = new NotePad()
    pc.Cpu = "i7-8700"
    pc.RAM = "12g"
    pc.Disk = "ZDisk"
    pc.Brand = "华硕"
    notePad.clolr = "黑色"
    println(pc.Brand)
    pc.getDetails()
    println(notePad.clolr)
  }

}

class Computer {
  var Cpu: String = ""
  var RAM: String =""
  var Disk: String =""

  def getDetails(): Unit = {
    println("Cpu=" + Cpu + "\nRam=" + RAM + "\nDISk=" + Disk)

  }
}
class PC extends Computer{
  var Brand :String = ""
}
class NotePad extends Computer{
  var clolr:String = ""
}

练习2
根据下图实现类。在TestCylinder类中创建Cylinder类的对象,设置圆柱的底面半径和高,并输出圆柱的体积
在这里插入图片描述

object Exercise02 {
  def main(args: Array[String]): Unit = {
    val area1 = new TestCylinder
    var area2 = new area1.Cylinder

    println(area2.findArea(5.0))
    println(area2.findVolume(2))
  }
}

class TestCylinder {

  class Cylinder {
    @BeanProperty var radius: Double = 1

    def findArea(radius: Double): Double = {
      var area: Double = 0
      area = radius * radius * 3.14
      area
    }

    @BeanProperty var length: Double = 1

    def findVolume(length: Double): Double = {
      var volume: Double = 0
      volume = findArea(radius) * length
      volume
    }
  }

}

练习3(多态应用)
定义员工类Employee,包含姓名和月工资,以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法

测试类中添加一个方法showEmpAnnal,实现获取任何员工对象的年工资,并在main方法中调用该方法

测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法

object Exercise03 {
  def main(args: Array[String]): Unit = {
    val worker = new Worker2
    val manager = new Manager

    showEmployeeAnnual(worker)
    showEmployeeAnnual(manager)
    testEmployee(worker)
    testEmployee(manager)

  }

  def showEmployeeAnnual(e: Employee): Unit = {
    println(e.getAnnual)
  }

  def testEmployee(e: Employee): Unit = {
    if (e.isInstanceOf[Worker2]) e.asInstanceOf[Worker2].work()
    else if (e.isInstanceOf[Manager]) e.asInstanceOf[Manager].manage()
  }
}

abstract class Employee {
  // 定义抽象属性
  var name: String
  var salary: Double

  // 定义抽象方法
  def getAnnual: Double
}

class Worker2 extends Employee {
  override var name: String = "工人"
  override var salary: Double = 2000

  override def getAnnual: Double = {
    this.salary * 12
  }

  def work(): Unit = {
    println("工人工作~")
  }
}

class Manager extends Employee {
  override var name: String = "经理"
  override var salary: Double = 20000.0
  var bonus = 60000

  override def getAnnual: Double = {
    this.salary * 12 + this.bonus
  }

  def manage(): Unit = {
    println("经理在管理~")
  }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机程序猿

觉得写的不错的给小编一点鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值