文章目录
Scala 02——Scala OOP前序类1. 类的基本结构2. 普通类和数据类的区别 继承1. extends2. override 抽象类抽象类的特点 单例对象1. 定义2. 场景3. 方法3.1 方法定义3.2 方法调用 特质1. 抽象类和特质的区别2. 强制混入语法3. 静态混入和动态混入 内部类Java和Scala内部类的区别当类与对象存在伴生关系时,类的写法内部类定义在实例中(①):内部类定义在伴生对象中(②): 样例类枚举定义 泛型1. 定义2. 泛型边界3. 协变、逆变、不变 包与包对象1. 命名规则:2. 包的作用3. 导包的不同方式4. 多级包的导包5. 包对象5.1 定义5.2 包对象的意义
Scala 02——Scala OOP
前序
为什么说Scala是纯粹的OOP?
不支持基本类型,一切皆为对象:Byte,Int…
Scala会将基本类型视为对象,还是在JVM上运行,自动与Java的基本数据类型完成拆装箱的操作。不支持静态关键字:static
Java和Scala的静态概念的区别
Java Java的静态成员:在Java中,静态成员(属性或方法)是指属于类本身而非类的实例的成员,静态成员可以在没有创建类的实例的情况下被访问。 Scala Scala的静态类型系统:Scala被称为静态语言,与其属性和方法是否属于对象无关,是因为在Scala中,所有的类型检查都是在编译时完成的,而非运行时。Scala的单例对象:为了提供类似于Java中静态成员的功能,Scala引入了单例对象。编译时检查和运行时检查的区别
编译时检查:在程序运行之前发现错误【Java Mysql Scala…】 类型检查语法检查结构检查访问规则检查 运行时检查:在运行时检查,通常由执行环境执行(如虚拟机或解释器)【解释型语言 或 脚本…】静态和动态的区别
静态是编译时检查,动态是运行时检查
expression = "3 * 7 + 2"result = eval(expression)print(result) # 输出 23
对于静态语言来说,expression
的结果是一个内容是表达式的字符串。
而对于动态语言来说(以Python的eval()
函数为例),eval()
函数允许执行一个字符串形式的表达式,并返回表达式的结果。是因为在运行前没有进行类型检查,编译器视其为表达式而非文本。
支持类型推断,类型预定,动静结合
动静结合
类型预定
在运行时保留类型信息类型推断
泛型编程中的不变、协变、逆变
泛型类型
定义
容器类
列表,如List[T]
集合,如Set[T]
映射,如Map<K,V>
函数和方法
def func[T](x:T):t = x
不变
定义
泛型类型 G[A] 和泛型类型 G[B] 的关系与类型 A 和类型 B的关系无关。
协变
定义
如果类型 A 是类型 B 的子类型,泛型类型 G[A] 是泛型类型 G[B] 的子类型。
假设我们有一个泛型类Container[+A]
,其中A
是一个协变类型参数。如果Dog
是Animal
的子类,那么Container[Dog]
也应该被看作是Container[Animal]
的子类型。这意味着你可以用Container[Dog]
的实例去替换Container[Animal]
的实例。
class Animalclass Dog extends Animalclass Container[+A]val dogs: Container[Dog] = new Container[Dog]val animals: Container[Animal] = dogs // 正确,因为Container是协变的
适用场景
作为输出,可以安全地从Container[Animal]
中读取Animal
类型的对象,不管容器实际包含的是Dog
还是哪种Animal
的子类型。
逆变
定义
如果类型 A 是类型 B 的子类型,泛型类型 G[B] 是泛型类型 G[A] 的子类型。
假设我们有一个泛型类Container[-A]
,其中A
是一个逆变类型参数。如果Dog
是Animal
的子类,那么Container[Animal]
应该被看作是Container[Dog]
的子类型。这意味着你可以用Container[Animal]
的实例去替换Container[Dog]
的实例。
class Animalclass Dog extends Animalclass Printer[-A] { def print(value: A): Unit = println(value)}val animalPrinter: Printer[Animal] = new Printer[Animal]val dogPrinter: Printer[Dog] = animalPrinter // 正确,因为Printer是逆变的
适用场景
方法是通用的,面向各种不同类型,可以用更宽泛的类型实例替代更具体的类型实例。
类
1. 类的基本结构
类的本质就是模板,我们根据以下代码模板学习Scala类的基本结构:
主构造器(构造参数)属性方法辅助构造器重写方法 toString的重写接近于JSON
的表达方式 // 主构造器:类自身class Point(x:Int,y:Int){ // 没有构造方法,通过构造参数列表实现对象创建 // 属性:必须赋初值,且赋的是主构造器的参数 private var _x:Int = x // 私有属性的命名方式通常为`{_参数}` private var _y:Int = y // 方法 def updatePoint(x:Int,y:Int): Unit = { _x = x _y = y } // 辅助构造器 // 辅助构造器必须调用主构造器 // 辅助构造器中有两个this,第一个this是辅助构造器的名称,第二个this是调用主构造器。 def this() = this(-1,-1) // getter 和 setter 方法 def getX=_x def getY=_y def setX(x:Int)={ _x=x } def setY(y:Int)={ _y=y } // 重写方法 override def toString: String = { s"(X:${_x},Y:${_y})" }}
修饰符 | 类(class) | 伴生对象(object) | 子类(subclass) | 同包(package) | 全局(world) |
---|---|---|---|---|---|
default(public) | Y | Y | Y | Y | Y |
protected | Y | Y | Y | N | N |
private | Y | Y | N | N | N |
2. 普通类和数据类的区别
普通类:可以有主构造器,也可以有辅助构造器,可以有属性,可以有方法,可以有重写方法,可以有get、set方法
数据类:只能有主构造器,不能有辅助构造器,只能有属性,不能有方法,不能有重写方法,不能有get、set方法
继承
1. extends
class ColorPoint(x:Int,y:Int,color:Boolean) extends Point(x:Int,y:Int) { var _color:Boolean = color private def getColor = if(_color) "red" else "black" override def move(offsetX: Int, offsetY: Int): Unit = { _x += offsetX*2 _y += offsetY*2 println(s"$getColor point moved to {${_x},${_y}}") } override def toString: String = s"$getColor point ${super.toString}"}val cp = new ColorPoint(0,0,true)println(cp)cp.move(12,25)
2. override
在重写方法时:
子类重写父类抽象方法推荐添加override
关键字子类重写父类非抽象方法必须添加override
关键字 抽象类
abstract class Shape { // 抽象方法 def draw() // 普通方法 def show:Unit = println("this is Shape")}
抽象类的特点
可以有抽象方法(Scala中没有方法体即为没有=号及=号后面的部分
)无法被实例化使用abstract
关键字修饰 单例对象
1. 定义
关键字:object
单例对象用于管理共享资源或共通逻辑
,封装静态工具方法
,提供了便携创建其他实例的工厂方法
可以直接通过单例对象名.属性或方法
来访问,类同于Java的静态属性和方法
采取惰性模式,第一次被访问时被创建
main
方法必须定义在单例对象中,才能被JVM
识别
同名的类和单例对象形成绑定关系,并称之为伴生类和伴生对象
object Util{ var PI:Float = 3.14f var count:Int = 0 def resume:Unit = println("this is my resume.")}// 调用Util.resume
class Commodity(sku:String,price:Float) { private val _sku:String = sku private val _price:Float = price def getSKU:String={ _sku } def getPrice:Float={ discount(_price) } // 伴生类自由访问伴生对象中的所有变量 def getSalePrice=discount(_price) override def toString:String={ s"SKU:${_sku},PRICE:${_price}" }}object Commodity{ // 商品折扣 private var _discount:Float = 1.0f // 包裹 def apply(sku:String,price:Float):Commodity = new Commodity(sku,price) // 拆解:伴生对象中可以自由访问伴生类中所有资源 def unapply(arg:Commodity):(String,Float) = (arg.getSKU,arg.getPrice) // 自定义方法 def setDiscount(discount:Float)=_discount=discount def discount(price:Float)=price*_discount}
2. 场景
工具类
3. 方法
3.1 方法定义
apply
方法
def apply(sku:String,price:Float):Commodity = new Commodity(sku,price)
unapply
方法
def unapply(arg:Commodity):(String,Float) = (arg.getSKU,arg.getPrice)
3.2 方法调用
object Person { // apply方法允许不使用new关键字就能创建Person实例 def apply(name: String, age: Int): Person = new Person(name, age) // unapply方法支持模式匹配,提取Person对象的属性 def unapply(p: Person): Option[(String, Int)] = { if (p != null) Some((p.name, p.age)) else None }}// 使用apply方法隐式创建Person对象val alice = Person("Alice", 25) // 实际调用 Person.apply("Alice", 25)// 使用模式匹配,其中unapply方法被隐式调用alice match { case Person(n, a) if a >= 18 => println(s"$n is an adult, aged $a.") case Person(n, _) => println(s"$n is a minor.")}
特质
1. 抽象类和特质的区别
抽象类可以传构造参数,而特质不能传构造参数;抽象类可以有构造代码(抽象属性结合参数的构造),而特质没有构造代码。
一个类可以继承多个特质但只能继承一个抽象类。
抽象类针对的是高复用性的功能,而特质更多是针对定制化的功能。
抽象类可以提供方法的默认实现,减少了子类重复相同代码的需要。
2. 强制混入语法
定义:要求其子类必须重写这个特质的方法,并允许在基类中使用特质的方法
abstract class Animal(brand:String){ var name:String val _type:String = brand def roar:Unit def me = s"${_type}:$name"}trait ByFoot { def walk() def run()}trait ByFly{ def fly()}trait BySwim{ def swim()}class Cat(nickname:String) extends Animal("猫") with ByFoot { override var name: String = nickname override def roar: Unit = println(me+"喵喵喵") override def walk: Unit = println(me+"悠闲地漫步") override def run: Unit = println(me+"正在快速跑")}class Bird(nickname:String) extends Animal("鸟") with ByFly with ByFoot { override var name: String = nickname override def roar: Unit = println(me+"叽叽叽") override def fly(): Unit = println(me+"正在飞") override def walk(): Unit = println(me+"正在闲逛") override def run(): Unit = println(me+"正在快速跑")}class Fish() extends Animal("鱼"){ self:BySwim=> // 等同于 this:BySwim=> override var name: String = _ override def roar: Unit = ???}
3. 静态混入和动态混入
静态混入和动态混入的核心区别:
静态混入:在创建对象之前,就混入特质(该类事物的通用
特征)动态混入:在创建对象之后,再混入特质(特殊事物的特殊
特征) // 静态混入val bird = new Bird("小雀")bird.run()bird.fly()bird.walk()bird.roar// 动态混入val fish3 = new Fish() with BySwim with ByFoot {override def swim(): Unit = ...override def walk(): Unit = ...override def run(): Unit = ...}
内部类
Java和Scala内部类的区别
Java中内部类是【外部类的成员】InClass ic = new OutClass.InClass()
Scala中内部类是【外部类对象的成员】 val oc = new OutClass();val ic = new oc.InClass();
当类与对象存在伴生关系时,类的写法
class OutClass { // ① /*class InClass{ override def toString: String = "InClass" }*/ private val in:InClass = new InClass override def toString: String = s"OutClass{${in}}"}object OutClass{ // ② /*class InClass{ override def toString: String = "InClass" }*/}
内部类定义在实例中(①):
这意味着InClass
与OutClass
的一个具体实例关联。
val oc = new OutClassprintln(oc)val ic: oc.InClass = new oc.InClass()
内部类定义在伴生对象中(②):
这里的OutClass.InClass
是一个整体,伴生对象能够通过伴生对象名称直接获取内部的属性或方法。
val oi: OutClass.InClass = new OutClass.InClass
样例类
/*描述【不可变值】的对象样例类构造参数默认声明为 val,自动生成 getter样例类的构造参数若声明为 var,自动生成 getter & setter样例类自动生成伴生对象样例类自动实现的其他方法:toString,copy,equals,hashCode样例类伴生对象实现的方法:apply, unapply(用于模式匹配)*/// 普通类的模式匹配案例case class Student(name:String, age:Int)// 构造参数默认 valcase class Point(var x:Int,var y:Int)// var 需要显式声明
枚举
定义
单例对象通过继承Enumeration实现枚举创建,通常用于定义一个有限取值范围的常量。
class EnumTest { 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) } val d = WeekDay.THU val info: String = d match { case WeekDay.MON => "Monday" case WeekDay.TUE => "Tuesday" case WeekDay.WEN => "Wednesday" case WeekDay.THU => "Thursday" case WeekDay.FRI => "Friday" case WeekDay.SAT => "Saturday" case WeekDay.SUN => "Sunday" }}
泛型
1. 定义
类型参数化,主要用于集合。
不同于 Java 泛型被定义在 [] 中,Scala泛型更为自由
// 也可以定义在可变参数中class MyArray[T](items:T*) { def join(sep:String) = items.mkString(sep)}
2. 泛型边界
[T<:F] 表示 T 必须是F的子类
[T>:F] 表示 T 必须是F的父类
class Fclass S extends Fclass Many[T<:F] (t:T){ ...}
3. 协变、逆变、不变
协变:[+T] 若A是B的子类,则 C[A]为C[B]的子类逆变:[-T] 若A是B的子类,则 C[B]为C[A]的子类不变:[T] 默认包与包对象
1. 命名规则:
可以以字母数字下划线点
开头,不能以数字
开头
2. 包的作用
命名空间管理:每个包提供了一个独立的命名空间作用域控制:包允许细粒度的访问控制逻辑分隔:将功能相似的类和对象放在同一个包中,使代码结构更清晰3. 导包的不同方式
import
导包语句可以出现在任何地方import
可以导入包、类、类成员 import com.kgc.Person// 方便使用类 Personimport com.kgc._ // 方便使用 com.kgc 包中的所有类import com.kgc.Person._// 方便使用类 Person 中的所有属性和方法import com.kgc.{Person=>PS,Book}// 只导入包中 Person和Book,并将Person重命名为PS
4. 多级包的导包
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-1 breakable({ while (j>=0){ if(array(j)>t){ array(j+1) = array(j) }else{ break() } j-=1 } }) array(j+1) = t } } }}
5. 包对象
5.1 定义
包中可以包含:类、对象、特质…
包对象中可以包含:除了类、对象、特质外,还可以包含变量和方法
5.2 包对象的意义
常用的函数、常量和类型可以在包对象中定义,这允许在相同包的任何地方访问这些共享资源。
package cha03.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 = year private var _month:Int = month private var _day:Int = day private var _hour:Int = hour private var _minute:Int = minute private var _second:Int = second private var _millis:Int = millis def 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) }}