第 7 章 面向对象编程 (中级部分)

一、包

回顾-Java如何引入包

语法: import  包;
比如 import java.awt.*;
我们引入一个包的主要目的是要使用该包下的类
比如 import java.util.Scanner;  就只是引入一个类Scanner。

回顾-Java包的特点

java中包名和源码所在的系统文件目录结构要一致,并且编译后的字节码文件路径也和包名保持一致。

Scala包的基本介绍

和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些。

1、Scala包的特点概述

基本语法
package 包名

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

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

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

2、Scala包的命名

命名规则:

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

命名规范:

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

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

Scala包注意事项和使用细节

1. scala进行package 打包时,可以有如下形式。
/**
  * 代码说明:
  * 1. package com.lj.scala{} 表示创建了包com.lj.scala在{}中
  * 2. 可以继续写它的子包packagetest,还可以继续写类,特质trait,还可以写object
  * 3. 即Scala支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait和object
  */
package com.lj.scala { // 包:com.lj.scala

    /**
      * @author Administrator
      * @create 2020-03-10 15:12
      */
    package packagetest {  // 包:com.lj.scala.packagetest

        class Person {   // 表示在包:com.lj.scala.packagetest创建类Person

            val name: String  = "Leo"
            
            def play(message: String): Unit = {
                println(this.name + " " + message)
            }
            
        }
        
        object test {   // 表示在包:com.lj.scala.packagetest创建object Test

            def main(args: Array[String]): Unit = {
                println("OK!!!")
            }
            
        }

    }

}
2. 包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用
第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,
这样就非常灵活了;
3. 作用域原则:可以直接向上访问。即: Scala中子包中直接访问父包中的内容, 大括号体现作用域;
4. 父包要访问子包的内容时,需要import对应的类等
package com.ali{
    //引入在com.ali包中希望使用到子包的类Tiger,因此需要引入.
    import com.ali.scala.Tiger
    //这个类就是在com.ali包下
    class User{
    }
    package scala {
        //Tiger在com.ali.scala包中
        class Tiger {
        }
    }
    object Test2 {
        def main(args: Array[String]): Unit = {
            //如果要在父包使用到子包的类,需要import (import com.ali.scala.Tiger)
            val tiger = new Tiger()
            println("tiger=" + tiger)
        }
    }
}
5. 可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层);
6. 包名可以相对也可以绝对:
比如,访问BeanProperty的绝对路径是:_root_.scala.beans.BeanProperty,
在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。
package com.ali.scala2
class Manager( var name : String ) {
    //第一种形式
    //@BeanProperty var age: Int = _
    //第二种形式, 和第一种一样,都是相对路径引入
    //@scala.beans.BeanProperty var age: Int = _
    //第三种形式, 是绝对路径引入,可以解决包名冲突
    @_root_. scala.beans.BeanProperty var age: Int = _
}
object TestBean {
    def main(args: Array[String]): Unit = {
        val m = new Manager("jack")
        println("m=" + m)
    }
}

4、包对象

基本介绍

	包可以包含类、对象和特质trait,但不能包含函数/方法或变量的定义。这是Java虚拟机的局限。
为了弥补这一点不足,scala提供了包对象的概念来解决这个问题。
package com.ali {
    //每个包都可以有一个包对象。你需要在父包(com.ali)中定义它,且名称与子包一样。
    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 就是包对象scala中声明的sayOk方法
                sayOk()
            }
        }
        object TestObj {
            def main(args: Array[String]): Unit = {
                val t  = new Test()
                t.test()
                //因为TestObje和scala这个包对象在同一包,因此也可以使用name属性。
                println("name=" + name)
            }
        }
    }
}

二、包的可见性

1、Scala中包的可见性介绍

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

示例代码:

package com.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 16:43
  */
object PackTest01 {

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

        val p1 = new People01
        p1.showInfo()
        People01.test(p1)

    }

}

class People01 {
    var name = "Jack"
    private var sal: Double = 999.89

    def showInfo(): Unit = {
        println("name=" + name + "--sal=" + sal)
    }
}

object People01 {

