Scala 第三篇 OOP篇
上接:Scala 第二篇 算子篇
- 前序
- 一、类
- 1、修饰符
- 2、创建类示例
- 3、类的继承
- 二、抽象类
- 三、单例对象
- 四、特质
- 1、动态混入
- 2、抽象类 VS 特质
- 五、内部类
- 六、样例类
- 七、枚举
- 八、泛型
- 九、隐式类
- 十、包与包对象
- 练习
前序
1、Scala 为纯粹OOP
1.1、不支持基本类型:一切皆为对象 Byte, Int,…
1.2、不支持静态关键字:static
1.3、支持类型推断,和类型预定,动静结合
一、类
关键字:class
创建对象:new
内含:成员变量和方法
区别:
1、默认访问修饰符为 public,也支持 private 和 protected
2、没有构造方法,通过构造参数列表实现对象创建
1、修饰符
修饰符 | 类(class) | 伴生对象(object) | 子类(subclass) | 同包(package) | 全局(world) |
---|---|---|---|---|---|
public(default) | Y | Y | Y | Y | Y |
protected | Y | Y | Y | N | N |
private | Y | Y | N | N | N |
2、创建类示例
-
创建类
scala">// 类自身:就是类的【主】构造器 class Student(name:String, age:Int) {// 属性private var _name:String = nameprivate var _age:Int = age// 辅助构造器:基于主构造器的重载def this() = this("Unknown", 0)def this(name:String) = this(name, 18)// 方法def play(): Unit = {println("play computer game")}def getName:String = _namedef getAge:Int = _agedef setName(name: String):Unit = _name = namedef setAge(age: Int):Unit = _age = ageoverride def toString(): String = s"Student{name=${_name}, age=${_age}}" }
-
创建对象与方法调用
scala">val stu1 = new Student("张三", 20) val stu2 = new Student() val stu3 = new Student("leaf")stu1.play() // play computer game stu2.setName("aaa") stu2.setAge(19) println(stu2) // Student{name=aaa, age=19} println(stu3.getAge) // 18
3、类的继承
-
类的继承
scala">// 继承关键字:extents, 继承Student类 class EliteStudent(name: String, age: Int, score: Int) extends Student(name, age){// 增添属性private var _score: Int = score// 重载方法override def play(): Unit = {println("Do homework")}override def toString(): String = s"${super.toString()}, score: ${score}" }
-
调用
scala">val eliteStudent = new EliteStudent("李四", 12, 100) eliteStudent.play() // 输出:Do homework println(eliteStudent) // Student{name=李四,age=12}, score: 100
二、抽象类
关键字:abstract
- 抽象类中可以有抽象方法(没有方法体的方法即抽象方法)
- 无法实例化
- 使用 abstract 关键字修饰
- 子类重写父类【抽象】方法可以省略 override 关键字,但不推荐省略
- 子类重写父类【非抽象】方法必须写 override 关键字
示例代码
scala">// 抽象类
abstract class Animal {def eat(): Unit // 抽象方法def play(): Unit
}class Dog extends Animal {override def eat(): Unit = {println("大口吃肉")}override def play(): Unit = {println("抓蝴蝶")}
}class Cat extends Animal{override def eat(): Unit = {println("优雅的吃")}override def play(): Unit = {println("滚毛球")}
}
三、单例对象
代替 Java 中的 static 关键字
1、关键字:object
2、可以包含属性和方法,且可以通过单例对象名直接调用
3、采取惰性模式,第一次被访问时创建
4、无构造器,且不能 new
5、程序入口方法必须定义在单例对象中
6、同一个文件中同名的类和单例对象形成绑定关系,并称之为伴生类和伴生对象
-
伴生类,伴生对象(单例对象)的创建
scala">// 伴生类:与伴生对象具有相同名称的类被称为伴生类。 class Student(name:String, age:Int) {private var _name:String = nameprivate var _age:Int = agedef this() = this("Unknown", 0)def this(name:String) = this(name,18)def getName:String = _namedef getAge:Int = _agedef setName(name: String):Unit = _name = namedef setAge(age: Int):Unit = _age = agedef play(): Unit = {println("play computer game")}override def toString(): String = s"Student{name=${_name},age=${_age}}" } // 伴生对象:伴生对象是一个单例对象,它与一个类具有相同的名称。 // 通常用于存放与该类相关的静态方法或字段。 object Student{var products:Array[String] = Array("BigData","Cloud")// apply方法常用于作为创建类实例的工厂方法,省去了使用new关键字的麻烦def apply(name: String, age: Int): Student = new Student(name, age)def add(a:Int,b:Int) = a + b }
-
调用
scala">Student.products.foreach(e => print(e + " ")) // 输出静态属性:BigData Cloud val stu = Student("张三", 12) // 省略 new print("\n" + Student.add(1, 5)) // 调用静态方法
四、特质
类似 java 的接口 (interface)
关键字:trait
1、包含字段、方法,亦可包含字段和方法的实现
2、类、单例对象、普通对象都可以扩展特质
3、没有构造器,不能实例化
4、单根继承,借助 with 实现多混入
scala">// 定义抽象类 Animal
abstract class Animal {var _name: String
}// 定义特质 ByFoot
trait ByFoot {def eat(): Unitdef play(): Unit
}// 定义类 Cat,实现了 ByFoot 特质
class Cat extends ByFoot {override def eat(): Unit = println("吃西瓜")override def play(): Unit = println("抓老鼠")
}// 定义类 Dog,继承了 Animal 抽象类并实现了 ByFoot 特质
class Dog(name: String) extends Animal with ByFoot {override var _name: String = nameoverride def eat(): Unit = println("大口吃肉")override def play(): Unit = println("抓蝴蝶")
}
1、动态混入
-
特质类的定义
scala">trait Animal {val name:Stringdef cry():Unit }trait ByFoot {def jog():Unitdef run():Unit } // 动态强制混入特质:只能定义一个强制混入特质,且必须位于类内首行 // self 是 this 的别名 class Penguin {self: Animal => // 强制混入特质语法val brand: String = "企鹅" }// 动态非强制混入特质 with,支持多混入 class Bear(nickName: String) {val _name: String = nickName }
-
调用,动态混入
scala">// 复合类型 Penguin with Animal val penguin: Penguin with Animal = new Penguin() with Animal {override val name: String = "阿童木"override def cry(): Unit = println(s"$brand $name is crying") } penguin.cry()val bear: Bear = new Bear("熊大") with Animal with ByFoot {override val name: String = "狗熊"override def cry(): Unit = println(s"$name ${_name} is crying")override def jog(): Unit = println(s"$name ${_name} is jogging")override def run(): Unit = println(s"$name ${_name} is running") }
2、抽象类 VS 特质
一般【优先使用特质】:
1、抽象类在于多类公共属性和行为的抽象,重点在于封装思想,本质为类,单继承,不支持多混入
2、接口在于一类事物的属性和行为标准定义,重点在于多态思想,支持多混入,动态混入
若需要带参构造,只能使用抽象类
五、内部类
内部类是定义在另一个类内部的类。内部类可以访问外部类的成员,包括私有成员。
内部类的主要优点之一是它们可以更轻松地访问外部类的状态,而不需要显式地传递引用
Java中内部类是【外部类的成员】:
InClass ic = new OutClass.InClass()
Scala中内部类是【外部类对象的成员】:
val oc = new OutClass();
val ic = new oc.InClass();
-
创建
scala">class OutClass(name:String,age:Int,gender:String,school:String,major:String) {class InnerClass(age:Int,gender:String){private var _age:Int = ageprivate var _gender:String = genderdef getAge = _agedef getGender = _genderdef setAge(age:Int) = _age = agedef setGender(gender:String) = _gender = gender}private val _name:String = nameprivate var _in:InnerClass = new InnerClass(age, gender)var _in2:OutClass.Inner2Class = new OutClass.Inner2Class(school, major)def setAge(age:Int) = _in.setAge(age)def setGender(gender:String) = _in.setGender(gender)def setIn(in:InnerClass) = _in = indef setIn2(in2:OutClass.Inner2Class) = _in2 = in2override def toString: String = s"${_name},${_in.getAge},${_in.getGender},${_in2._school},${_in2._major}" } object OutClass{class Inner2Class(school:String,major:String){val _school:String = schoolval _major:String = major} }
调用
scala">val oc = new OutClass("henry",22,"male","xx","通信") oc.setAge(33) oc.setGender("female") println(oc)val in = new oc.InnerClass(30, "female") // 外部类对象.内部类(...) oc.setIn(in)val in2 = new OutClass.Inner2Class("xxx","人工智能") oc.setIn2(in2)
六、样例类
描述【不可变值】的对象
样例类构造参数默认声明为 val,自动生成 getter
样例类的构造参数若声明为 var,自动生成 getter & setter
样例类自动生成伴生对象
样例类自动实现的其他方法:toString,copy,equals,hashCode
样例类伴生对象实现的方法:apply, unapply(用于模式匹配)
普通类的模式匹配案例
scala">case class Student(name:String, age:Int) // 构造参数默认 val
case class Point(var x:Int,var y:Int) // var 需要显式声明
scala">// val obj: Any = Student("张三", 18)
val obj: Any = Point(10, 20)
val info = obj match {case Student(_, 22) => "年龄22"case Student(name, _) if name.startsWith("张") => "姓张"case Point(a, _) => s"$a"
}
println(info)
scala">// 追加伴生对象并实现 apply & unapply
object Point{def apply(x: Int, y: Int): Point = new Point(x, y)def unapply(arg: Point): Option[(Int, Int)] = Some((arg._x,arg._y))
}
七、枚举
单例对象通过继承 Enumeration 实现枚举创建,简单易用,但不可扩展
通常用于定义一个有限取值范围的常量
scala">object WeekDay extends Enumeration {val MON = Value(0)val TUE = Value(1)val WEN = Value(2)val THU = Value(3)val FRI = Value(4)val SAT = Value(5)val SUN = Value(6)
}
scala">val wd = WeekDay.WEN
val info = wd match {case WeekDay.MON => "星期一"case WeekDay.TUE => "星期二"case WeekDay.WEN => "星期三"case _ => "不需要"
}
八、泛型
类型参数化,主要用于集合
不同于 Java 泛型被定义在 [] 中
-
测试类
scala">class GrandFather(name:String) {val _name:String = nameoverride def toString: String = _name } object GrandFather{def apply(name: String): GrandFather = new GrandFather(name) }class Father(name:String) extends GrandFather(name:String) {} object Father{def apply(name: String): Father = new Father(name) }class Son(name:String) extends Father(name:String) {} object Son{def apply(name: String): Son = new Son(name) }class GrandSon(name:String) extends Son(name:String){} object GrandSon{def apply(name: String): GrandSon = new GrandSon(name) }
-
泛型边界定义
scala">// 上边界:T<:A 泛型为某个类型的子类 // 下边界:T>:A 泛型为某个类型的父类 class MyArray[T <: Father](items:T*) {def join(sep:String) = items.mkString(sep) } // Type GrandFather does not conform to 【 upper bound 】 Father of type parameter T val arr:MyArray[GrandFather] = ...class MyArray[T >: Son](items:T*) {def join(sep:String) = items.mkString(sep) } // Type GrandSon does not conform to 【 lower bound 】 Son of type parameter T val arr:MyArray[GrandSon] = ...
-
型变:多态
协变:[+T] 若A是B的子类,则 C[A]为C[B]的子类
逆变:[-T] 若A是B的子类,则 C[B]为C[A]的子类
不变:[T] 默认scala">class MyArray[+T](items:T*) {def join(sep:String) = items.mkString(sep) } // Father 是 Son 的父类,则 MyArray[Father] 就是 MyArray[Son] 的父类 val arr:MyArray[Father] = new MyArray[Son](Son("henry"),Son("ariel"))class MyArray[-T](items:T*) {def join(sep:String) = items.mkString(sep) } // Father 是 Son 的子类,则 MyArray[Son] 就是 MyArray[Father] 的子类 val arr:MyArray[Son] = new MyArray[Father](Son("henry"),Son("ariel"))class MyArray[T](items:T*) {def join(sep:String) = items.mkString(sep) } // 所有泛型都必须为 Son val arr:MyArray[Son] = new MyArray[Son](Son("henry"),Son("ariel"))
九、隐式类
用implicit关键字修饰的类,扩展其主构造器唯一参数类型的功能
只能在类、Trait、对象(单例对象、包对象)内部定义
构造器只能携带一个非隐式参数
隐式类不能是 case class
在同一作用域内,不能有任何方法、成员或对象与隐式类同名
隐式类必须有主构造器且只有一个参数
-
隐式参数:隐式传入
场景:多个函数共享同一个参数,选择柯里化,将最后一个列表设计为该共享参数的唯一参数,并将该参数设置为 implicit
scala">implicit order:Ordering[Int] = Ordering.Int.reverse val sorted = Array(8,1,3,2,5).sorted(implicit order:Ording[Int])
-
隐式函数:隐式类型转换
scala">implicit def strToInt(str:String) = str.toInt val a:Int = "12"
-
隐式类:扩展
scala">// 字符串的方法扩展,而在 Java 中 String 是final的,无法扩展 implicit class StrExt(str:String){def incr() = str.map(c=>(c+1).toChar)def isEmail = str.matches("\\w+@[a-z0-9]{2,10}\\.(com(.cn)?|cn|edu|org)") }
scala">val a:String = "12665473@qq.com" val incr: String = a.incr val isEmail: Boolean = a.isEmail
十、包与包对象
包命名规则:字母、数字、下划线、点,不能以数字开头,在【一个类文件中可以定义多个并列的包】
导包的不同方式scala">import com.org.Person // 方便使用类 Person import com.org._ // 方便使用 com.kgc 包中的所有类 import com.org.Person._ // 方便使用类 Person 中的所有属性和方法 import com.org.{Person=>PS,Book} // 只导入包中 Person和Book,并将Person重命名为PS
不同于Java:import 导包语句可以出现在任意地方
可以导入包、类、类成员
单个文件多包结构:资源按包名语义分类存放,方便管理和使用
测试样例,import 导包语句可以出现在任意地方
scala">package cha03{import cha03.util.Sorts // 导包object PackageTest {def main(args: Array[String]): Unit = {val array: Array[Int] = Array(3, 1, 5, 4, 2)Sorts.insertSort(array)array.foreach(println)}}
}package cha03.util{object Sorts{def insertSort(array: Array[Int]): Unit ={import scala.util.control.Breaks._ // 导包 for(i<- 1 until array.length){val t = array(i)var j = i-1breakable({while (j>=0){if(array(j)>t){array(j+1) = array(j)}else{break()}j-=1}})array(j+1) = t}}}
}
包对象
包中可以包含:类、对象、特质…
包对象可以包含:除了类、对象、特质外,还可以包含变量和方法
scala">package test {// 导包(包在下面定义)import test.util.Constants._ // 导入 test.util.Constants 包对象中的所有成员,包括 PI 和 getQuarter 方法import test.util.Constants.{DataFormat => DF} // 导入 test.util.Constants 包对象中的 DataFormat 类,并将其重命名为 DFobject PackageTest {def main(args: Array[String]): Unit = {println(PI * 2 * 2)println(getQuarter(5))val format: DF = DF(2024, 3, 29)println(format.stdYMD())println(format.stdFull())println(format.timestamp())}}
}package test.util {import java.util.Calendar// 包对象package object Constants {// 变量val PI: Float = 3.14f// 方法def getQuarter(month: Int) = (month - 1) / 3 + 1// 类class DataFormat(year: Int, month: Int, day: Int,hour: Int, minute: Int, second: Int,millis: Int) {private var _year: Int = yearprivate var _month: Int = monthprivate var _day: Int = dayprivate var _hour: Int = hourprivate var _minute: Int = minuteprivate var _second: Int = secondprivate var _millis: Int = millisdef this(year: Int, month: Int, day: Int) {this(year, month, day, 0, 0, 0, 0)}def stdYMD(): String = s"${_year}-${_month}-${_day}"def stdFull(): String = s"${_year}-${_month}-${_day} ${_hour}:${_minute}:${_second}.${_millis}"def timestamp(): Long = {val cld = Calendar.getInstance()cld.set(_year, _month, _day, _hour, _minute, _second)cld.set(Calendar.MILLISECOND, 555)cld.getTimeInMillis}}object DataFormat {def apply(year: Int, month: Int, day: Int,hour: Int, minute: Int, second: Int, millis: Int): DataFormat= new DataFormat(year, month, day, hour, minute, second, millis)def apply(year: Int, month: Int, day: Int): DataFormat= new DataFormat(year, month, day)}}
}
练习
【 练习一 】需求说明假设类Book有属性title和author(多个),books是Book的列表实现Book类,同时使用主构造器与辅助构造器实现Book的伴生对象,使用伴生对象创建Book实例创建books,使用List[Book]初始化5个以上Book实例找出books中书名包含“xxx”的书,并打印书名找出books中作者名以“xxx”打头的书,并打印书名
val list = List(Book("武侠:最强小保安",Array("张三","李四","王五")),Book("都市:上门赘婿",Array("阿强","洞冥福","花花")),Book("武侠:翔龙会",Array("阿庆嫂","黄世仁")),Book("都市:缘起",Array("徐世明","张丘月")),Book("武侠:小李飞刀",Array("王栋","李宏","张明")),
)
【 练习二 】需求说明现在Book拥有电子版本,可以在多终端上播放属性:作者author:String,书名bookName:String,类型bookType:String内容chapters:Array[String]方法:简介resume():Unit定义Ebook特质方法:play()使Book动态混入Ebook特质,实现play()方法
val chapters = Array("“春游浩荡,是年年寒食,梨花时节。白锦无纹香烂漫,玉树琼苞堆雪。静夜沉沉,浮光霭霭,冷浸溶溶月。人间天上,烂银霞照通彻。浑似姑射真人,天姿灵秀,意气殊高洁。万蕊参差谁信道,不与群芳同列。浩气清英,仙才卓荦,下土难分别。瑶台归去,洞天方看清绝。”\n\n 作这一首《无俗念》词的,乃南宋末年一位武学名家,有道之士。此人姓丘,名处机,道号长春子,名列全真七子之一,是全真教中出类拔萃的人物。《词品》评论此词道:“长春,世之所谓仙人也,而词之清拔如此”。这首词诵的似是梨花,其实词中真意却是赞誉一位身穿白衣的美貌少女,"
)
val book = Book("张三","倚天屠龙记","武侠小说")