[toc]
##
> Scala语言是面向对象的:
>
> 1. Java是面向对象的编程语言,由于历史原因,`Java中还存在着非面向对象的内容:基本类型 ,null,静态方法等。`
> 2. Scala语言来自于Java,所以天生就是面向对象的语言,而且==Scala是纯粹的面向对象的语言,即在Scala中,一切皆为对象。==
> 3. 在面向对象的学习过程中可以对比着Java语言学习。
### 1. 类与对象
> 1. `类`是抽象的,概念的,代表一类事物,比如人类,猫类等
> 2. `对象`是具体的,实际的,代表一个具体事物
> 3. `类`是对象的模板,`对象`是类的一个个体,对应一个实例
> 4. Scala中类和对象的区别和联系和Java是一样的。
~~~~scala
/**
* @Date 2021/3/25 17:29
* @Version 10.21
* @Author DuanChaojie
*/
object OopCatDemo {
def main(args: Array[String]): Unit = {
val cat = new Cat
/**
* 说明
* 1. cat.name = "小白" 其实不是直接访问属性,而是 cat.name_$eq("小白")
* 2. cat.name 等价于 cat.name()
*/
cat.name = "小白"
cat.age = 10
cat.color = "白色"
printf("\n小猫的信息如下: %s %d %s", cat.name, cat.age, cat.color)
}
}
/**
* 定义一个类Cat
* 一个class Cat 对应的字节码文件只有一个 Cat.class ,默认是public
*/
class Cat {
/**
* 定义/声明三个属性
* 说明
* 1. 当我们声明了 var name :String时, 在底层对应 private name
* 2. 同时会生成 两个public方法 name() <=类似=> getter public name_$eq() => setter
*/
var name: String = "" //给初始值
var age: Int = _ // _ 表示给age 一个默认的值 ,如果Int 默认就是0
var color: String = _ // _ 给 color 默认值,如果String ,默认是就是null
}
/** 反编译后得到的结果如下:
* public class Cat{
* private String name = "";
* private int age;
* private String color;
* *
* public String name(){return this.name;}
* public void name_$eq(String x$1) { this.name = x$1; }
* *
* public int age() { return this.age; }
* public void age_$eq(int x$1) { this.age = x$1; }
* *
* public String color() { return this.color; }
* public void color_$eq(String x$1) { this.color = x$1; }
* }
*/
~~~~
> Scala如何定义类:
>
> - ~~~~scala
> [修饰符] class 类名 {
> 类体
> }
> ~~~~
>
> Scala定义类的注意事项:
>
> 1. Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public),[修饰符在后面再详解]。
> 2. 一个Scala源文件可以包含多个类。
#### 属性
> 属性是类的一个组成部分,一般是值数据类型,也可是引用类型。比如我们前面定义猫类 的 age 就是属性。
>
> 属性注意事项和细节说明:
>
> 1. 属性的定义语法同变量,示例:`[访问修饰符] var 属性名称 [:类型] = 属性值`
>
> 2. 属性的定义类型可以为任意类型,包含值类型或引用类型
>
> 3. Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略,==这点和Java不同==。
>
> 4. 如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型。
>
> 5. 如果在定义属性时,暂时不赋值,也可以使用符号`_`(下划线),让系统分配默认值。
>
> ![image-20210325222129832](assets/image-20210325222129832.png)
>
> 6. 不同对象的属性是独立,互不影响,一个对象对属性的更改,不影响另外一个。这点和java完全一样
>
> 7. 属性的高级部分:属性的高级部分和构造器(构造方法/函数) 相关,我们把属性高级部分放到构造器那里讲解。
#### 方法
> Scala中的方法其实就是函数,声明规则请参考函数式编程中的函数声明。
~~~~scala
def 方法名(参数列表) [:返回值类型] = {
方法体
}
~~~~
> 给DogSon类添加cal方法,可以计算两个数的和
~~~~scala
/**
* @Date 2021/3/25 18:48
* @Version 10.21
* @Author DuanChaojie
*/
object MethodDemo01 {
def main(args: Array[String]): Unit = {
val dogSon = new DogSon
println(dogSon.cal(10,11))
}
}
class DogSon {
//属性
private var sal: Double = _
var food: String = _
//方法
def cal(n1: Int, n2: Int): Int = {
n1 + n2
}
}
~~~~
> 方法的调用机制原理:
>
> 1. 当我们Scala开始执行时,先在栈区开辟一个main栈。main栈是最后被销毁
> 2. 当Scala程序在执行到一个方法时,总会开一个新的栈。
> 3. 每个栈是独立的空间,变量(基本数据类型)是独立的,相互不影响(引用类型除外)
> 4. 当方法执行完毕后,该方法开辟的栈就会被jvm机回收。
>
> 课堂练习题:
~~~~scala
/**
* @Date 2021/3/25 18:56
* @Version 10.21
* @Author DuanChaojie
*/
object MethodExercise {
def main(args: Array[String]): Unit = {
method01()
val matrix = new Matrix
matrix.width = 2.2
matrix.length = 2.51
val res1 = matrix.method()
println(res1.formatted("%.2f"))
method02(4, 2)
val res2 = method03(4.22, 3.11)
println(res2.formatted("%.2f"))
val res3 = method05(9, 3, '/')
println(res3)
}
/**
* 编程一个方法,方法不需要参数,
* 在方法中打印一个 10*8 的矩形,在main方法中调用该方法。
*/
def method01(): Unit = {
for (i <- 1 to 10) {
for (j <- 1 to 8) {
print("*")
}
println()
}
}
def method02(m: Int, n: Int): Unit = {
for (i <- 1 to m) {
for (j <- 1 to n) {
print("*")
}
println()
}
}
/**
* 修改上一个程序,编写一个方法,提供m和n两个参数,
* 方法中打印一个m*n的矩形,再编写一个方法算该矩形的面积(可以接收长length,和宽width),
* 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。
*
* @param width
* @param length
* @return
*/
def method03( Double, length: Double): Double = {
width * length
}
/**
* 判断一个数是奇数odd还是偶数
*/
def method04(n: Int): Unit = {
if (n % 2 == 0) {
println("是偶数")
} else {
println("是奇数")
}
}
/**
* 定义小小计算器类(Calcuator),实现加减乘除四个功能
* 用一个方法搞定
*/
def method05(m: Int, n: Int, oper: Char): Int = {
if (oper == '+') {
m + n
} else if (oper == '-') {
m - n
} else if (oper == '*') {
m * n
} else {
m / n
}
}
}
class Matrix {
var length: Double = _
var Double = _
/**
* 修改上一个程序,编写一个方法中,方法不需要参数,计算该矩形的面积,
* 并将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印(结果保留小数点2位)。
*/
def method(): Double = {
length * width
}
}
~~~~
#### 创建对象
> ~~~scala
> val | var 对象名 [:类型] = new 类型()
> ~~~
>
> 1. 如果我们不希望改变对象的引用(即:内存地址),`应该声明为val 性质的,否则声明为var, Scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。`
> 2. Scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,`但当类型和后面new 对象类型有继承关系即多态时,`就必须写了
~~~~scala
/**
* @Date 2021/3/25 18:33
* @Version 10.21
* @Author DuanChaojie
*/
object OopCreateObj {
def main(args: Array[String]): Unit = {
// emp1类型就是Emp
val emp1 = new Emp
// 如果我们希望将子类对象,交给父类的引用,这时就需要写上类型
val emp2: Person = new Emp
}
}
class Person {
}
class Emp extends Person {
}
~~~~
#### 类与对象应用实例
> 小狗案例:
>
> 1. 编写一个Dog类,包含name(String)、age(Int)、weight(Double)属性。
> 2. 类中声明一个say方法,返回String类型,方法返回信息中包含所有属性值。
> 3. 在另一个TestDog类中的main方法中,创建Dog对象,并访问say方法和所有属性,将调用结果打印输出。
~~~~scala
/**
* @Date 2021/3/25 19:41
* @Version 10.21
* @Author DuanChaojie
*/
object DogCaseTest {
def main(args: Array[String]): Unit = {
val dog = new Dog
dog.name = "tomcat"
dog.age = 2
dog.weigth = 6
println(dog.say())
}
}
/**
* 小狗案例:
* 编写一个Dog类,包含name(String)、age(Int)、weight(Double)属性
* 类中声明一个say方法,返回String类型,方法返回信息中包含所有属性值。
* 在另一个DogCaseTest类中的main方法中,创建Dog对象,并访问say方法和所有属性,将调用结果打印输出。
*/
class Dog {
var name = ""
var age = 0
var weigth = 0.0
def say(): String = {
"小狗信息如下: name=" + this.name + "\t age=" + this.age + "\t weight=" + this.weigth
}
}
~~~~
#### 类和对象的内存分配策略
~~~~scala
/**
* @Date 2021/3/25 18:37
* @Version 10.21
* @Author DuanChaojie
*/
object OopMemState {
def main(args: Array[String]): Unit = {
val p1 = new PersonTo
p1.name = "jack"
p1.age = 10
var p2 = p1
println(p1.toString)
p2.name = "tom"
println(p1.toString)
}
}
class PersonTo {
var name = ""
// 如果是使用 _ 方式给默认值,则属性必须指定类型
var age: Int = _
override def toString: String = {
"name = " + name + "\nage = " + age
}
}
~~~~
![image-20210325223241221](assets/image-20210325223241221.png)
### 2. 构造器
> 1. 前面我们在创建Person的对象时,是先把一个对象创建好后,再给他的年龄和姓名属性赋值,如果现在我要求,在创建人类的对象时,就直接指定这个对象的年龄和姓名,该怎么做? 这时就可以使用构造方法/构造器。
> 2. `构造器(constructor)又叫构造方法,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。`
#### 回顾Java构造器
~~~~java
[修饰符] 方法名(参数列表){
构造方法体
}
~~~~
> Java构造器的特点:
>
> 1. 在Java中一个类可以定义多个不同的构造方法,构造方法重载
> 2. 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造器),比如 Person (){}
> 3. 一旦定义了自己的构造方法(构造器),默认的构造方法就覆盖了,就不能再使用默认的无参构造方法,除非显示的定义一下,即: Person(){};
>
> Java构造器的案例:
>
> 1. Java构造器的案例在前面定义的Person类中添加两个构造器:
> 2. 第一个无参构造器:利用构造器设置所有人的age属性初始值都为18
> 3. 第二个带name和age两个参数的构造器:使得每次创建Person对象的同时初始化对象的age属性值和name属性值。
~~~~java
class Person{
public String name;
public int age;
public String getInfo(){
return name+"\t"+age;
}
public Person(){
age = 18;
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
}
~~~~
#### Scala构造器
> 1. 和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法(即scala中构造器也支持重载)。
>
> 2. Scala类的构造器包括: ==主构造器 和 辅助构造器==
>
> 3. ~~~~scala
> class 类名(形参列表) { // 主构造器
>
> // 类体
>
> def this(形参列表) { // 辅助构造器
> }
>
> def this(形参列表) { //辅助构造器可以有多个...
> }
> }
> ~~~~
>
> 4. 辅助构造器 函数的名称this, 可以有多个,编译器通过不同参数来区分。
>
> 5. Scala构造器的快速入门
>
> - 创建PersonA对象的同时初始化对象的age属性值和name属性值。
~~~~scala
/**
* @Date 2021/3/25 20:16
* @Version 10.21
* @Author DuanChaojie
*/
object ConstructorDemo01 {
def main(args: Array[String]): Unit = {
//val person = new PersonA("tom",99)//不会走主构造器
val person = new PersonA("tom")
println(person)
val a1 = new A
val a2 = new A()
}
}
/**
* 构造器的快速入门
* 创建Person对象的同时初始化对象的age属性值和name属性值
*/
class PersonA(inName: String, inAge: Int) {
var name: String = inName
var age: Int = inAge
age += 10
println("age=" + age)
def this(name: String) {
//辅助构造器,必须在第一行显式调用主构造器(可以是直接,也可以是间接)
this("jack", 10)
println("------------------")
//this
this.name = name //重新赋值
}
//重写了toString,便于输出对象的信息
override def toString: String = {
"name=" + this.name + "\t age" + this.age
}
}
class A() {
}
~~~~
> Scala构造器注意事项和细节:
>
> 1. Scala构造器作用是完成对新对象的初始化,构造器没有返回值。
> 2. 主构造器的声明直接放置于类名之后 [反编译]。
> 3. 主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别【案例演示+反编译】
> 4. 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。
```scala
/**
* @Date 2021/3/25 21:47
* @Version 10.21
* @Author DuanChaojie
*/
object ConstructorDemo02 {
def main(args: Array[String]): Unit = {
val a = new AA("jack")
//输出的顺序是
//1. BB~~~ 父类
//2. AA~~~ 主构造器
//3. AA this(name:String) 辅助构造器
}
}
class BB() {
println("BB~~~")
}
class AA() extends BB() {
println("AA~~~")
def this(name: String) {
this
println("AA this(name:String)")
}
}
```
> 5. ==辅助构造器名称为this(这个和Java是不一样的)==,多个辅助构造器通过不同参数列表进行区分, 在底层就是f构造器重载。
```scala
/**
* @Date 2021/3/25 21:50
* @Version 10.21
* @Author DuanChaojie
*/
object ConstructorDemo03 {
def main(args: Array[String]): Unit = {
val person = new PersonT("jack")
person.showInfo()
}
}
/**
* 定义了一个PersonT类
* PersonT 有几个构造器?四个
*/
class PersonT private() {
var name: String = _
var age: Int = _
def this(name: String) {
//辅助构造器无论是直接或间接,最终都一定要调用主构造器,执行主构造器的逻辑
//而且需要放在辅助构造器的第一行
// [这点和java一样,java中一个构造器要调用同类的其它构造器,也需要放在第一行]
this() //直接调用主构造器
this.name = name
}
//辅助构造器
def this(name: String, age: Int) {
this() //直接调用主构造器
this.name = name
this.age = age
}
def this(age: Int) {
this("匿名") //调用主构造器,因为 def this(name : String) 中调用了主构造器!
this.age = age
}
def showInfo(): Unit = {
println("PersonT信息如下:")
println("name=" + this.name)
println("age=" + this.age)
}
}
```
> 6. 如果想让主构造器变成私有的,`可以在()之前加上private`,这样用户只能通过辅助构造器来构造对象了【反编译】
> 7. 辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)
### 3. 属性高级
前面我们讲过属性了,这里我们再对属性的内容做一个加强。
#### 构造器参数
> 1. Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
> 2. 如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用 【案例+反编译】
> 3. 如果参数使用var关键字声明,那么那么Scala会将参数作为类的成员属性使用,`并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法`,即这时的成员属性是私有的,但是可读写。
```scala
/**
* @Date 2021/3/25 21:54
* @Version 10.21
* @Author DuanChaojie
*/
object ConstructorDemo04 {
def main(args: Array[String]): Unit = {
val worker1 = new Worker("smith1")
//不能访问 inName
//println(worker1.inName)
val worker2 = new Worker2("smith2")
//可以访问 inName
println(worker2.inName)
val worker3 = new Worker3("smith3")
worker3.inName = "mary"
println(worker3.inName)
}
}
//1. 如果 主构造器是Worker(inName: String) ,那么 inName就是一个局部变量
class Worker(inName: String) {
var name = inName
}
//2.如果 主构造器是Worker2(val inName: String) ,那么 inName就是Worker2的一个private的只读属性
class Worker2(val inName: String) {
var name = inName
}
//3.如果 主构造器是Worker3(var inName: String) ,那么 inName就是Worker3的一个
// 一个private 的可以读写属性
class Worker3(var inName: String) {
var name = inName
}
```
#### Bean属性
> 1. JavaBeans规范定义了Java的属性是像getXxx()和setXxx()的方法。许多Java工具(框架)都依赖这个命名习惯。为了Java的互操作性。`将Scala字段加@BeanProperty时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。`
> 2. 注意:给某个属性加入@BeanPropetry注解后,会生成getXXX和setXXX的方法
> 3. `并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存。`
```scala
import scala.beans.BeanProperty
/**
* @Date 2021/3/25 22:05
* @Version 10.21
* @Author DuanChaojie
*/
object BeanPropertDemo {
def main(args: Array[String]): Unit = {
val car = new Car
car.name = "宝马"
println(car.name)
//使用 @BeanProperty 自动生成 getXxx 和 setXxx
car.setName("奔驰")
println(car.name)
println(car.getName())
}
}
class Car {
@BeanProperty var name: String = null
}
```
### 4. Scala对象的创建流程
~~~~scala
class Person {
var age: Short = 90
var name: String = _
def this(n: String, a: Int) {
this()
this.name = n
this.age = a
}
}
//var p : Person = new Person("小倩",17)
~~~~
> 流程分析(面试题-写出)
>
> 1. 加载类的信息(属性信息,方法信息)
> 2. 在内存中(堆)开辟空间
> 3. 使用父类的构造器(主和辅助)进行初始
> 4. 使用主构造器对属性进行初始化 【age:90, naem null】
> 5. 使用辅助构造器对属性进行初始化 【 age:17, naem 小倩 】
> 6. 将开辟的对象的地址赋给 p 这个引用。
### 5. Scala包详解☆
#### 回顾Java中的包
> 现在有两个程序员共同开发一个项目,程序员xiaoming希望定义一个类取名 Dog ,程序员xiaoqiang也想定义一个类也叫 Dog。两个程序员为此还吵了起来,怎么办?
>
> 使用Java包解决问题:
>
> 1. Java包的三大作用
>
> 1. 区分相同名字的类
> 2. 当类很多时,可以很好的管理类
> 3. 控制访问范围
>
> 2. Java打包命令
>
> 1. 打包基本语法:package com.atguigu;
>
> 3. Java如何引入包
>
> 1. 语法: import 包
>
> 2. ~~~java
> import java.awt.*;
> ~~~
>
> 3. 我们引入一个包的主要目的是要使用该包下的类
>
> 4. `比如 import java.util.Scanner; 就只是引入一个类Scanner。`
>
> 4. Java包的特点
>
> 1. java中包名和源码所在的系统文件目录结构要一致,并且编译后的字节码文件路径也和包名保持一致。
>
> 5. 打包的本质分析
>
> 1. 实际上就是创建不同的文件夹来保存类文件
> 2. 使用打包技术来解决上面的问题,不同包下Dog类
>
> 小红的老虎
~~~~java
package com.atguigu.chapter07.javapackage.xh;
/**
* @Date 2021/3/27 17:56
* @Version 10.21
* @Author DuanChaojie
*/
public class Tiger {
public String name = "xh--Tiger";
}
~~~~
> 小明的老虎
~~~~java
package com.atguigu.chapter07.javapackage.xm;
/**
* @Date 2021/3/27 17:56
* @Version 10.21
* @Author DuanChaojie
*/
public class Tiger {
public String name = "xm--Tiger";
}`
~~~~
> 测试
~~~java
package com.atguigu.chapter07.javapackage;
import com.atguigu.chapter07.javapackage.xh.Tiger;
/**
* @Date 2021/3/27 17:57
* @Version 10.21
* @Author DuanChaojie
*/
public class TestTiger {
public static void main(String[] args) {
// xh的Tiger
Tiger tiger1 = new com.atguigu.chapter07.javapackage.xh.Tiger();
// xm的Tiger
com.atguigu.chapter07.javapackage.xm.Tiger tiger2 = new com.atguigu.chapter07.javapackage.xm.Tiger();
//xh--Tiger
System.out.println(tiger1.name);
//xm--Tiger
System.out.println(tiger2.name);
}
}
~~~
#### Scala包入门
> 和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项。
>
> Scala包快速入门
>
> - 使用打包技术来解决上面的问题,不同包下Dog类
>
> 小红的老虎
~~~~scala
package com.atguigu.chapter07.scalapackage.xh
/**
* @Date 2021/3/27 18:00
* @Version 10.21
* @Author DuanChaojie
*/
class Tiger {
val name: String = "xh---Tiger"
}
~~~~
> 小明的老虎
~~~scala
package com.atguigu.chapter07.scalapackage.xm
/**
* @Date 2021/3/27 18:01
* @Version 10.21
* @Author DuanChaojie
*/
class Tiger {
val name: String = "xm---Tiger"
}
~~~
> 测试
~~~~scala
package com.atguigu.chapter07.scalapackage
/**
* @Date 2021/3/27 18:02
* @Version 10.21
* @Author DuanChaojie
*/
object TestTiger {
def main(args: Array[String]): Unit = {
val tiger1 = new com.atguigu.chapter07.scalapackage.xh.Tiger()
val tiger2 = new com.atguigu.chapter07.javapackage.xm.Tiger()
//xh---Tiger
println(tiger1.name)
//xm--Tiger
println(tiger2.name)
}
}
~~~~
#### Scala包特点☆
> 1. 基本语法:`package 包名`
> 2. Scala包的四大作用(和Java一样)
> 1. 区分相同名字的类
> 2. 当类很多时,可以很好的管理类
> 3. 控制访问范围
> 4. `可以对类的功能进行扩展`
>
> 3. ==Scala中包名和源码所在的系统文件目录结构要可以不一致==,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)。
>
> 4. 包的命名:
>
> 1. `命名规则:`
> 1. 只能包含数字、字母、下划线、`小圆点.` 但不能用数字开头, 也不要使用关键字。
> 1. demo.class.exec1 //错误 , 因为class是关键字
> 2. demo.12a // 错误,因为不能以数字开头
> 2. `命名规范:`
> 1. `一般是小写字母+小圆点一般是`
> 2. `com.公司名.项目名.业务模块名`,比如:`com.atguigu.oa.controller`
>
> 5. Scala会自动引入常用包
>
> ![image-20210329154208525](assets/image-20210329154208525.png)
#### Scala包使用细节
> 1. scala进行package 打包时,可以有如下形式。
>
> 2. 包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了。
>
> 3. `作用域原则:`可以直接向上访问。即: ==Scala中子包中直接访问父包中的内容, 大括号体现作用域。==(`提示:Java中子包使用父包的类,需要import`)。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可。
>
> 4. 父包要访问子包的内容时,需要import对应的类等
>
> 5. 可以在同一个.scala文件中,声明多个并列的package`(建议嵌套的pakage不要超过3层)`
>
> ~~~~scala
>
> /** 代码说明
> * 1. package com.atguigu{} 表示我们创建了包 com.atguigu ,在{}中
> * 我们可以继续写它的子包 scala //com.atguigu.scala,
> * 还可以写类,特质trait,还可以写object
> * 2. 即sacla支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait和object
> */
> package com.atguigu {
>
> import com.atguigu.scala.Person
> object Test {
> def main(args: Array[String]): Unit = {
> // 父包要访问子包的内容时,需要import对应的类等
> val person = new Person
> println(person.name)
> }
> }
> /**包对象后面详解
> *说明
> * 1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决
> * 2. package object scala 表示创建一个包对象 scala, 他是 com.atguigu.scala这个包对应的包对象
> * 3. 每一个包都可以有一个包对象
> * 4. 包对象的名字需要和子包一样
> * 5. 在包对象中可以定义变量,方法
> * 6. 在包对象中定义的变量和方法,就可以在对应的包中使用
> * 7. 在底层这个包对象会生成两个类 package.class 和 package$.class
> */
> package object scala {
> var name = "king"
>
> def sayHiv(): Unit = {
> println("package object scala sayHI~")
> }
> }
>
> package scala {
> class Person {
> val name: String = "mm"
>
> def eat(): Unit = {
> println(this.name + "吃吃吃~~~")
> }
> }
>
> import com.atguigu.java.Catt
>
> object Test {
> def main(args: Array[String]): Unit = {
> val p = new Person()
> println(p.eat())
> println("Object Test")
> val c = new Catt
> println(c.name)
>
> println(name)//king
> }
> }
>
> }
>
> package java {
>
> class Catt {
> val name = "小甜心"
> }
> package test {
>
> object Test {
> def main(args: Array[String]): Unit = {
> val cat = new Catt
> println(cat.name)
> }
> }
> }
> }
> }
> ~~~~
>
> 6. 包名可以相对也可以绝对,比如,访问BeanProperty的绝对路径是:`_root_. scala.beans.BeanProperty` ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。
>
> ~~~~scala
> package com.atguigu.chapter07.scalapackage
>
> /**
> * @Date 2021/3/27 18:49
> * @Version 10.21
> * @Author DuanChaojie
> */
> object TestBean {
> def main(args: Array[String]): Unit = {
> val m = new Manager("jack")
> println("m.age = " + m.age3)
> }
> }
>
> class Manager(var name: String) {
>
> import scala.beans.BeanProperty
>
> //第一种形式 [使用相对路径引入包]
> @BeanProperty var age: Int = _
> //第二种形式, 和第一种一样,都是相对路径引入
> @scala.beans.BeanProperty var age2: Int = _
> //第三种形式, 是绝对路径引入,可以解决包名冲突
> @_root_.scala.beans.BeanProperty var age3: Int = _
> }
> ~~~~
#### Scala的包对象
包可以包含类、对象和特质trait,`但不能包含函数/方法或变量的定义。`这是Java虚拟机的局限。为了弥补这一点不足,scala提供了包对象的概念来解决这个问题。
~~~~scala
/** 代码说明
* 1. package com.atguigu{} 表示我们创建了包 com.atguigu ,在{}中
* 我们可以继续写它的子包 scala //com.atguigu.scala,
* 还可以写类,特质trait,还可以写object
* 2. 即sacla支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait和object
*/
package com.atguigu {
import com.atguigu.scala.Person
object Test {
def main(args: Array[String]): Unit = {
// 父包要访问子包的内容时,需要import对应的类等
val person = new Person
println(person.name)
}
}
/**包对象后面详解
*说明
* 1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决
* 2. package object scala 表示创建一个包对象 scala, 他是 com.atguigu.scala这个包对应的包对象
* 3. 每一个包都可以有一个包对象
* 4. 包对象的名字需要和子包一样
* 5. 在包对象中可以定义变量,方法
* 6. 在包对象中定义的变量和方法,就可以在对应的包中使用
* 7. 在底层这个包对象会生成两个类 package.class 和 package$.class
*/
package object scala {
var name = "king"
def sayHiv(): Unit = {
println("package object scala sayHI~")
}
}
package scala {
class Person {
val name: String = "mm"
def eat(): Unit = {
println(this.name + "吃吃吃~~~")
}
}
}
}
~~~~
> 包对象的底层实现机制分析:
>
> - 一个包对象会生成两个类package和 package$
> - ![image-20210329160446330](assets/image-20210329160446330.png)
> - 说明了包去使用包对象的变量或者方法的原理
> - 当创建包对象后,在该包下生成 `public final class package 和 public final class package$`
> - 通过 package$ 的一个静态实例完成对包对象中的属性和方法的调用。
>
> 包对象的注意事项:
>
> - 每个包都可以有一个包对象。你需要在父包中定义它。
> - 包对象名称需要和包名一致,一般用来对包的功能补充
#### 包的可见性
##### Java
> java提供四种访问控制修饰符号控制方法和变量的访问权限(范围):
>
> 1. 公开级别:用public 修饰,对外公开
> 2. 受保护级别:用protected修饰,对子类和同一个包中的类公开
> 3. 默认级别:没有修饰符号,向同一个包的类公开
> 4. 私有级别:用private修饰,只有类本身可以访问,不对外公开
>
> ![image-20210329160938962](assets/image-20210329160938962.png)
>
> Java访问修饰符使用注意事项
>
> 1. 修饰符可以用来修饰类中的属性,成员方法以及类
> 2. 只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。
##### Scala
在Java中,访问权限分为: public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
~~~~scala
package com.atguigu.chapter07.scalavisit
/**
* @Date 2021/3/27 19:05
* @Version 10.21
* @Author DuanChaojie
*/
object TestVisit {
def main(args: Array[String]): Unit = {
val clerk = new Clerk
clerk.showInfo()
Clerk.test(clerk)
}
}
/**
* 类
*/
class Clerk {
var name: String = "jack"
private var sal: Double = 9999.9
protected var age = 10
var job: String = "大数据工程师"
def showInfo(): Unit = {
//在本类可以使用私有的
println(" name " + name + " sal= " + sal)
}
}
/**
*当一个文件中出现了 class Clerk 和 object Clerk
* 1. class Clerk 称为伴生类
* 2. object Clerk 的伴生对象
* 3. 因为scala设计者将static拿掉, 他就是设计了 伴生类和伴生对象的概念
* 4. 伴生类 写非静态的内容 伴生对象 就是静态内容
*/
object Clerk {
def test(c: Clerk): Unit = {
//这里体现出在伴生对象中,可以访问c.sal
println("test() name=" + c.name + " sal= " + c.sal)
}
}
~~~~
> Scala中包的可见性和访问修饰符的使用
>
> 1. 当属性访问权限为默认时,从底层看属性是private的,`但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法`,因此从使用效果看是任何地方都可以访问)
> 2. 当方法访问权限为默认时,默认为public访问权限
> 3. `private为私有权限,只在类的内部和伴生对象中可用` 【案例演示】
> 4. ==protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问 (编译器)==
> 5. 在scala中没有public关键字,即不能用public显式的修饰属性和方法。
> 6. 包访问权限(表示属性有了限制。同时包也有了限制),这点和Java不一样,体现出Scala包使用的灵活性
>
> ~~~scala
>
> class Person {
> /**
> * 这里我们增加一个包访问权限
> * 下面private[scalavisit] :
> * 1,仍然是private
> * 2. 在scalavisit包(包括子包)下也可以使用name ,相当于扩大访问范围
> */
> protected[scalavisit] val name = "jack"
> }
> ~~~
#### 包的引入
> 1. Scala引入包也是使用import, 基本的原理和机制和Java一样,但是Scala中的import功能更加强大,也更灵活。
> 2. 因为Scala语言源自于Java,所以`java.lang包中的类会自动引入到当前环境中,而Scala中的scala包和Predef包的类也会自动引入到当前环境中,`即起其下面的类可以直接使用。
> 3. 如果想要把其他包中的类引入到当前环境中,需要使用import
>
> Scala引入包的细节和注意事项:
>
> 1. ==在Scala中,import语句可以出现在任何地方==,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,`缩小import 包的作用范围`,提高效率。
> 2. Java中如果想要导入包中所有的类,`可以通过通配符*,Scala中采用下 _`
> 3. 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
> 4. 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名。
> 5. 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。
~~~scala
package com.atguigu.chapter07.scalapackage
import scala.collection.mutable
/**
* @Date 2021/3/27 19:48
* @Version 10.21
* @Author DuanChaojie
*/
object TestImport {
def main(args: Array[String]): Unit = {
// 可以使用选择器,选择引入包的内容,这里,我们只引入HashMap, HashSet
import scala.collection.mutable.{HashMap, HashSet}
var map = new HashMap()
var set = new HashSet()
}
def testImport1(): Unit = {
// 下面的含义是将java.util.HashMap重命名为JavaHashMap
import java.util.{HashMap => JavaHashMap}
import scala.collection.mutable._
var map = new HashMap() //此时的HashMap指向的是scala中的HashMap
var jMap = new JavaHashMap() //此时使用的java 中hashMap的别名
}
def testImport2(): Unit = {
import java.util.{HashMap=>_,_}
// 含义为引入java.util 包的所有类,但是忽略HahsMap类
var map = new mutable.HashMap()
}
}
~~~
### 6. Scala面向对象编程☆
> 面向对象编程方法—抽象
>
> 那么怎么理解抽象呢?
>
> 我们在前面去定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。
>
> ![image-20210329163201629](assets/image-20210329163201629.png)
~~~~scala
package com.atguigu.chapter07.scalaoop
/**
* @Date 2021/3/27 20:00
* @Version 10.21
* @Author DuanChaojie
*/
object BankDemo {
def main(args: Array[String]): Unit = {
val acc = new Account("1021", 999999, "1015")
acc.query("1015")
val balance = acc.withDraw("1015", 9999)
println("取出后的余额:" + balance)
}
}
/**
* 编写一个Account类
*
* @param inAccount 主构造器形参
* @param inBalance 主构造器形参
* @param inPwd 主构造器形参
*/
class Account(inAccount: String, inBalance: Double, inPwd: String) {
/**
* 属性:账号,余额,密码
* 方法:查询,取款,存款
*/
private val accountNo = inAccount
private var balance = inBalance
private var pwd = inPwd
/**
* 查询账号余额的方法
*
* @param pwd 根据密码进行查询余额
*/
def query(pwd: String): Unit = {
if (!this.pwd.equals(pwd)) {
println("密码错误")
return
}
printf("账号为%s 当前余额是%.2f\n", this.accountNo, this.balance)
}
/**
* 取款的方法
*
* @param pwd 根据密码取钱
* @param money 取多少钱
* @return
*/
def withDraw(pwd: String, money: Double): Any = {
if (!this.pwd.equals(pwd)) {
println("密码错误")
return
}
//判断money是否合理
if (this.balance < money) {
println("余额不足")
return
}
this.balance -= money
this.balance
}
}
~~~~
> ==面向对象编程有三大特征:封装、继承和多态==。下面我们一一为同学们进行详细的讲解。
#### 封装
> `封装(encapsulation)`就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
>
> 封装的理解和好处:
>
> 1. 隐藏实现细节
> 2. 提可以对数据进行验证,保证安全合理
> 3. 同时可以加入业务逻辑
>
> 如何体现封装:
>
> 1. 对类中的属性进行封装
> 2. 通过成员方法,包实现封装
>
> 封装的实现步骤:
>
> ~~~~scala
> //1、将属性进行私有化
> //2、提供一个公共的set方法,用于对属性判断并赋值
> def setXxx(参数名 : 类型) : Unit = {
> //加入数据验证的业务逻辑
> 属性 = 参数名
> }
>
> //3、提供一个公共的get方法,用于获取属性的值
> def getXxx() [: 返回类型] = {
> return 属性
> }
> ~~~~
>
> 快速入门案例:
>
> 那么在Scala中如何实现这种类似的控制呢?请大家看一个小程序(TestEncap.scala),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证[要求1-120之间]
>
> ~~~~scala
> 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
> } else {
> println("输入的数据不合理");
> //可考虑给一个默认值
> this.age = 17
> }
> }
> }
>
> ~~~~
>
> Scala封装的注意事项和细节:
>
> 前面讲的Scala的封装特性,大家发现和Java是一样的,下面我们看看Scala封装还有哪些特点。
>
> 1. Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的[案例+反编译+说明]
>
> ![image-20210329214929806](assets/image-20210329214929806.png)
>
> 2. 因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的get/set,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性 [案例]
>
> 3. 从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法, 看一下反编译的代码就明白
>
> 4. 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射。
#### 继承
> Java继承回顾:
~~~java
class 子类名 extends 父类名 { 类体 }
//子类继承父类的属性和方法
~~~
> 继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类即可。
>
> 和Java一样,Scala也支持类的单继承
~~~scala
//Scala继承的基本语法
class 子类名 extends 父类名 { 类体 }
~~~
> 编写一个Student 继承 Person的案例,体验一下Scala继承的特点:
~~~~scala
package com.atguigu.chapter07.scalaoop
/**
* @Date 2021/3/27 20:18
* @Version 10.21
* @Author DuanChaojie
*/
object Extends01 {
def main(args: Array[String]): Unit = {
val student = new Student
// 调用了student.name() ,是从Person继承过来的
student.name = "dd"
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中....")
}
}
~~~~
> Scala继承给编程带来的便利
>
> 1. 代码的复用性提高了
> 2. 代码的扩展性和维护性提高了【面试官问:当我们修改父类时,对应的子类就会继承相应的方法和属性】
>
> scala子类继承了什么,怎么继承了?
>
> 1. 子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问
~~~~scala
package com.atguigu.chapter07.scalaoop
/**
* @Date 2021/3/27 20:36
* @Version 10.21
* @Author DuanChaojie
*/
object Extends02 {
def main(args: Array[String]): Unit = {
//1. 在scala中,子类继承了父类的所有属性
//2. 但是private的属性和方法无法访问
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 n3() , private n3_$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
}
}
~~~~
##### 重写方法
scala明确规定,==重写一个非抽象方法需要用override修饰符==,调用超类的方法使用super关键字
```scala
package com.atguigu.chapter07.scalaoop
/**
* @Date 2021/3/27 20:44
* @Version 10.21
* @Author DuanChaojie
*/
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()
}
}
```
##### 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类型。
```scala
/**
* @Date 2021/3/27 20:47
* @Version 10.21
* @Author DuanChaojie
*/
object TypeConvert01 {
def main(args: Array[String]): Unit = {
// ClassOf的使用,可以得到类名
println(classOf[String])
val str = "king"
// 使用反射机制
println(str.getClass.getName)
// isInstanceOf asInstanceOf
var person = new Person200
var emp = new Emp200
//将子类引用给父类(向上转型,自动)
person = emp
//将父类的引用重新转成子类引用(多态),即向下转型
var emp200 = person.asInstanceOf[Emp200]
emp200.sayHello()
}
}
/**
* Person200类
*/
class Person200 {
var name: String = "tom"
def printName() { //输出名字
println("Person printName() " + name)
}
def sayHi(): Unit = {
println("sayHi...")
}
}
/**
* 这里我们继承Person200
*/
class Emp200 extends Person200 {
//这里需要显式的使用override
override def printName() {
println("Emp printName() " + name)
//在子类中需要去调用父类的方法,使用super
super.printName()
sayHi()
}
def sayHello(): Unit = {
println("say hello ~~")
}
}
```
> 最佳实践:
>
> 类型检查和转换的最大价值在于:可以判断传入对象的类型,然后转成对应的子类对象,进行相关操作,这
>
> 里也体现出多态的特点。
```scala
package com.atguigu.chapter07.scalaoop
/**
* @Date 2021/3/27 20:55
* @Version 10.21
* @Author DuanChaojie
*/
object TypeConvert02 {
def main(args: Array[String]): Unit = {
val stu = new Student400
val emp = new Emp400
convert(stu)
convert(emp)
}
/**
* 写了一个参数多态代码
* 因为在oop中一个父类的引用可以接收所有子类的引用,多态(参数多态)
*/
def convert(person: Person400): Unit = {
//使用Scala中类型检查和转换
if (person.isInstanceOf[Emp400]) {
//person.asInstanceOf[Emp400],对p的类型没有任何变化,而是返回的是Emp400
person.asInstanceOf[Emp400].showInfo()
} else if (person.isInstanceOf[Student400]) {
person.asInstanceOf[Student400].cry()
} else {
println("转换失败~")
}
}
}
/**
* Person400
*/
class Person400 {
def printName(): Unit = {
println("Person400 printName")
}
def sayOk(): Unit = {
println("Person400 sayOk")
}
}
/**
* Student400
*/
class Student400 extends Person400 {
val stuId = 100
override def printName(): Unit = {
println("Student400 printName")
}
def cry(): Unit = {
println("学生的id=" + this.stuId)
}
}
/**
* Emp400
*/
class Emp400 extends Person400 {
val empId = 800
override def printName(): Unit = {
println("Emp400 printName")
}
def showInfo(): Unit = {
println("雇员的id=" + this.empId)
}
}
```
##### Scala中超类的构造
回顾-Java中超类的构造
从代码可以看出:在Java中,创建子类对象时,子类的构造器总是去调用一个父类的构造器(显式或者隐式调用)。
~~~~java
package com.atguigu.chapter07.scalaoop;
/**
* @Date 2021/3/29 13:14
* @Version 10.21
* @Author DuanChaojie
*/
public class JavaBaseConstractor {
public static void main(String[] args) {
//A()
//B()
B b = new B();
//A(String name)mm
//B(String name)mm
B mm = new B("mm");
}
}
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()
//super();
System.out.println("B()");
}
public B(String name) {
super(name);
System.out.println("B(String name)" + name);
}
}
~~~~
> Scala超类的构造说明:
>
> 1. 类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用.),这点在前面我们说过了。
> 2. 只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params)
~~~scala
/**
* @Date 2021/3/27 22:27
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaBaseConstrator {
def main(args: Array[String]): Unit = {
// Person...
// Emp ....
val emp1 = new Emp700("dd",17)
println("-------------------------------------")
//Person...
//Emp ....
//Emp 辅助构造器~
val emp2 = new Emp700("mm")
}
}
/**
* 父类Person
*/
class Person700(pName:String) {
var name = pName
println("Person...")
def this() {
this("默认的名字")
println("默认的名字")
}
}
/**
* 子类Emp继承Person
* @param eName
* @param eAge
*/
class Emp700(eName:String,eAge:Int) extends Person700(eName) {
println("Emp ....")
//辅助构造器
def this(name: String) {
this(name,100) // 必须调用主构造器
//this 的话打印以下流程:
// Person...
// 默认的名字
// Emp ....
// Emp 辅助构造器~
this.name = name
println("Emp 辅助构造器~")
}
def showInfo(): Unit = {
println("雇员的名字 ", name)
}
}
~~~
##### 覆写字段
> 1. 在Scala中,子类改写父类的字段,我们称为覆写/重写字段。覆写字段需使用 override修饰。
> 2. 回顾:在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写。
> 3. 回顾-Java另一重要特性: 动态绑定机制
> 1. 如果调用的是方法,则Jvm机会将该方法和对象的内存地址绑定
> 2. 如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
~~~~java
/**
* @Date 2021/3/27 22:42
* @Version 10.21
* @Author DuanChaojie
*/
public class JavaDaynamicBind {
public static void main(String[] args) {
/**
* 将一个子类的对象地址,交给了一个AA(父类的)引用
* java的动态绑定机制的小结
* 1.如果调用的是方法,则Jvm机会将该方法和对象的内存地址绑定
* 2.如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
*/
AA obj = new BB();
System.out.println(obj.sum()); //40 //? 30
System.out.println(obj.sum1()); //30 //? 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覆写字段快速入门:
>
> 我们看一个关于覆写字段的案例。
~~~~scala
/**
* @Date 2021/3/29 13:19
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaFiledOverride {
def main(args: Array[String]): Unit = {
val obj1: AAA = new BBB
val obj2: BBB = new BBB
println(obj1.age)//20
println(obj2.age)//20
}
}
class AAA {
// 如果把val age 改成var报错
val age: Int = 10 // 会生成 public age()
}
class BBB extends AAA {
override val age: Int = 20 // 会生成 public age()
}
~~~~
![image-20210329222804715](assets/image-20210329222804715.png)
> 覆写字段的注意事项和细节
>
> 1. def只能重写另一个def(即:方法只能重写另一个方法)
> 2. val只能重写另一个val 属性 或 重写不带参数的def
> 3. var只能重写另一个抽象的var属性
```scala
/**
* @Date 2021/3/29 13:27
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaFieldOverrideDetail01 {
def main(args: Array[String]): Unit = {
val b1 = new BBBBB()
println(b1.sal) //0
val b2: AAAAA = new BBBBB()
println(b2.sal()) //0
}
}
class AAAAA {
def sal(): Int = {
return 10
}
}
class BBBBB extends AAAAA {
// 重写不带参数的def
override val sal: Int = 0 //底层 public sal
}
```
> var只能重写另一个抽象的var属性
~~~~scala
/**
* @Date 2021/3/29 13:29
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaFieldOverrideDetail02 {
def main(args: Array[String]): Unit = {
//var 只能重写另一个抽象的var属性
}
}
/**
* 在A03中,有一个抽象的字段(属性)
* 1. 抽象的字段(属性):就是没有初始化的字段(属性)
* 2. 当一个类含有抽象属性时,则该类需要标记为abstract
* 3. 对于抽象的属性,在底层不会生成对应的属性声明,而是生成两个对应的抽象方法(name name_$eq)
*/
abstract class A03 {
var name : String //抽象
var age: Int = 10
}
class Sub_A03 extends A03 {
/**
* 说明
* 1. 如果我们在子类中去重写父类的抽象属性,本质是实现了抽象方法
* 2. 因此这里我们可以写override ,也可以不写
*/
override var name : String = ""
}
~~~~
> 抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
>
> var重写抽象的var属性小结:
>
> 1. 一个属性没有初始化,那么这个属性就是抽象属性
> 2. 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
> 3. 如果是覆写一个父类的抽象属性,那么override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此override可省略]
##### 抽象类
`在Scala中,通过abstract关键字标记不能被实例化的类。方法不用标记abstract,只要省掉方法体即可。抽象类可以拥有抽象字段,抽象字段/属性就是没有初始值的字段`
快速入门案例:我们看看如何把Animal做成抽象类, 包含一个抽象的方法cry()
~~~~scala
/**
* @Date 2021/3/29 13:49
* @Version 10.21
* @Author DuanChaojie
*/
object AbstractDemo01 {
def main(args: Array[String]): Unit = {
println("通过jd-gui查看反编译后的代码")
}
}
/**
* 抽象类
*/
abstract class Animal {
var name: String //抽象的字段
var age: Int // 抽象的字段
var color: String = "black" //普通属性
def cry() //抽象方法,不需要标记 abstract
}
~~~~
> 抽象类的价值更多是在于设计,是设计者设计好后,让子类继承并实现抽象类(即:实现抽象类的抽象方法)
>
> Scala抽象类使用的注意事项和细节讨论:
>
> 1. 抽象类不能被实例
>
> ~~~~scala
> /**
> * @Date 2021/3/29 13:54
> * @Version 10.21
> * @Author DuanChaojie
> */
> object AbstractClassDetail01 {
> def main(args: Array[String]): Unit = {
> /**
> * 默认情况下,一个抽象类是不能实例化的,但是你实例化时,
> * 动态的实现了抽象类的所有抽象方法,也可以
> */
> val animal = new Animal03 {
> override def sayHello(): Unit = {
> println("say Hello ~")
> }
>
> override var food: String = _
> }
> animal.sayHello()
> }
> }
> ~~~~
>
> 2. 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
>
> ~~~~scala
> abstract class Animal02 {
> //在抽象类中可以有实现的方法
> def sayHi(): Unit = {
> println("xxx")
> }
> }
> ~~~~
>
> 3. 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
>
> 4. 抽象方法不能有主体,不允许使用abstract修饰。
>
> 5. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。【案例演示+反编译】
>
> ~~~scala
>
> abstract class Animal03 {
> def sayHello()
>
> var food: String
> }
>
>
> class Dog extends Animal03 {
> override def sayHello(): Unit = {
> println("小狗汪汪叫!")
> }
>
> override var food: String = ""
> }
> ~~~
>
> 6. 抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
>
> 7. 抽象类中可以有实现的方法。
>
> 8. 子类重写抽象方法不需要override,写上也不会错
##### 匿名子类
和Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类
```java
/**
* @Date 2021/3/29 13:59
* @Version 10.21
* @Author DuanChaojie
*/
public class NoNameDemo01 {
public static void main(String[] args) {
//在java中去创建一个匿名子类对象
A2 a2 = new A2() {
@Override
public void cry() {
System.out.println("cry...");
}
};
a2.cry();
}
}
abstract class A2 {
abstract public void cry();
}
```
> scala匿名子类案例
~~~scala
/**
* @Date 2021/3/29 14:07
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaNoNameDemo01 {
def main(args: Array[String]): Unit = {
val monster = new Monster {
override var name: String = _
override def cry(): Unit = {
println("妖怪嗷嗷叫...:)")
}
}
monster.cry()
}
}
abstract class Monster {
var name: String
def cry()
}
~~~
##### 继承层级
![image-20210329224450767](assets/image-20210329224450767.png)
> 继承层级图小结:
>
> 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可以是任何类。
#### 多态
##### 静态属性和静态方法
> 有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?请使用面向对象的思想,编写程序解决。
>
> Scala中静态的概念-伴生对象
>
> - Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。
~~~~scala
package com.atguigu.chapter08
/**
* @Date 2021/3/30 17:13
* @Version 10.21
* @Author DuanChaojie
*/
object AccompanyObject {
def main(args: Array[String]): Unit = {
println(ScalaPerson.sex) //true 在底层等价于 ScalaPerson$.MODULE$.sex()
ScalaPerson.sayHi() //在底层等价于 ScalaPerson$.MODULE$.sayHi()
}
}
/**
* 说明
* 1. 当在同一个文件中,有 class ScalaPerson 和 object ScalaPerson
* 2. class ScalaPerson 称为伴生类,将非静态的内容写到该类中
* 3. object ScalaPerson 称为伴生对象,将静态的内容写入到该对象(类)
* 4. class ScalaPerson 编译后底层生成 ScalaPerson类 ScalaPerson.class
* 5. object ScalaPerson 编译后底层生成 ScalaPerson$类 ScalaPerson$.class
* 6. 对于伴生对象的内容,我们可以直接通过 ScalaPerson.属性 或者方法
*/
//伴生类
class ScalaPerson { //
var name: String = _
}
//伴生对象
object ScalaPerson { //
var sex: Boolean = true
def sayHi(): Unit = {
println("object ScalaPerson sayHI~~")
}
}
~~~~
![image-20210330215916913](assets/image-20210330215916913.png)
> 伴生对象的小结:
>
> 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.方法 来实现调用。
> 9. 当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化
>
> ![image-20210330220150662](assets/image-20210330220150662.png)
>
> 伴生对象解决小孩游戏问题
>
> 如果,设计一个var total Int表示总人数,我们在创建一个小孩时,就把total加1,并且 total是所有对象共享的就ok了!,我们使用伴生对象来解决!
```scala
/**
* @Date 2021/3/30 17:21
* @Version 10.21
* @Author DuanChaojie
*/
object ChildJoinGame {
def main(args: Array[String]): Unit = {
// 创建三个小孩
val child0 = new Child("白骨精")
val child1 = new Child("蜘蛛精")
val child2 = new Child("黄鼠狼精")
Child.joinGame(child0)
Child.joinGame(child1)
Child.joinGame(child2)
Child.showNum()
}
}
/**
* 伴生类,
*
* @param cName
*/
class Child(cName: String) {
var name = cName
}
/**
* 伴生对象,静态内容
*/
object Child {
// 统计共有多少小孩的属性
var totalChildNum = 0
def joinGame(child: Child): Unit = {
printf("%s 小孩加入了游戏\n", child.name)
// totalChildNum 加1
totalChildNum += 1
}
def showNum(): Unit = {
printf("当前有%d小孩玩游戏\n", totalChildNum)
}
}
```
> 伴生对象 apply 方法
>
> - 在伴生对象中定义apply方法,可以实现: 类名(参数) 方式来创建对象实例。
~~~~scala
/**
* @Date 2021/3/30 17:31
* @Version 10.21
* @Author DuanChaojie
*/
object ApplyDemo01 {
def main(args: Array[String]): Unit = {
val pig1 = new Pig("小花")
// 使用apply方法来创建对象
val pig2 = Pig("小黑猪") //自动 apply(pName: String)
val pig3 = Pig() // 自动触发 apply()
println("pig2.name=" + pig2.name) //小黑猪
println("pig3.name=" + pig3.name) //匿名猪猪
}
}
/**
* 伴生类,演示apply方法
*
* @param pName
*/
class Pig(pName: String) {
var name: String = pName
}
object Pig {
// 编写一个apply方法
def apply(pName: String): Pig = new Pig(pName)
def apply(): Pig = new Pig("匿名猪猪")
}
~~~~
==Scala单例对象这个部分我们放在scala设计模式专题进行讲解==
##### Java接口interface回顾
声明接口
- `interface 接口名`
实现接口
- `class 类名 implements 接口名1,接口2`
Java接口的使用小结:
- 在Java中, 一个类可以实现多个接口。
- 在Java中,接口之间支持多继承
- 接口中属性都是常量
- 接口中的方法都是抽象的
Scala接口的介绍:
- 从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。
- ==Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。 理解trait 等价于(interface + abstract class)==
- ![image-20210330221053920](assets/image-20210330221053920.png)
##### 特质trait
trait 的声明
~~~~scala
// trait 命名 一般首字母大写
trait 特质名 {
trait体
}
~~~~
在scala中,java中的接口可以当做特质使用。Serializable: 就是scala的一个特质。
~~~~scala
/**
* @Date 2021/3/30 17:41
* @Version 10.21
* @Author DuanChaojie
*/
object TraitDemo01 {
def main(args: Array[String]): Unit = {
}
}
//trait Serializable extends Any with java.io.Serializable
//在scala中,java的接口都可以当做trait来使用(如上面的语法)
trait T1 extends Serializable{
}
trait T2 extends Cloneable{
}
~~~~
> Scala中trait 的使用:
>
> 一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
~~~~scala
//没有父类
class 类名 extends 特质1 with 特质2 with 特质3 ..
//有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3
~~~~
> 特质的快速入门案例:
>
> 1. 可以把特质可以看作是对继承的一种补充
> 2. Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响。
> 3. 所以我们认为: Scala引入trait特征 第一可以替代Java的接口, 第二个也是对单继承机制的一种补充
> 4. ![image-20210330221654786](assets/image-20210330221654786.png)
~~~~scala
/**
* @Date 2021/3/30 17:44
* @Version 10.21
* @Author DuanChaojie
*/
object TraitDemo02 {
def main(args: Array[String]): Unit = {
val c = new C
val f = new F
// 连接mysql数据库...
c.getConnect()
// 连接oracle数据库..
f.getConnect()
}
}
/**
* 按照要求定义一个trait
*/
trait Trait01 {
// 定义一个规范
def getConnect()
}
/**
* 先将六个类的关系写出
*/
class A {}
class B extends A {}
class C extends A with Trait01 {
override def getConnect(): Unit = {
println("连接mysql数据库...")
}
}
class D {}
class E extends D {}
class F extends D with Trait01 {
override def getConnect(): Unit = {
println("连接oracle数据库..")
}
}
~~~~
> 特质trait 的再说明:
>
> Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。【案例演示+反编译】
~~~scala
/**
* @Date 2021/3/30 17:48
* @Version 10.21
* @Author DuanChaojie
*/
object TraitDemo03 {
def main(args: Array[String]): Unit = {
// 创建sheep
val sheep = new Sheep
sheep.sayHi()
sheep.sayHello()
}
}
/**
* 当一个trait有抽象方法和非抽象方法时:
* 1. 一个trait在底层对应两个 Trait03.class 接口
* 2. 还对应 Trait03$class.class Trait03$class抽象类
*/
trait Trait03 {
// 抽象方法
def sayHi()
// 普通方法
def sayHello(): Unit = {
println("say Hello~")
}
}
/**
* 当trait有接口和抽象类是
* 1.class Sheep extends Trait03 在底层 对应
* 2.class Sheep implements Trait03
* 3.当在 Sheep 类中要使用 Trait03的实现的方法,就通过 Trait03$class
*/
class Sheep extends Trait03 {
override def sayHi(): Unit = {
println("say Hi~")
}
}
~~~
![image-20210330221945017](assets/image-20210330221945017.png)
> 2. 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
> 3. 所有的java接口都可以当做Scala特质使用 【案例演示+小结】
>
> 带有特质的对象,动态混入:
>
> 1. 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能【反编译看动态混入本质】
> 2. 此种方式也可以应用于对抽象类功能进行扩展
> 3. ==动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。==
> 4. 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。[如何理解]
> 5. 如果抽象类中有 抽象的方法,如何动态混入特质?
~~~~scala
/**
* @Date 2021/3/30 18:14
* @Version 10.21
* @Author DuanChaojie
*/
object MixInDemo01 {
def main(args: Array[String]): Unit = {
// ocp原则
// 在不修改 类的定义基础,让他们可以使用trait方法
val oracleDB = new OracleDB with Operate3
oracleDB.insert(100)
val mySQL = new MySQL3 with Operate3
mySQL.insert(200)
//如果一个抽象类有抽象方法,如何动态混入特质
val mySql_ = new MySQL3_ with Operate3 {
override def say(): Unit = {
println("say~")
}
}
mySql_.insert(999)
mySql_.say()
}
}
trait Operate3 { //特质
def insert(id: Int): Unit = { //方法(实现)
println("插入数据 = " + id)
}
}
class OracleDB { //空
}
abstract class MySQL3 {
}
abstract class MySQL3_ {
def say()
}
~~~~
> 在Scala中创建对象共有几种方式?
>
> 1. new 对象
> 2. apply 创建
> 3. 匿名子类方式
> 4. 动态混入
>
> 回顾一下:在Java中创建对象共有几种方式?
>
> 1. new对象
> 2. 反射创建对象
> 3. 反序列化的方式
> 4. clone对象的方式
>
> 叠加特质
>
> 构建对象的同时如果混入多个特质,称之为叠加特质,==那么特质声明顺序从左到右,方法执行顺序从右到左。==
>
> 叠加特质应用案例:
>
> - `目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序`
~~~~scala
/**
* @Date 2021/3/30 18:34
* @Version 10.21
* @Author DuanChaojie
*/
object AddTraits {
def main(args: Array[String]): Unit = {
/** 创建 MySQL4实例时,动态的混入 DB4 和 File4
* 第一个问题:初始化流程:
* 1. Operate4...
* 2. Data4
* 3. DB4
* 4. File4
* Scala在叠加特质的时候,会首先从后面的特质开始执行(即从左到右)
*/
val mysql = new MySQL4 with DB4 with File4
println("------------------------------------------------------")
/**
* 当我们执行一个动态混入对象的方法,其执行顺序是:
* 1. 向文件
* [2. 向数据库] ,如果File4类中insert方法是这个 super.insert(id) ,则有第二步
* 3. 插入数据 = 100000
* 顺序是:
* (1) 从右到左开始执行
* (2) 当执行到super时,是指的左边的特质
* (3) 如果左边没有特质了,则super就是父特质
*/
mysql.insert(100000)
println("------------------------------------------------------")
/**
* 练习题:
* 初始化流程:
* 1. Operate4...
* 2. Data4
* 3. File4
* 4. DB4
* 方法执行顺序:
* 1. 向数据库
* 2. 向文件
* 3. 插入数据 = 1111111
*/
val mySQL4 = new MySQL4 with File4 with DB4
mySQL4.insert(1111111)
}
}
trait Operate4 {
println("Operate4...")
// 抽象方法
def insert(id: Int)
}
/**
* 特质,继承了Operate4
*/
trait Data4 extends Operate4 {
println("Data4")
// 实现/重写 Operate4 的insert
override def insert(id: Int): Unit = {
println("插入数据 = " + id)
}
}
/**
* 特质,继承 Data4
*/
trait DB4 extends Data4 {
println("DB4")
// 重写 Data4 的insert
override def insert(id: Int): Unit = {
println("向数据库")
super.insert(id)
}
}
/**
* 特质,继承 Data4
*/
trait File4 extends Data4 {
println("File4")
// 重写 Data4 的insert
override def insert(id: Int): Unit = {
println("向文件")
//super.insert(id) //调用了insert方法(难点),这里super在动态混入时,不一定是父类
//如果我们希望直接调用Data4的insert方法,可以指定,如下
//说明:super[?] ?的类型,必须是当前的特质的直接父特质(超类)
super[Data4].insert(id)
}
}
class MySQL4 {} //普通类
~~~~
> 叠加特质注意事项和细节:
>
> 1. 特质声明顺序从左到右。
> 2. Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
> 3. Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
> 4. 如果想要调用具体特质的方法,可以指定:`super[特质].xxx(…).`其中的泛型必须是该特质的直接超类类型
>
> 当作富接口使用的特质
>
> 富接口:即该特质中既有抽象方法,又有非抽象方法
~~~~scala
trait Operate {
def insert( id : Int ) //抽象
def pageQuery(pageno:Int, pagesize:Int): Unit = { //实现
println("分页查询")
}
}
~~~~
> 特质中的具体字段
>
> 特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。
```scala
/**
* @Date 2021/3/30 19:18
* @Version 10.21
* @Author DuanChaojie
*/
object MixInPro {
def main(args: Array[String]): Unit = {
val mySql = new MySQL6 with DB6 {
override var sal = 11
}
}
}
trait DB6 {
// 抽象字段
var sal: Int
var opertype: String = "insert"
def insert(): Unit = {
}
}
class MySQL6 {}
```
反编译后的代码
![QQ截图20210330192853](assets/QQ截图20210330192853.png)
> 特质中的抽象字段
>
> - 特质中未被初始化的字段在具体的子类中必须被重写。
>
> 特质构造顺序
>
> - 特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”
> - 第一种特质构造顺序(声明类的同时混入特质)
> - 第二种特质构造顺序(在构建对象时,动态混入特质)
>
> 分析两种方式对构造顺序的影响
>
> - 第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
> - 第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。
~~~~scala
/**
* @Date 2021/3/30 19:31
* @Version 10.21
* @Author DuanChaojie
*/
object MixInSeq {
def main(args: Array[String]): Unit = {
/**
* 这时FF是这样 形式 class FF extends EE with CC with DD
* 调用当前类的超类构造器
* 第一个特质的父特质构造器
* 第一个特质构造器
* 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
* 第二个特质构造器
* .......重复4,5的步骤(如果有第3个,第4个特质)
* 当前类构造器
* E...
* A...
* B....
* C....
* D....
* F....
*/
val ff = new FF
println("----------------------------")
/** 这时我们是动态混入
* 先创建 new KK 对象,然后再混入其它特质
* 调用当前类的超类构造器
* 当前类构造器
* 第一个特质构造器的父特质构造器
* 第一个特质构造器.
* 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
* 第二个特质构造器
* .......重复5,6的步骤(如果有第3个,第4个特质)
* 当前类构造器 [案例演示]
* E...
* K....
* A...
* B....
* C....
* D....
*/
val kk = 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...")
}
/**
* 先继承了EE类,然后再继承CC 和DD
*/
class FF extends EE with CC with DD {
println("F....")
}
/**
* KK直接继承了普通类EE
*/
class KK extends EE {
println("K....")
}
~~~~
> 扩展类的特质
>
> 1. 特质可以继承类,以用来拓展该类的一些功能
> 2. 所有混入该特质的类,会自动成为那个特质所继承的超类的子类
> 3. 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误。
```scala
/**
* @Date 2021/3/30 19:48
* @Version 10.21
* @Author DuanChaojie
*/
object ExtendTraitDemo01 {
def main(args: Array[String]): Unit = {
println("-----------------------")
}
}
/**
* 说明
* 1. LoggedException 继承了 Exception
* 2. LoggedException 特质就可以 Exception 功能
*/
trait LoggedException extends Exception {
def log(): Unit = {
// 方法来自于Exception类
println(getMessage())
}
}
/**
* 因为 UnhappyException 继承了 LoggedException
* 而 LoggedException 继承了 Exception
* UnhappyException 就成为 Exception子类
*/
class UnhappyException extends LoggedException{
// 已经是Exception的子类了,所以可以重写方法
override def getMessage = "错误消息!"
}
/**
* 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类
* 否则就会出现了多继承现象,发生错误。
*/
class UnhappyException2 extends IndexOutOfBoundsException with LoggedException{
// 已经是Exception的子类了,所以可以重写方法
override def getMessage = "错误消息!"
}
class CCC {}
/**
* 错误的原因是 CCC 不是 Exception子类
*/
//class UnhappyException3 extends CCC with LoggedException{
// // 已经是Exception的子类了,所以可以重写方法
// override def getMessage = "错误消息!"
//}
```
> 自身类型
>
> - 自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
> - 举例说明自身类型特质,以及如何使用自身类型特质
~~~~scala
/**
* @Date 2021/3/30 20:04
* @Version 10.21
* @Author DuanChaojie
*/
object SelfTypeDemo {
def main(args: Array[String]): Unit = {
println("-------------------------------------- ")
}
}
/**
* Logger就是自身类型特质,当这里做了自身类型后,那么
* trait Logger extends Exception,要求混入该特质的类也是 Exception子类
*/
trait Logger {
// 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用
this: Exception =>
def log(): Unit ={
// 既然我就是Exception, 那么就可以调用其中的方法
println(getMessage)
}
}
//class Console extends Logger {} //对吗? 错误
class Console extends Exception with Logger {}//对吗?
~~~~
##### 嵌套类
> 在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。
>
> 嵌套类类似于Java中的内部类。
>
> 面试题:Java中,类共有五大成员,请说明是哪五大成员
>
> 1. 属性
> 2. 方法
> 3. 内部类
> 4. 构造器
> 5. 代码块
>
> Java内部类的简单回顾
>
> 在Java中,一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
```java
/**
* @Date 2021/3/30 20:12
* @Version 10.21
* @Author DuanChaojie
*/
public class JavaInnerClass {
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);
inner1.test(inner1);
inner2.test(inner1);
// 创建Java静态内部类
// 因为在java中静态内部类是和类相关的,使用 new OuterClass.StaticInnerClass()
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
}
}
/**
* 外部类
*/
class OuterClass {
class InnerClass { //成员内部类
//test方法可以接收 InnerClass实例
public void test(InnerClass ic) {
System.out.println(ic);
}
}
static class StaticInnerClass { //静态内部类
}
}
```
> Scala嵌套类的使用1
>
> 请编写程序,定义Scala 的成员内部类和静态内部类,并创建相应的对象实例
~~~~scala
/**
* @Date 2021/3/30 20:24
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaInnerClassDemo {
def main(args: Array[String]): Unit = {
// 创建外部类
val outer1:ScalaOuterClass1 = new ScalaOuterClass1();
val outer2:ScalaOuterClass1 = new ScalaOuterClass1();
// Scala创建内部类的方式和Java不一样,将new 关键字放置在前
// 使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass1
val inner2 = new outer2.ScalaInnerClass1
// 创建静态内部类对象
val staticInner = new ScalaOuterClass1.ScalaStaticInnerClass
println(staticInner)
}
}
/**
* 伴生对象
*/
class ScalaOuterClass1{
// 成员内部类
class ScalaInnerClass1{
}
}
/**
* 伴生对象
*/
object ScalaOuterClass1{
// 静态内部类
class ScalaStaticInnerClass{
}
}
~~~~
> Scala嵌套类的使用2
>
> 请编写程序,在内部类中访问外部类的属性。
>
> - 方式1:内部类如果想要访问外部类的属性,可以通过外部类对象访问。即:访问方式:==外部类名.this.属性名==
```scala
/**
* 外部类
* 内部类访问外部类的属性的方法1
* 外部类名.this.属性
*/
class ScalaOuterClass2 {
//定义两个属性
var name = "scoot"
private var sal = 30000.9
class ScalaInnerClass { //成员内部类,
def info() = {
// 访问方式:外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例,
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
println("name = " + ScalaOuterClass2.this.name
+ " sal =" + ScalaOuterClass2.this.sal)
}
}
}
```
> 方式2:内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即:访问方式:外部类名别名.属性名 【外部类名.this 等价 外部类名别名】
>
>
```scala
/**
* 外部类
* 内部类访问外部类的属性的方法2 使用别名的方式
* 1. 将外部类属性,写在别名后面
*/
class ScalaOuterClass3 {
// 这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
myouter => class ScalaInnerClass3 { //成员内部类,
def info() = {
// 访问方式:外部类别名.属性名
// 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
println("name~ = " + myouter.name
+ " sal~ =" + myouter.sal)
}
//这里有一个方法,可以接受ScalaInnerClass实例
//下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的影响
def test(ic: ScalaOuterClass3#ScalaInnerClass3): Unit = {
System.out.println("使用了类型投影" + ic)
}
}
//定义两个属性
var name = "jack"
private var sal = 800.9
}
object ScalaOuterClass3 { //伴生对象
class ScalaStaticInnerClass3{ //静态内部类
}
}
```
> 解决方式-使用类型投影
>
> 类型投影是指:在方法声明上,如果使用 `外部类#内部类` 的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)【案例演示】
>
> 完整代码:
~~~~scala
/**
* @Date 2021/3/30 20:24
* @Version 10.21
* @Author DuanChaojie
*/
object ScalaInnerClassDemo {
def main(args: Array[String]): Unit = {
// 创建外部类
val outer1:ScalaOuterClass1 = new ScalaOuterClass1();
val outer2:ScalaOuterClass1 = new ScalaOuterClass1();
// Scala创建内部类的方式和Java不一样,将new 关键字放置在前
// 使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass1
val inner2 = new outer2.ScalaInnerClass1
// 创建静态内部类对象
val staticInner = new ScalaOuterClass1.ScalaStaticInnerClass
println(staticInner)
}
}
/**
* 伴生对象
*/
class ScalaOuterClass1{
// 成员内部类
class ScalaInnerClass1{
}
}
/**
* 伴生对象
*/
object ScalaOuterClass1{
// 静态内部类
class ScalaStaticInnerClass{
}
}
/**
* 外部类
* 内部类访问外部类的属性的方法1
* 外部类名.this.属性
*/
class ScalaOuterClass2 {
//定义两个属性
var name = "scoot"
private var sal = 30000.9
class ScalaInnerClass { //成员内部类,
def info() = {
// 访问方式:外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例,
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
println("name = " + ScalaOuterClass2.this.name
+ " sal =" + ScalaOuterClass2.this.sal)
}
}
}
/**
* 外部类
* 内部类访问外部类的属性的方法2 使用别名的方式
* 1. 将外部类属性,写在别名后面
*/
class ScalaOuterClass3 {
// 这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
myouter => class ScalaInnerClass3 { //成员内部类,
def info() = {
// 访问方式:外部类别名.属性名
// 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
println("name~ = " + myouter.name
+ " sal~ =" + myouter.sal)
}
//这里有一个方法,可以接受ScalaInnerClass实例
//下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的影响
def test(ic: ScalaOuterClass3#ScalaInnerClass3): Unit = {
System.out.println("使用了类型投影" + ic)
}
}
//定义两个属性
var name = "jack"
private var sal = 800.9
}
object ScalaOuterClass3 { //伴生对象
class ScalaStaticInnerClass3{ //静态内部类
}
}
~~~~
## ☆