    def test(p: People01): Unit = {
        // 这里体现出在伴生对象中,可以访问p.sal
        println("test() name=" + p.name + " sal= " + p.sal)
    }

}
=====================运行结果===========================
name=Jack--sal=999.89
test() name=Jack sal= 999.89
=====================运行结果===========================

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.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 16:43
  */
object PackTest01 {

    def main(args: Array[String]): Unit = {
    
        val p2 = new People02
        p2.showInfo()

    }

}

class People02 {
    var name = "Jack"
    /**
      * 增加包访问权限后
      * 1.private同时起作用。不仅同类可以使用;
      * 2. 同时com.lj.scala.packagetest中包下其他类也可以使用。
      * /
    private[packagetest] var sal: Double = 999.89

    def showInfo(): Unit = {
        println("p2: name=" + name + "--sal=" + sal)
    }
    
}
=====================运行结果===========================
p2: name=Jack--sal=999.89
=====================运行结果===========================

三、包的引入

1、Scala引入包基本介绍

	Scala引入包也是使用import, 基本的原理和机制和Java一样,但是Scala中的import功能更加强
大,也更灵活。
	因为Scala语言源自于Java,所以java.lang包中的类会自动引入到当前环境中,而Scala中的
scala包和Predef包的类也会自动引入到当前环境中,即其下面的类可以直接使用。
	如果想要把其他包中的类引入到当前环境中,需要使用import语言。

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

1. 在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直
延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范
围,提高效率;
2. Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下 _;
		例如:import scala.collection.mutable._
3. 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号);
		例如:import scala.collection.mutable.{HashMap, HashSet}
4. 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分;
		例如:import java.util.{ HashMap=>JavaHashMap, List}
				  import scala.collection.mutable._
				  var map = new HashMap() // 此时的HashMap指向的是scala中的HashMap
				  var map1 = new JavaHashMap(); // 此时使用的java中hashMap的别名
5. 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。
		//  含义为 引入java.util包的所有类,但是忽略HahsMap类。
		import java.util.{ HashMap=>_, _}
		//  此时的HashMap指向的是scala中的HashMap, 而且idea工具,的提示也不会显示java.util的
		//  HashMaple 
		var map = new HashMap() 

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

如何理解抽象

	定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板),
这种研究问题的方法称为抽象。

基本介绍

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

《封装》

1、封装介绍
	封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的
其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
2、封装的理解和好处
	隐藏实现细节
	也可以对数据进行验证,保证安全合理
3、如何体现封装
	对类中的属性进行封装
	通过成员方法,包实现封装
4、封装的实现步骤
1. 将属性进行私有化;
2. 提供一个公共的set方法,用于对属性判断并赋值
			def  setXxx(参数名 : 类型) : Unit = {
					//加入数据验证的业务逻辑
					属性 = 参数名
			}
3. 提供一个公共的get方法,用于获取属性的值
			def getXxx() [: 返回类型] = {
					return 属性
			}

封装示例代码:

package com.lj.scala.packagetest

import scala.io.StdIn

/**
  * @author Administrator
  * @create 2020-03-10 17:26
  */
object PackTest02 {

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

        val p1 = new People03
        var  i = 0
        var age: Int = 0
        while (i < 3) {
            print("请输入年龄:")
            age = StdIn.readInt()
            p1.setAge(age)
            i += 1
        }

    }

}

class People03 {

    var name: String = _
    private var age: Int = 0
    private var salary: Double = 0.00

    def setAge(age: Int): Unit = {
        if (age > 18 && age < 80) {
            this.age = age
            println("输入数值合理,可以进行查看!!!")
        } else {
            println("输入的数值不合理!!!")
        }
    }

}
=====================运行结果===========================
请输入年龄:13
输入的数值不合理!!!
请输入年龄:19
输入数值合理,可以进行查看!!!
请输入年龄:22
输入数值合理,可以进行查看!!!
=====================运行结果===========================
5、Scala封装的注意事项和细节
1. Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果
属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修
饰符,那么自动生成的setter/getter方法是public的;
2. 因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修
饰符) 不用写专门的get,set方法,默认会创建,访问时,直接对象.变量。这样也是为了保持访问
一致性;
3. 从形式上看p1.name 直接访问属性,其实底层仍然是访问的方法;
4. 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射。

《继承》

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

