1.类和对象
Scala是一种函数式的面向对象语言,支撑面向对象编程思想,也有类和对象的概念。
1.1相关概念
什么是面向对象?
面向对象是一种编程思想,它是基于面向过程,强调的是有对象为基础完成各种操作。
面向对象的三大思想是什么?
1.符合人们的思考习惯
2.复杂问题简单化
3.把程序员从执行者变成指挥者
什么是类?
类是属性和行为的集合体,是一个抽象的概念。
属性(也称为成员变量):名词,用来描述事物的外在特征的
行为(也称为成员方法):动词,表示能够做什么
例如:学生有姓名、年龄(属性),学生要学习、吃饭(行为)
什么是对象?
对象是类的具体体现、实现
面向对象的三大特征?
封装、继承、多态
1.2创建类和对象
class Person{} //创建类
def main(args: Array[String]): Unit = {
val p = new Person() // 实例化对象
println(p) // 打印对象
}
1.3简写方式
用法:
- 1.如果类是空的,没有任何成员,则可以省略{}
- 2.如果构造器的参数为空,可以省略()
class Person //创建类,省略 {}
def main(args: Array[String]): Unit = {
val p = new Person // 实例化对象 , 省略 ()
println(p) // 打印对象
}
2.定义和访问成员变量
2.1 用法
- 在类中val/val定义成员变量
- 对象可以通过
变量名.
的方式来访问
2.2 实例:
需求:
定义一个Person类,包含姓名和年龄
创建一个名为张三,年龄为20 的对象
打印对象的名字和年龄
//实体类 ,添加属性:姓名,年龄
class Person{
var age: Int = 0
var name: String = ""
}
object demo_oop {
def main(args: Array[String]): Unit = {
val p = new Person
p.name = "张三" // 属性赋值
p.age = 28
println(p.name, p.age) // 属性访问
}
3.用下划线来初始化成员变量的默认值
在使用var类型的成员变量时可以是用下划线来初始化变量(需要制定成员数据类型)的值:
//实体类
class Person{
var age: Int = _ //下划线来初始化变量
var name: String = _
}
object demo_oop {
def main(args: Array[String]): Unit = {
val p = new Person
p.name = "张三"
p.age = 28
println(p.name, p.age)
}
val 不能使用下划线来初始化,必须手动初始化。
4.定义和访问成员方法:
4.1 语法
使用def来定义方法:
def 方法名(参数1: 数据类型, 参数2: 数据类型): [返回数据类型] = {
//方法本体 最后一行作为返回数据
)
4.2 实例
class Customer { //创建类
var name: String = _ // 成员变量 用下划线初始化值
var age: Int = _
def printHello(): Unit ={ // 成员方法
println(s"Hello, ${name}")
}
}
object demo_oop {
def main(args: Array[String]): Unit = {
val c = new Customer() //实例化对象
c.name = "李四" // 对象的成员赋值
c.printHello() // 调用对象的成员方法
}
}
5.访问权限修饰符
Scala中没有 public
修饰符,如果没有 private
、protected
修饰,则为公共访问,反之外部不能访问。
用 private 关键字修饰,带有此标记的成员仅在包含了成员定义的类或对象内部可见,同样的规则还适用内部类。
在 scala 中,对保护(Protected)成员的访问比 java 更严格一些。因为它只允许保护成员在定义了该成员的的类的子类中被访问
6.类的构造器
6.1类的主构造器
语法:
def 类名(var/val 参数名: 数据类型 = 默认值, var/val 参数名: 数据类型 = 默认值) = {
//构造代码块
}
实例:
class Person(var name: String, var sex: String = "M") {
def printHello() = println(s"Hello, ${name}, ${sex}")
}
def main(args: Array[String]): Unit = {
val p = new Person(name = "renwl", sex = "F")
p.printHello()
}
6.2类的辅助构造器
语法:
def this(参数名: 数据类型 = 默认值, 参数名: 数据类型 = 默认值){
//第一行需要调用主构造器或者其他构造器
}
实例:
class Person{
var name: String = _
def this(name: String) = {
this() // 调用主构造器
this.name = name // 赋值
}
def this(name: String, age: Int) = {
this(name) // 调用上一个构造器,对name赋值
this.age = age
}
def printHello() = println(s"Hello, ${name}, ${age}")
}
def main(args: Array[String]): Unit = {
val p = new Person("rewl")
p.printHello()
}
7.单例对象
语法:
Scala中没有static关键字,要想定义类似于Java中的static变量、方法,就要使用Scala中的单例对象。
一个单例对象是就是一个值。单例对象的定义方式很像类,但是使用关键字 object
:
object 单例对象名 {}
单例对象中,可以是用 单例对象名.
的形式调用成员。
实例:
object Dog {
val leg_num: Int = 4 // 单例成员变量
def printLegs() = println(s"狗子有${leg_num}条腿。。。")
}
def main(args: Array[String]): Unit = {
println(Dog.leg_num)
Dog.printLegs()
}
8.main方法
在Scala中如果需要运行一个程序,需要定义一个main方法,在Java中main方法是静态的,但是scala中没有静态方法,故scala中,这个main方法必须放在一个单例对象中。
8.1定义main方法
def main(args: Array[String]): Unit = {
//方法体
}
8.2继承APP特质
object extendapp extends App { // 继承APP特质
println("Hello word")
}
9.伴生对象
在Java中,经常有一些类,同时具有实例成员又有静态成员。比如:
public class Generals {
private static String armsName = "青龙偃月刀"
public void toWar(){
System.out.println("武将拿着" + armsName + ",上阵杀敌。");
}
public static void main(String[] args){
new Generals().toWar();
}
}
在Scala中,要实现类似的效果,可以使用伴生对象来实现。
9.1定义伴生对象
一个class和object具有相同的名字,这个object成为伴生对象,这个class成为伴生类。
- 伴生对象必须跟伴生类具有一样的名字
- 伴生对象和伴生类在同一个scala源文件中
- 伴生对象和伴生类可以互相访问private属性
class Generals { // 里面的对象是非静态的
def toWar() = println(s"武将拿着${Generals.armsName},上阵杀敌!")
}
object Generals {
private val armsName = "青龙偃月刀"
}
def main(args: Array[String]): Unit = {
val g = new Generals
g.toWar()
}
9.2 private[this]访问权限
如果某一个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问。
//private[this]访问修饰符
//定义一个Person伴生类,并在其中定义一个name字段
class Person(private[this] var name: String){
}
//定义Person类的伴生对象
object Person{
def printPerson(p: Person) = println(p.name)//无法访问private[this] 修饰的name,会报错
}
def main(args: Array[String]): Unit = {
val p = new Person("wula")
Person.printPerson(p)
}
9.3 apply方法
在Scala中,支持创建对象的时候,免new的动作,这种写法非常简便,优雅。要实现免new,我们就需要通过伴生对象的apply方法来实现。
语法:
//定义
object 伴生对象名{
def apply(参数名: 数据类型) = new 类(...)
}
//创建对象
val 对象名 = 伴生对象名(参数名)
实例:
class Person(private var name: String){
}
//定义Person类的伴生对象
object Person{
def printPerson(p: Person) = println(p.name)
def apply(name: String): Person = new Person(name) // 定义apply方法
}
def main(args: Array[String]): Unit = {
val p = Person("wula") //实例化对象 免new
Person.printPerson(p)
}
10.继承
概述:
实际开发者,我们发现很多类中的内容是相似的,每次写很麻烦。于是我们就可以把这些相似的内容提取出来单独的放到一个类中(父类),然后让那么多个类(子类)和这个类(父类)产生一个关系,从而实现子类可以访问父类的内容,这种关系叫:继承。
因为Scala语音是支持面向对象的,我们也可以用Scala来实现继承,通过继承来减少重复的代码。
10.1语法:
- Scala中试用extends关键字来实现继承
- 可以在子类中定义父类中没有的字段和方法,或者重写父类的方法
- 类和单例对象都可以有父类
class/object A类 extends B类{
//承继父业
}
10.2类型继承
Scala中类型判断:
- isInstanceOf
- getClass/classOf
语法:
// 判断对象是否为指定的类型
var trueOrFalse: Boolean = 对象.isInstanceOf[类型]
//类型转换: 将对象转换为指定的类型
val 变量 = 对象.asInstanceOf[类型]
// 获取对象的类型
对象.getclass()
// 获取类的类型
classOf[类名]
11.抽象类
Scala支持抽象类,使用abstract关键字来实现。
11.1概念
如果类中有抽象字段或抽象方法,那么该类为一个抽象类。
- 抽象字段:没有初始化值的变量就是抽象字段。
- 抽象方法:没有方法体的方法就是一个抽象方法。
11.2语法:
//定义抽象类
abstract class 抽象类名{
//定义抽象字段
var/val 抽象字段名:类型
//定义抽象方法
def 方法名(参数: 参数类型): 返回类型
}
11.3抽象类案例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IkTrmq0M-1615037356268)(C:\Users\TD-RENWL\AppData\Roaming\Typora\typora-user-images\image-20201203153659343.png)]
- 定义一个抽象类Shape,抽象方法area
- 实现一个Square正方形的类,继承Shape,有一个边长的主构造器,并实现计算面积的方法
- 实现一个长方形的类,继承Shape,有一个长、宽的主构造器,并实现计算面积的方法
- 实现一个圆形的类,继承Shape,有一个半径的主构造器,并实现计算面积的方法
//抽象类
abstract class Shape {
def area(): Double // 抽象方法
val output: String //抽象字段
}
// 继承抽象类必须 重写抽象类中所有的方法和初抽象字段
class Square(var edge: Double) extends Shape {
override def area(): Double = edge * edge //重写抽象方法
override val output: String = "长方形" // 重写抽象字段
}
class Rectangle(var length: Double, var width: Double) extends Shape {
override def area(): Double = length * width
override val output: String = "正方形"
}
class Circle(var raius: Double) extends Shape {
override def area(): Double = Math.PI * raius * raius
override val output: String = "圆形"
}
def main(args: Array[String]): Unit = {
val s = new Square(2.5)
println(s.output, s.area())
val r = new Rectangle(2, 4)
println(r.output, r.area())
val c = new Circle(2)
println(c.output, c.area())
}
12.匿名内部类
匿名内部类继承了类的匿名的子类对象,它可以直接用来创建实例对象。
语法:
new (抽象)类名(){
//重写方法和成员变量
}
当类的主构造器参数列表为空是,则小括号客省略不写。
使用场景:
- 当对象方法仅调用一次的时候
- 可以作为方法的参数进行传递
//抽象类
abstract class Shape {
def area(): Double // 抽象方法
val output: String //抽象字段
}
class Square(var edge: Double) extends Shape {
override def area(): Double = edge * edge
override val output: String = "长方形"
}
def main(args: Array[String]): Unit = {
val nm = new Square(10){
override def area(): Double = 10
}
println(nm.area())
}
13.特质
13.1概述:
有时,我们会遇到一些特定的需求,即:在不影响当前继承体系的情况下,对某些类(或者某些对象)的功能进行加强,例如:有猴子类和大象类,它们都有年龄,姓名,以及吃的功能,但是部分的猴子通过马戏团的训练后,学会了骑独轮车的技能。这个不能定义到父类或者猴子类中,而是要定义到特质中。Scala中特质使用trait修饰。
13.2特点:
- 特质能提高代码的复用性
- 特质可以提高代码的扩展性和可维护性
- 类与特质之间是继承关系,只不过类与类之间只支持单继承,但是类与特质间,可以是多继承
- Scala的特质中可以有普通字段,抽象字段,普通方法,抽象方法。
注:
1.如果特质中只有抽象的内容,这种特质称为:瘦接口
2.如果特质中既有抽象内容,又有具体内容,这种特质称为:福接口
13.3动态混入trait
当我们希望在不改变类继承的体系的情况下,对对象的功能进行临时的增强或拓展,这个时候就可以使用对象混入技术了,所谓的对象混入就是指在Scala中,类和特质间没有任何的继承关系,但是通过特定的关键字,却可以让该类对象具有指定特质中的成员。
语法:
var/val 对象名 = new 类 with 特质
实例:
trait RunDulunche{
def dulunche()=println("骑车中。。。")
def othcs(): Unit
}
class Monkey{
def eat()=println("猴子吃香蕉。。。")
}
def main(args: Array[String]): Unit = {
val m = new Monkey() with RunDulunche {
override def othcs(): Unit = println("猴子其他的技能,翻跟斗。。。") // 重写特质中的方法
}
m.othcs()
m.dulunche() //该对象具有特质中的成员
}
13.4使用trait实现适配器模式
13.4.1设计模式简介
概述:
涉及模式是前辈们对代码开发的经验总结,是解决特定问题的一系列讨论,它并不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性已经安全性的解决方案。
分类:
设计模式一共23中,分如下3类:
- 1.创建型:指的是需要创建对象的,常用的模式有:单例模式、工厂方法模式
- 2.结构型:指的是类、特质间的关系架构,常见的模式有:适配器模式、装饰模式
- 3.行为型:指的是类(特质)能做什么,常见的有模块方法模式、职责链模式
13.4.2适配器模式
当特质中有多个抽象方法时,而我们需要用到其中某一个或几个方法时,不得不重写该特质中的所有抽象方法,这样做很麻烦。这种情况我们可以定义一个抽象类继承该特质,重写特质中的所有方法,方法体为空,这时候,我们再继承这个抽象类,要使用某些方法时,只需要重写指定的方法就可以了。这种抽象类就叫:适配器类,这种设计模式称为:适配器设计模式。
结构:
trait 特质A{
//抽象方法1
//抽象方法2
//抽象方法3
//...
}
abstract class 类B extends A{
//重写方法1,方法体为空
//重写方法2,方法体为空
//重写方法3,方法体为空
//...
}
class 自定义类C extends 类B{
//需要使用哪个方法就重写哪个方法即可
}
实例:
1.定义特质PlayLOL,添加6个抽象方法,分别为top()、mid()、adc()、support()、jungle()、schoolchild().
2.定义抽象类Player,继承PlayLOL特质,重写特质中的所有方法,方法体为空。
3.定义普通类GreeHand,继承Player,重写support()和schoolchild()方法。
4.定义main方法,在其中创建GreenHand类的对象,并调用其方法进行测试
//1.定义特质PlayLOL,添加6个抽象方法,分别为top()、mid()、adc()、support()、jungle()、schoolchild().
trait PlayLOL{
def top()
def mid()
def support()
def jungle()
def schoolchild()
}
//2.定义抽象类Player,继承PlayLOL特质,重写特质中的所有方法,方法体为空。
abstract class Player extends PlayLOL{
override def top(): Unit = {}
override def mid(): Unit = {}
override def support(): Unit = {}
override def jungle(): Unit = {}
override def schoolchild(): Unit = {}
}
//3.定义普通类GreeHand,继承Player,重写support()和schoolchild()方法。
class GreeHand extends Player{
override def support(): Unit = println("按住鼠标不放手,不死不放手。")
}
// 4.定义main方法,在其中创建GreenHand类的对象,并调用其方法进行测试
def main(args: Array[String]): Unit = {
val g = new GreeHand()
g.support()
}
13.5使用trait实现模板方法模式
在面向对象程序设计中,会经常遇到这种情况:设计一个系统时知道了算法所需要的关键步骤,而且确定了这些步骤的执行顺序,单某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
如:去银行办理业务一般有4个流程:取号、排队、办理具体业务、对工作人员进行评价等,其中取号、排队、评价对每个客户都是一样的,可以在父类中实现,但是具体业务办理却不同,可以延迟到子类中实现,可以用到模板方法设计模式了。
13.5.1概述:
Scala中,先定义操作中的算法骨架,而将具体算法的一些步骤延迟到子类中,是的子类可以不改变改算法结构的情况下重定义该算法的某些特定的步骤。
优点:
- 扩展性更强:父类中封装了公共的部分,而可变的部分交给子类来实现。
- 符合开闭原则:部分方法由子类实现,因此子类可以通过扩展方式来增加相应的功能。
缺点:
- 类的个数增加,导致系统更加庞大,涉及也更加抽象。
- 提供了代码阅读的难度
13.5.2语法:
class A{
def 方法名(参数列表) = { //具体方法:模板方法
//步骤1 ,已知
//步骤2,未知 , 定义抽象方法
//步骤3,已知
// ...
}
// 定义抽象方法
}
class B extends A{
//重写抽象方法
}
13.5.3实例:
需求:
定义一个模板类Template,添加code()和getRunTime()方法,用来获取某些代码的执行时间。
定义一个ForDemo类继承Template,然后重写run() 方法,用来计算打印10000次 “Hello Scala!” 的执行时间。
定义main 方法,测试程序的具体时间。
实现代码:
//定义一个模板类Template,添加code()和getRunTime()方法,用来获取某些代码的执行时间。
abstract class Template {
def getRunTime() = {
val start = System.currentTimeMillis() // 获取当前时间毫秒
code()
val end = System.currentTimeMillis()
end - start
}
def code()
}
//定义一个ForDemo类继承Template,然后重写run() 方法,用来计算打印10000次 "Hello Scala!" 的执行时间。
class ForDemo extends Template {
override def code(): Unit = for (i <- 1 to 10000) println("Hello Scala!!!")
}
//定义main 方法,测试程序的具体时间。
def main(args: Array[String]): Unit = {
val f = new ForDemo
println(f.getRunTime())
}
13.6使用trait实现职责链模式
13.6.1概述
多个trait中出现了同一个方法,且该方法最后都调用了super.方法名(),当类继承了这多个trait后,就可以依次调用多个trait中的此同一个方法了,这就形成了一个调用链。
执行顺序:
- 按照从有往左的顺序依次执行:首先会从最右边的trait方法开始执行,从右往左执行trait中的方法。
- 当所有子特质的该方法都执行完毕后,最后会执行父特质中的方法。
13.6.2实例:
需求:
通过Scala代码,模拟支付过程的调用链。
如果我们需要开发一个支付功能,需要执行一系列的验证才能完成支付,例如:
1、进行支付签名校验
2、数据合法性校验
3.。。
步骤:
- 定义一个Handler特质,添加具体的handle(data: String)方法,表示处理数据(具体支付逻辑)
- 定义一个DataValidHandler特质,继承自Handler:重写handle方法,验证数据,并调用父特质的handle方法
- 定义一个SignatureValidHandler特质,继承自Handler:重写handle方法,验证签名,并调用父特质的handle方法
- 创建PayMent类,继承DataValidHandler、SignatureValidHandler特质,定义pay(data:String)方法,用户支付请求,调用父特质handle方法
- 定义main 方法,测试。
参考代码:
//1. 定义一个Handler特质,添加具体的handle(data: String)方法,表示处理数据(具体支付逻辑)
trait Handler {
def handle(data: String) = {
println("支付进行中。。。")
println(s"${data}")
}
}
//2. 定义一个DataValidHandler特质,继承自Handler:重写handle方法,验证数据,并调用父特质的handle方法
trait DataValidHandler extends Handler {
override def handle(data: String): Unit = {
println("验证数据。。。")
super.handle(data)
}
}
//3. 定义一个SignatureValidHandler特质,继承自Handler:重写handle方法,验证签名,并调用父特质的handle方法
trait SignatureValidHandler extends Handler {
override def handle(data: String): Unit = {
println("验证签名。。。")
super.handle(data)
}
}
//4. 创建PayMent类,继承DataValidHandler、SignatureValidHandler特质,定义pay(data:String)方法,用户支付请求,调用父特质handle方法
class PayMent extends DataValidHandler with SignatureValidHandler {
def pay(data: String) = {
println("发起支付请求。。。")
super.handle(data)
}
}
//5. 定义main 方法,测试。
def main(args: Array[String]): Unit = {
val p = new PayMent()
p.pay("王五向张三转账40元。")
}
输出:
发起支付请求。。。
验证签名。。。
验证数据。。。
支付进行中。。。
王五向张三转账40元。
13.7 trait的构造机制
13.7.1 概述:
如果遇到一个类继承了某个父类且继承了多个父特质的情况,那该类(子类),该类的父类,及该类的父特质间是如何构造的呢?
13.7.2 构造机制规则
- 每个特质只有一个无参数的构造器,trait也有构造代码,但是跟类不同,特质不能有构造参数
- 遇到一个类继承另一个类,已经多个trait的情况,当创建该类的实例时,它的构造器执行顺序如下:
- 执行父类的构造器
- 按从左往右的顺序,依次执行trait的构造器
- 如果trait有父trait,则先执行父trait的构造器
- 如果多个trait有同样的父trait,则父trait的构造器只初始化依次
- 执行子类构造器。
13.7.3 实例:
需求:
- 定义一个父类及多个特质,然后用一个类去继承它们。
- 创建子类对象,并测试trait的构造顺序。
步骤:
- 创建Logger特质,在构造器中打印“执行Logger构造器”
- 创建MyLogger特质,继承Logger特质,在 构造器中打印“执行MyLogger构造器”
- 创建TimeLogger特质,继承Logger特质,在 构造器中打印“执行TimeLogger构造器”
- 创建Person类,在构造器中打印“执行Person构造器”
- 创建Student类,继承Person、MyLogger,TimeLogger,构造器中打印“执行student构造器”
- 创建main方法,创建student类对象,观察输出。
//1. 创建Logger特质,在构造器中打印“执行Logger构造器”
trait Logger {
println("执行Logger构造器")
}
//2. 创建MyLogger特质,继承Logger特质,在 构造器中打印“执行MyLogger构造器”
trait MyLogger extends Logger {
println("执行MyLogger构造器")
}
//3. 创建TimeLogger特质,继承Logger特质,在 构造器中打印“执行TimeLogger构造器”
trait TimeLogger extends Logger {
println("执行TimeLogger构造器")
}
//4. 创建Person类,在构造器中打印“执行Person构造器”
class Person {
println("执行Person构造器")
}
//5. 创建Student类,继承Person、MyLogger,TimeLogger,构造器中打印“执行student构造器”
class Student extends Person with MyLogger with TimeLogger {
println("执行student构造器")
}
//6. 创建main方法,创建student类对象,观察输出。
def main(args: Array[String]): Unit = {
val s = new Student()
}
输出:
执行Person构造器 //父类构造器
执行Logger构造器 //父特质构造器
执行MyLogger构造器 //右边第一个特质构造器
执行TimeLogger构造器 //第二个特质构造器
执行student构造器 //子类构造器
13.8 trait继承class
在Scala中,trait(特质)也可以继承class(类)。特质会将class中的成员都继承下来。
13.9 程序员
需求:
现实生活中有很多程序员,例如:Python、Java程序员,他们都有姓名(name),年龄(age),都要吃饭(eat),都有自己掌握的技能(skill),不同的是,部分的程序员培训后掌握了其他的技能,掌握了大数据技术(bigdata),实现更好的就业。
目的:
考察特质、抽象类的相关内容。
abstract class Programmer {
var name: String = _
var age: Int = _
def eat()
def skill()
}
trait BigData {
def learnBigDataSkill(): Unit = {
println("学习大数据技术:Hadoop、Hive、Spark等技能")
}
}
class PythonProgrammer extends Programmer {
override def eat(): Unit = println("Python程序员吃白米饭。。。")
override def skill(): Unit = println("精通Python")
}
class PJavaProgrammer extends Programmer {
override def eat(): Unit = println("Java程序员吃大馒头。。。")
override def skill(): Unit = println("精通Java")
}
class PartPythonProgrammer extends PythonProgrammer with BigData{
override def skill(): Unit = {
super.skill()
super.learnBigDataSkill()
}
}
def main(args: Array[String]): Unit = {
val p = new PartPythonProgrammer()
p.skill()
}