类和对象
Scala中,一切皆对象
类
- scala中, 类并不声明public,所有的类都默认是public的,在定义类时添加public会报错
- 一个Scala源文件中可以定义多个class,并且这些 class都是public的,Scala在编译的时候,会编译成多个.class文件
属性
- 在声明一个属性时, Scala底层属性是private的,同时会生成两个方法,一个是以属性名命名的相当于getter,另一个是属性名+_$eq命名,相当于setter
- 所以在调用类属性时,不是直接调用的是类属性,而是调用的类属性的get或set方法
//以class声明的类, Scala只会生成一个.class文件
class Cat{
var name:String = _ //等同于 private String name = null; 同时生成了name()和name_$eq()两个方法
}
属性可以不用写数据类型,但必须初始化,数据类型可以根据类型自动推断
属性如果赋值为null,必须标明类型
- Scala类的主构造器的形参未使用任何修饰符修饰, 那么这个参数是局部变量,
- 如果参数使用val声明, 那么Scala会将参数作为类的私有只读属性,即没有set只有get
- 如果参数使用var声明, 那么这个参数是私有的,但是get和set都有
class Person(val inName : String, var inAge:Int, inBirth:String){
//这个时候inName参数是一个私有只读属性, inAge是私有的可读写的, inBirth是类的内部变量外部无法访问,
//只能是主构造器,辅助构造器不能这么写
var name : String = inName
var age : Int = inAge
var birth : String = inBirth
def this(name:String){
this("name",0,"birth")//也是为了去先调用父类的构造器
this.name=name
}
}
val p = new Person("jack",0,"birth")
println(p.inName)//可以调用,但是不能set
println(p.inAge)//get\set都可以
//println(p.inBirth)//无法调用这个参数
- @BeanProperty注解, 可以给属性自动生成getXxx(),setXxx()方法,且与原本的类似属性名+_$eq等方法没有冲突,两者都可以使用
@BeanProperty var name : String = inName
构造器
//语法
class 类名(形参列表){//主构造器
def this(形参列表){//辅助构造器,辅助构造器可以有多个
}
}
//eg:
class Person(inName : String, inAge:Int){
var name : String = inName
var age : Int = inAge
def this(name:String){
this("name",0)//也是为了去先调用父类的构造器
this.name=name
}
}
- Scala构造器有主构造器和辅助构造器
- 构造器没有返回值,主构造器的声明直接放置于类名之后
- 主构造器会执行类定义中的所有语句, 即执行除函数外的所有语句
- 辅助构造名是this, 多个辅助构造器通过不同参数区分
- 辅助构造器执行前必须先调用主构造器, 即在辅助构造器内的第一行语句显示调用主构造器,可以是直接也可以是间接
- 如果想让主构造器变成私有的,可以在()之前加上private, 辅助构造器变成私有在def之前加private
class Person private(){
private def this(name:String){
}
}
- 辅助构造器的声明不能和主构造器声明一致
对象
- 创建对象:
val cat:Cat=new Cat
如果不希望改变对象的引用,应该声明为val性质的, 设计者推荐使用val,因为一般来说,我们只是去改变对象属性值,而不是改变对象的引用
Scala在声明对象的时候也可以使用类型推断, 所以类型声明可以省略, 但是当类型和后面new的对象存在继承时,不能省略
- 对象创建流程分析:
- 加载类信息(属性信息,方法信息)
- 在内存中(堆)开辟内存空间(大小根据属性数据类型大小来定)
- 使用父类构造器对父类进行初始化
- 使用主构造器对属性进行初始化
- 使用辅助构造器对属性进行初始化(如果使用的是辅助构造器进行new)
- 将创建对象的内存地址赋值给引用变量
包
- Scala的包的功能更加强大, 但同样的使用起来也更复杂一些
- 作用同Java一样, 区分相同名字的类, 管理类, 控制访问范围
- Scala中包名和源码所在的系统文件目录结构上可以不一致, 但是编译后的字节码文件路径会和包名保持一致(这个工作由编译器完成)
- 当Scala类中的包名改变后, 再次编译时, Scala会将之前编译的class文件清除掉, 重新生成新的class文件
- Scala会自动引入常用的包: java.lang.*, scala包(基本数据、List等), Predef包(print、String、Map等), 但是其子包有的还是需要自己导入
注意事项:
- Scala使用包时写法:
//常用写法 package com.ty.scala class scalapackage {} //等同于 package com.ty package scala//分开写 class scalapackage {} //等同于 package com.ty{ //加个大括号 package scala{ class scalapackage {} } }
- 也就是说Scala支持在一个文件中,创建多个包,建议嵌套的package不要超过三层,已经给各个包创建类等, 类名和文件名也无关系
- 作用域原则:
- 可以直接向上访问, 即Scala中子包直接 访问父包中的内容,大括号体现作用域,
- 在子包和父包中类重名时, 采用就近原则
- 父包访问子包的内容时,需要import
- 包名可以相对也可以绝对, 比如在访问BeanProperty的绝对路径是:
_root_.scala.beans.BeanProperty
, 在一般情况下, 我们使用相对路径来引入包, 只有当包名冲突时,才会使用绝对路径来处理
包对象
包可以包含类, 对象, 特质trait, 但不能包含函数或变量的定义, 这是Java虚拟机的局限, 为了弥补这一点, scala提供了包对象的概念来解决这个问题
//包对象声明,
package object scala{}
- 每一个包有且只能有一个包对象, 要在父包中定义
- 包对象名字需要和包名一样
- 在包对象中可以定义变量和方法
- 在包中可以直接使用包对象中定义的变量和方法
- 包对象在底层会生成两个类, package.class和 package$.class
包的可见性与访问修饰符
- 属性访问权限默认时, 从底层看是private的, 但是提供了类似get/set的方法,是public权限的
- 当方法访问权限默认时, 默认为public
- private同Java一样, 添加了private后 get/set的方法也是private的
- protected,scala中,该权限只能子类访问,同包无法访问
- scala中没有public关键字, 不能使用public显示修饰属性和方法
- 包访问权限: 在访问权限后加包名 private[scala] val name=…
- 增加包访问权限后, private仍然起作用, 不仅同类可以使用, scala中包下其他类包括子包也可以使用
包的引入
-
在scala中, import语句可以出现在任何地方, 并不仅限于文件顶部, import语句的作用一直延伸到包含该语句的块末尾, 只用在需要的地方引入,缩小引入包的范围, 提高效率
def func(): Unit ={ import scala.collection.mutable.{HashMap,HashSet} }
-
Java中如果想要导入包中所有的类, 可以使用*, scala中使用 _
import scala.collection.mutable._
-
如果不想引入某个包中全部的类, 而是其中的几个类, 可以采用选取器{}
import scala.collection.mutable.{HashMap,HashSet}
-
如果 引入的多个包有相同的类, 那么可以将不需要的类进行重命名区分
import scala.collection.mutable.{HashMap=>ScalaHashMap,HashSet} import java.util.{HashMap=>JavaHashMap} var map=new ScalaHashMap() var map1=new JavaHashMap()
-
如果某个冲突的类根本就不会用到, 那么这个类 可以隐藏掉
import java.util.{HashMap=>_,_}//引入util包中除HashMap外的所有类
继承与抽象类
继承
-
子类继承父类所有的方法和属性, 只是私有属性和方法无法访问
-
重写
- 使用override关键字修饰, 调用父类方法用super关键字
override def func(){ super.func() }
-
classOf\isInstanceOf\asInstanceOf
- classOf
- 获取类的class
- isInstanceOf
- 判断实例是否是类的实例
- asInstanceOf
- 将实例转换为类的实例,可以向下转型
- classOf
-
子类中只有主构造器才可以调用父类中的构造器,辅助构造器不能直接调用父类的构造器, 在scala中,不可以super()调用父构造器
-
子类调用父类其他构造器
class children(name:String) extends Parent(name){}
-
Scala中, 子类可以覆写父类中的字段, 字段前添加override关键字即可, 看起来是字段的覆写, 实质上是方法的覆写
- 方法只能重写另一个方法
- 字段只能重写另一个字段或不带参数的同名方法(相当于重写的字段自动生成的同名方法将其覆写了)
- 字段重写只能是val的, var的变量不可覆写
- 如果要重写var变量, 那么其父类中的同名变量不能初始化(即抽象属性), 而且其父类必须是抽象的, override可以省略
- 原因: 抽象属性在编译成字节码时,自动生成的get/set方法也是抽象的
抽象类
-
Scala中, 用abstract标记的类就是抽象类, 抽象方法不需要标记(标记了运行时还会报错), 只要省掉方法体即可, 抽象字段就是没有初始值的字段
abstract class A{//抽象类 var s:String//抽象字段 def getString()///抽象方法 } class B extends A{ override var s="bbn" override def getString(): Unit = { } }
-
抽象类的价值更多在于设计,是设计者设计好后, 让子类继承并实现抽象类
-
抽象方法和属性不能用private, final来修饰, 因为这些关键字都是和重写\实现相违背的
-
子类重写抽象方法和抽象字段可以不用写override字段
伴生对象
-
Scala的设计者使用伴生类和伴生对象来代替static关键字
-
将非静态的部分放在伴生类中, 将静态的部分放入伴生对象中
-
在一个文件中存在class 和object 且同名那么class修饰为伴生类, 将非静态的内容写入该类, object修饰的是伴生对象,将静态的内容写入该对象
-
对于伴生对象的属性或方法, 可以通过伴生对象名之间访问
object TestPackage{ def main(args: Array[String]): Unit = { print(Test.name)//之间访问伴生对象的属性 } } class Test{ } object Test{ var name:String="aaa" }
apply方法
- 在伴生对象中定义apply方法, 就可以直接使用类名来创建对象实例而不用new关键字了
object TestPackage{
def main(args: Array[String]): Unit = {
val test=Test()//自动调用apply方法返回一个实例对象
print(test.age)
}
}
class Test{
var age=20
}
object Test{
var name:String="aaa"
def apply(): Test = new Test()
}
接口与特质
-
Scala中使用特质trait来代替接口
-
Scala中接口可以当做trait使用
object Test extends Comparable[Test]{ override def compareTo(o: Test): Int = { 1 } }
-
使用特质:
-
使用多个特质:
使用with连接多个特质
object Test extends Comparable[Test] with Serializable { override def compareTo(o: Test): Int = { 1 } }
-
如果该类存在父类
object Test extends Parent with Comparable[Test] with Serializable { override def compareTo(o: Test): Int = { 1 } }
-
-
特质可以同时拥有抽象方法和具体方法,一个类可以实现或继承多个特质
动态混入特质
- 除了可以在类声明时继承特质以外, 还可以在构建对象时,混入特质,扩展目标类功能
object TestTrait{
def main(args: Array[String]): Unit = {
val value = new TraitTest() with Serializable//动态混入
}
}
class TraitTest{
}
object TraitTest extends Comparable[Test] {
def apply(): TraitTest = new TraitTest()
override def compareTo(o: Test): Int = {
1
}
}
叠加特质
-
构建对象的同时如果混入多个特质, 称之为叠加特质, 特质声明顺序从左到右, 方法执行顺序从右到左(就是创建类实例时, 从左向右加载, 但是当类实例调用方法时, 是从右向左调用的 )
-
Scala中如果调用super,并不是表示调用父特质的方法, 而是向前面继续查找特质, 如果找不到,才会去父特质中去查找
-
如果想要调用具体特质的方法可以
super[特质名(需要是直接超类)]
去调用 -
可以理解为,with关键字将两个特质做成了父子关系,即
object TraitTest extends Comparable[Test] with Serializable //可以看做是 //Serializable先继承了Comparable特质. //然后TraitTest再继承了Serializable //即TraitTest-> Serializable -> Comparable //即可以理解为叠加特质其实就是将所有的特质作为父子相连
-
叠加特质时
- 如果混入该特质的类,已经继承了另一个类(A), 则要求A是特质超类的子类
- 即混入的特质需要具有相同父类,或者混入的特质没有同名方法
-
如果在子特质中重写/实现了一个父特质的抽象方法, 但是同时调用了super, 这时这个方法不是完全实现, 因此需要声明为abstract override, 这时super调用的方法就和动态混入顺序有密切关系
trait T1{ println("T1") def t() } trait T2 extends T1{ println("T2") abstract override def t(): Unit ={//声明为abstract override println("t2 func") super.t()//这个可以调用with前面的特质的方法,不一定非要调用父类方法 } }
-
富接口: 特质中既有抽象方法又有非抽象方法
-
自身类型: 主要是为了解决特质的循环依赖问题, 同时可以确保特质在不扩展某个类的情况下, 依然可以做到 限制混入该特质的类的类型
class A extends Exception{ } trait Logger1 { this: Exception =>//要求混入Logger1特质的类必须是Exception的子类 def log(): Unit ={ print(getMessage) } }
内部类
-
内部类访问外部类属性:
def main(args: Array[String]): Unit = { val b =new B() val c = new b.C() c.printB() c.printd() } class B { //给B取别名为d,内部类可以通过该别名直接访问属性,必须在第一行 d=> var name:String ="b" private var sal:Double =1.2 class C{ def printB(): Unit ={ println(B.this.name)//访问外部类属性 println(B.this.sal) } def printd(): Unit ={ println(d.name)//通过别名访问外部类属性 println(d.sal) } } }
-
类型投影
def main(args: Array[String]): Unit = { val b =new B() val b1 =new B() //默认情况下, 内部类实例和创建该内部类实例的外部对象关联 val c = new b.C() val c1 = new b1.C() c1.printSelf(c1)//可以传值,必须是b1.C的类型 c1.printSelf(c)//不可以 //使用类型投影 c1.printSelf2(c1) c1.printSelf2(c)//此时就可以使用其他b实例创建的内部类实例了 } class B { //给B取别名为d d=> var name:String ="b" private var sal:Double =1.2 class C{ //传入的C必须是自己本身 def printSelf(c:C): Unit ={ println(c) } //传入的C必须是自己本身 def printSelf2(c:B#C): Unit ={ println(c) } } }
隐式类、隐式转换、隐式函数
-
隐式转换函数是以implicit关键字声明的带有单个参数 的函数, 这种函数会自动应用, 将值从 一种类型转换成另一种类型
//将double转换为int implicit def doubleToIntFunction(d:Double): Int ={ d.toInt } var num:Int = 3.5//数值在IDEA中会带下划线,表示使用了隐式转换
-
隐式转换函数和函数名无关, 至于参数类型和返回值类型有关
-
隐式转换函数可以有多个,但需要保证在当前环境下,参数和返回值相对的只有一个
-
隐式转换骚操作:
class Mysql{ def insert(): String ={ "insert" } } class Delete{ def delete(): String ={ "delete" } } def main(args: Array[String]): Unit = { implicit def f2(d:Mysql): Delete ={ new Delete } val mysql=new Mysql mysql.insert() mysql.delete()//mysql可以直接使用delete方法 }
-
隐式变量
将某个形参标记为implicit,编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数
class Mysql{ implicit var name: String ="mysql" def insert(implicit name2:String): String ={ println(name2) } }
-
隐式类
Scala2.10后提供了隐式类, 可以用implicit声明类.
- 特点:
- 所带的构造参数有且只能有一个
- 隐式类必须被定义在类或伴生对象或包对象内
- 隐式类不能是case class
- 作用域内不能有与之相同的标识符
class D{ def printD():String ={ println("D") "D" } } implicit class E(d:D){ def printE(): Unit ={ println(d.printD()+" E") } } def main(args: Array[String]): Unit = { val d =new D d.printE() }
- 特点:
-