Scala继承的基本语法

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

Scala继承示例代码:

package com.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 17:44
  */
object PackTest03 {

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

        val stu01 = new Student04
        // 子类Student04可以访问父类People04的属性和方法
        stu01.name = "Leo"
        stu01.showInfo("Jack", 22)

    }

}

class People04 {

    var name: String = _
    var age: Int = _

    def showInfo(name: String, age: Int): Unit = {
        println("信息:name=" + name + ",age=" + age)
    }

}

class Student04 extends People04 {
    var id: Int = _

    def play(message: String): Unit = {
        println("他在玩王者荣耀!!!")
    }
}
=====================运行结果===========================
信息:name=Jack,age=22
=====================运行结果===========================

Scala子类继承了什么,怎么继承了?

子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问。
2、重写方法
说明: scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字。

示例代码:

package com.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 17:44
  */
object PackTest03 {

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

        val stu01 = new Student04
        // 子类Student04可以访问父类People04的属性和方法
        stu01.name = "Leo"
        stu01.showInfo("Jack", 22)

    }

}

class People04 {

    var name: String = _
    var age: Int = _

    def showInfo(name: String, age: Int): Unit = {
        println("信息:name=" + name + ",age=" + age)
    }

    def sayHello(): Unit = {
        println("Say Hello!!!")
    }

}

class Student04 extends People04 {
    var id: Int = _

    def play(message: String): Unit = {
        println("他在玩王者荣耀!!!")
    }

    override def showInfo(name: String, age: Int): Unit = {
        println("重写父类的方法!")
        // 子类调用超类的方法使用super关键字
        super.sayHello()
    }

}
=====================运行结果===========================
重写父类的方法!
Say Hello!!!
=====================运行结果===========================
3、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类型。

示例代码:

package com.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 21:18
  */
object PackTest04 {

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

        val stu01 = new Student05;
        // 使用classOf获取类名
        println("stu01的类名:" + classOf[Student05])
        // 使用反射的机制获取类名
        println("stu01的类名:" + stu01.getClass.getName)
        // 测试isInstanceOf 与 asInstanceOf
        var p01 = new People05("Jack", "男", 13)
        p01.showInfo()
        // 将子类引用给父类(向上转型,自动)
        p01 = stu01
        p01.showInfo()
        // 将父类的引用重新转成子类引用(多态),即向下转型
        val stu02 = p01.asInstanceOf[Student05]
        stu02.showInfo()

    }

}

class People05 {

    var name: String = _
    var sex: String = _
    var age: Int = _

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

    def showInfo(): Unit = {
        println(s"show info:name=$name, sex=$sex, age=$age.")
    }

}

class Student05 extends People05 {

    var ID: Int = 1000010

    def play(name: String, games: String): Unit = {
        println(name + "在玩" + games)
    }

}
=====================运行结果===========================
stu01的类名:class com.lj.scala.packagetest.Student05
stu01的类名:com.lj.scala.packagetest.Student05
show info:name=Jack, sex=, age=13.
show info:name=null, sex=null, age=0.
show info:name=null, sex=null, age=0.
=====================运行结果===========================
4、Scala中超类的构造

回顾-Java中超类的构造

示例代码:

class A {
    public A() {
        System.out.println("A()");
    }
    public A(String name) {
        System.out.println("A(String name)" + name);
    }
}
class B extends A{
    public B() {
        //这里会隐式调用super(); 就是无参的父类构造器A()
        System.out.println("B()");
    }
    public B(String name) {
        super(name);   // 调用父类的构造器
        System.out.println("B(String name)" + name);
    }
}

说明:

	从代码可以看出:在Java中,创建子类对象时,子类的构造器总是去调用一个父类的构造
器(显式或者隐式调用)。

Scala超类的构造说明

1. 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是
间接调用);

示例代码:

class Person {
    
    var name = "Jack"
    println("Person...")
    
}

class Emp extends Person {
    
    println("Emp ....")
    
    def this(name : String) {
        
        this // 必须调用主构造器
        this.name = name
        println("Emp 辅助构造器~")
        
    }
}
2. 只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的
构造器中,不能调用super(params);

示例代码:

class Person(name: String) { //父类的构造器
    
}


class Emp (name: String) extends Person(name) {
// 将子类参数传递给父类构造器,这种写法√
// 可以将子类的参数name传递给父类的name

    // super(name)  (×) 没有这种语法
    
    def  this() {
        
        super("abd") // (×)不能在辅助构造器中调用父类的构造器
        
    }
    
}
5、覆写字段

基本介绍

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

回顾

在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写。

原因:
	这个主要涉及到java里面一个字段隐藏的概念,父类和子类定义了一个同名的字段,不会报错。
但对于同一个对象,用父类的引用去取值(字段),会取到父类的字段的值,用子类的引用去取值(
字段),则取到子类字段的值。在实际的开发中,要尽量避免子类和父类使用相同的字段名,否则
很容易引入一些不容易发现的bug。

回顾-Java另一重要特性: 动态绑定机制

1. 当调用对象方法的时候,该方法会和该对象的内存地址绑定;
2. 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用。

示例代码:

package com.lj.scala.packagetest;

/**
 * @author Administrator
 * @create 2020-03-10 22:11
 */
public class JavaDaynamicBind {

    public static void main(String[] args) {

        /**
         * 1. 当调用对象方法的时候,该方法会和该对象的内存地址绑定;
         * 2. 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用。
         */
        
        AA aa = new BB();
        System.out.println("值:" + aa.sum());   // 40 --> 屏蔽子类BB中的sum方法 // 30
        System.out.println("值:" + aa.sum1());  // 30 --> 屏蔽子类BB中的sum1方法 // 20

    }

}

class AA {

    public int i = 10;

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }

    public int getI() {
        return i;
    }
}

class BB extends AA {

    public int i = 20;

    /*
    public int sum() {
        return i + 20;
    }
     */

    public int getI() {
        return i;
    }

    /*
    public int sum1() {
        return i + 10;
    }
     */

}

Scala覆写字段注意事项

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

示例代码:

package com.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 22:28
  */
object PackTest06 {

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



    }

}

class SmallCar {

    var name: String = _
    val color: String = "黑色"

    def run(): String = {
        println("running......")
        "running......"
    }

    def info(name: String): Unit ={
        println("这个车开起来很舒服!")
    }

}

class SmallBMW extends SmallCar {

    // 重写val属性
    override val color: String = "白色"
    // 重写不带参数的def run()
    override def run(): String = "跑的飞快!!!"

    // 带参数的def重写
    override def info(name: String): Unit = {
        println("这个车开起来很省油")
    }

}

// var只能重写另一个抽象的var属性
abstract class A01 {

    // 抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类中
    var name: String  // 抽象属性
}

class B01 extends A01{

    /**
      * 说明:
      * 1. 如果我们在子类中去重写父类的抽象属性,本质是实现了抽象方法;
      * 2. 因此我们也可写override,也可以不写。
      */
    override var name: String = "金毛"
    override var age: Int = 2
}

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

基本介绍

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

抽象类基本语法

示例代码:

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

说明:

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

Scala抽象类使用的注意事项和细节

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

基本介绍

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

回顾-java匿名子类使用

package com.lj.scala.packagetest;

/**
 * @author Administrator
 * @create 2020-03-10 23:07
 */
public class NoNameTest {

    public static void main(String[] args) {

        // 创建一个匿名子类对象
        A05 a05 = new A05() {
            @Override
            public void cry() {
                System.out.println("cry....");
            }
        };
        a05.cry();

    }

}

abstract class A05 {
    abstract public void cry();
}
=====================运行结果===========================
cry....
=====================运行结果===========================

scala匿名子类示例代码:

package com.lj.scala.packagetest

/**
  * @author Administrator
  * @create 2020-03-10 23:12
  */
object ScalaNoName {

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

        val monster = new Monster {
            override var name: String = "白骨精"

            override def cry(): Unit = {
                println(name + "嗷嗷叫!!!")
            }
        }

        monster.cry()

    }

}

abstract class Monster {

    // 抽象属性
    var name: String
    // 抽象方法
    def cry()
}
=====================运行结果===========================
白骨精嗷嗷叫!!!
=====================运行结果===========================

学习来自:北京尚学堂韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!

发布了24 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/XuanAlex/article/details/104778550