第二讲 Scala面向对象
一、类的基本操作
(一)、类的定义
1、类的定义
类或者类型,就是对客观的一类事物的抽象。用一个class关键字来描述,在这个类中可以拥有这一类事物的属性,行为等等。
或者说就是用计算机的语言来描述的一类事物,就是类,在java,scala中都是用关键字class来标识。
因为类是对一类事物的抽象,所以不具备具体的行为执行能力,要想完成具体的操作,就需要使用该类的实例或者对象。
//创建scala中的一个类
class Dog {
//用val修饰的变量是只读属性,有getter但没有setter
//(相当与Java中用final修饰的变量)
val id = "9527"
//用var修饰的变量既有getter又有setter
var age: Int = 18
// _代表初始值,使用时属性必须指定类型
var color:String = _
//类私有字段,只能在伴生对象和类的内部使用
private var name: String = "唐伯虎"
//对象私有字段,访问权限更加严格的,当前对象的字段只能被Person类的方法访问到
//对象私有字段,只能在类的内部使用
private[this] val pet = "小强"
//举例
def keepPet()={
println(pet)
}
}
object enter{
def main(args: Array[String]): Unit = {
val d = new Dog
//调用属性
println(d.id)
println(d.age)
//调用方法
d.keepPet()
}
}
2、getter/setter操作
//getter
println(dog leg)
//setter
//对象.属性_=(值)
dog.leg_=(10)
println(dog currentLeg)
在Scala中使用@BeanProperty注解来给成员变量提供getter/setter,该注解和private访问权限修饰符不可共存.
这种方式定义类似java bean的操作,在Scala中几乎不用,Java bean的主要作用:
说白了就是在各个业务线/层之间传递数据,Scala提供了另外的一种结果来模拟Java中的bean--case class(样例类,样本类)
class Student {
@BeanProperty var name:String = _
private var age = 13
}
(二)、类的构造
1、Java中的构造器
类的构造函数,在java中一个类的构造函数(器),可以分为有参构造器或者无参的构造器,或者默认构造器的说法。所谓默认构造器指的就是,用户不指定/创建相关构造器,而虚拟机会自动的为我们的类添加一个无/空参的构造器,用于对象的创建。
在java中一个类,可以拥有若干个构造器,既可以拥有无参构造器,也可以拥有有参构造器,如果用户提供了构造器,此时便不会再有默认的构造器了。
2、Scala中的构造器
Scala中有两类构造器——主构造器和辅助构造器。
(1)、构造器验证
object _02ConstructorOps {
def main(args: Array[String]): Unit = {
val p = new Person("jack", 23)
p.show()
}
}
class Person() {
private var name:String = _
private var age:Int = _
def Person(): Unit = {
println("--Person()--是默认的构造器吗?")
}
//自定义构造器 自定义构造器的第一句话必须要调用其它构造器
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
println("---------this(name:String, age:Int)---------")
}
println("-------------构造器~----------------")
def show(): Unit = {
println(s"name=${name}\tage=${age}")
}
}
(2)、Scala构造器
上例中的这个和类的定义交织在一起的默认的构造器,是scala类中最重要的一个构造器——主构造器(默认的构造器是无参主构造器,定义在类名和类的{之间,省略了())。
其余的自定义构造器,也就是在类中通过def this(xxx)定义的构造器称之为辅助构造器,同时辅助构造器的第一句话,必须要通过this来调用本类主构造器或者其它辅助构造器。
object _02ConstructorOps {
def main(args: Array[String]): Unit = {
val _3p = new Person(77)
_3p.show()
}
}
class Person(var name:String, var age:Int) {
def Person(): Unit = {
println("--Person()--是默认的构造器吗?")
}
def this() {
this("嘿嘿", 33)
println("---this() ---")
}
def this(name:String) {
this()
this.name = name
println("---this(name:String) ---")
}
def this(age:Int) {
this("tom")
this.age = age
println("---this(age:Int) ---")
}
println("-------------构造器~----------------")
def show(): Unit = {
println(s"name=${name}\tage=${age}")
}
}
(三)、内部类
主要用到这个内部类的原因,就在于如果将两个事物,每一个事物在java中都是类class,将这两个可能具有包含关系,比如人Person,心脏Heart,平行去定义,就不大符合实际的情况。而Heart是人这一类事物内部的一类其它事物,能够直接访问人这一类事物中的很多属性,如果将Heart定义在外部,要想获取人的血液,就需要在Heart并行的创建人Person对象,再进行其它操作,显然不合理。
对于这类型的操作,我们就是用类的嵌套来完成定义,或者这种结构称之为嵌套类或者内部类。
1、Scala版本的内部类
object _03InnerClassOps {
def main(args: Array[String]): Unit = {
val outer = new Outer
val inner = new outer.Inner()
inner.show()
}
}
class Outer { ooo =>
val x = 5
class Inner { iii =>
val x = 6
def show(): Unit = {
val x = 7
System.out.println("x = " + x)
System.out.println("inner class x = " + iii.x)
System.out.println("Outer class x = " + ooo.x)
}
}
}
2、总结
scala的内部类和java的内部类的主要区别就在内部类对象的创建方式不一样。其次scala为了灵活的在内部类的局部访问内部类成员或者外部类的成员,可以给内部类和外部类的引用提供一个别名,方便操作。
也就是说,上例中这iii.x <=> this.x, ooo.x <=> Outer.this.x
内部类使用蛮多的一个地方:就是匿名内部类。
(四)、object对象
scala中的除了class以外,还有两个与此平级的结构——一个是Object对象,一个是Trait特质。为啥要有这个Object呢?
主要的原因在于,在java中的一个class既可以拥有非静态的成员,也可以拥有静态static的成员。但是在scala中的class结构中,只能拥有非静态。为了给scala中的类也来提供类似于java中的静态成员的功能,于是乎就有了Object对象这个结构。
1、object对象
Java中的main函数是静态,此时在class中定义的main是无法运行,但是把class ObjectClassOps替换成object ObjectClassOps便可以运行,主要的原因在于,被object所修饰的结构中的所有的成员都是static静态。如下图2-2-1所示:
所以我们可以归纳出object的主要作用:
- 给Scala类提供程序运行的入口,静态的main函数
- 给Scala类也来提供静态成员——Scala类的伴生对象来实现。
2、单例
(1)、Scala版本单例
- 在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的
- 存放工具方法和常量
- 高效共享单个不可变的实例
- 单例模式
/**
* scala的单例操作
*/
object _06LazhSingletonOps {
def main(args: Array[String]): Unit = {
val s1 = Singleton.getInstance()
println("x: " + s1.x)
s1.x = 6
val s2 = Singleton.getInstance()
println("x: " + s2.x)
println("-------------------------")
val s3 = LazySingleton.getInstance()
println("x: " + s3.x)
s3.x = 9
val s4 = LazySingleton.getInstance()
println("x: " + s4.x)
}
}
class Singleton private () {//私有化主构造器
var x = 5
}
object Singleton {
val instance = new Singleton
def getInstance(): Singleton = {
instance
}
}
class LazySingleton private() {
var x = 6
}
object LazySingleton {
var instance: LazySingleton = null
def getInstance(): LazySingleton = {
if(instance == null) {
LazySingleton.synchronized {
if (instance == null) {
instance = new LazySingleton
}
}
}
instance
}
}
我们发现,在上述这个案例当中,同一个scala源文件中可以包含类名相同的一个class和object,这在java中是不被允许的;同时上述的这种单例的构造结果,可以看到对一个class似乎也提供了静态的功能。把这种结构,和class在同一个源文件(.scala)中同名object结构称之为该类的伴生对象,把该类称之为该object的伴生类。
伴生对象的主要作用,就是为其伴生类提供类似java中的静态成员操作;其二还可以提供另外一种对象的创建方式。
package cn.bigdata.scala
class Dog {
val id = 1
private var name = "bigdata"
def printName(): Unit ={
//在Dog类中可以访问伴生对象Dog的私有属性
println(Dog.CONSTANT + name )
}
}
/**
* 伴生对象
*/
object Dog {
//伴生对象中的私有属性
private val CONSTANT = "汪汪汪 : "
def main(args: Array[String]) {
val p = new Dog
//访问私有的字段name
p.name = "123"
p.printName()
}
}
注意:
1. 要想使用伴生对象创建本类的对象,必须要让本伴生对象复写一个apply方法,该apply方法的参数列表对应的是本类构造器的参数列表。
2. 伴生对象,不仅可以访问对应伴生类的非私有成员,同时还可以访问对应伴生类的私有成员
3. 伴生对象的使用,在scala中是非常非常广泛,举个例子,在工作中集合对象的创建几乎都用伴生对象来操作,还有后面spark中各个非常关键的对象的创建也是用伴生对象的形式。
二、类的继承体系
类与类之间的一个很重要关系——继承/扩展(extends)。
(一)、类的扩展
1、继承特点
- 子类可以继承父类的所有非私有(private),非静态的成员(变量和成员方法)。
- 可以对父类的相关方法进行覆盖/重写
- 也可以添加自己独有的成员
- 被final修饰的父类成员,子类不可以继承
- 被protected修饰的父类成员,子类可以继承
- 子类覆盖父类的方法的异常,必须要大于等于父类的异常
- 子类的访问权限必须要大于等于父类
2、继承案例
class Person {
private var name:String = _
protected var age:Int = 0
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
}
def show(): Unit = {
println(s"person's name is $name")
}
}
class Student extends Person {
age = 15
def this(age:Int) {
this()
this.age = age
}
override def show(): Unit = {
super.show()//子类调用父类的成员通过super关键字
println(s"Student's age is $age")
}
}
注意:
override在Java中是一个注解,用来表示该方法是继承的,Scala中是一个关键字,必须要添加在重写的方法前面,除非该方法是抽象的。
public class OverrideOps {
public static void main(String[] args) {
Fu f = new Zi();
f.llllllllllll();
}
}
class Fu {
public void llllllllllll(){
}
}
class Zi extends Fu {
@Override//错误的
public void lllllll11lll() {
//原因在于这个中多了两个数字1,看着和L挺接近的,但是是两个不同的方法,所以无法覆盖
//正是因为这一点,在定义Long的数据时候,尽量使用L来进行标识
}
@Override
public void llllllllllll() {
super.llllllllllll();
}
}
(二)、类型检查和转换
2、Scala的类型检查和转换
在Scala中也有类似于java中的类型检查和类型转换,这些操作,通常应用在多态(父类引用指向子类对象),类型的判断的时候。在Scala中使用isInstanceOf来进行类型判断,是asInstanceOf进行类型转换。需要注意的是这两个操作都是对象的方法。
var map01 = Map[String,Any]("name"->"张三","gender"->'男',"bhtday"->1998)
println(map01("name").isInstanceOf[String])
println(map01("gender").isInstanceOf[Char])
println(map01("bhtday").isInstanceOf[Int])
println(map01("name").asInstanceOf[String])
println(map01("gender").asInstanceOf[Char])
println(map01("bhtday").asInstanceOf[Int])
(三)、受保护字段和方法
所谓受保护的字段和方法其实就是被访问权限修饰符protected所修饰的成员。被该关键字所修饰的成员有啥特点:
Java:只能被子类访问,同时必须要在本包下面被访问。
Scala:在Java的基础之上,可以做到更灵活、更精准的访问权限控制
object _03ProtectOps {
def main(args: Array[String]): Unit = {
val dog = new Dog("黃色")
dog.show()
dog.age
}
}
class Animal {
private var name:String = _
protected var age = 3
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
}
def show(): Unit = {
println(s"Animal: ${name}, ${age}")
}
}
class Dog extends Animal {
age = 4
private var color:String = _
def this(color:String) {
this()
this.color = color
}
override def show(): Unit = {
super.show()
println("color: " + color)
}
}
这里有一个错误,age不能像java中一样,同同一个包下面被直接访问,错误如下图3-1-1所示:
但是scala中提供了一个更加强大的功能来精确的控制一个成员的访问权限。就只private和protected后面加上中括号[],[]里面写上要在哪一个范围内可以被访问,比如这里将其修改为
protected[extendz],其中的这个extendz就是本类所在的包,于是age便可以被访问了。更加精确的是,只能在本包,及其子包下面被访问。
class Animal { private var name:String = _ protected[this] var age = 3 def this(name:String, age:Int) { this() this.name = name this.age = age } def show(): Unit = { println(s"Animal: ${name}, ${age}") } } class Dog extends Animal { age = 4 private var color:String = _ def this(color:String) { this() this.color = color } override def show(): Unit = { super.show() println("color: " + color) } def makeFriend(dog:Dog): Unit = { println(s"${this.age}和另外的一個小狗${dog.age}來交朋友") } } |
其中有一个比较特殊的就是private[this]或者protected[this]
编译会出现如下问题,如图3-1-2所示:
被protected[this]所修饰的变量,只能在本类,及其子类中被调用,但不可以被子类对象调用。
这种通过精确指定成员的访问权限修饰的方式在scala中是非常常见的,大家一定要能读懂。
总结:
Scala中的访问权限修饰符就只有这么两个:private、protected,没有Java中的public,所以我们在创建类的时候,不需要加public关键字,其实不加这个public就相当于Java中的public。
(四)、超类的构造
在类的扩展中有个问题,子类没有办法给父类中的name进行直接赋值,所以尝试子类辅助构造器通过类似Java中的super关键字去调用父类的相关构造器,但是出现如图3-1-3下异常:
也是就是,Scala中子类无法通过super调用父类构造,但是在子类show方法中,却可以成功调度用super.show()方法,super在这里便是父类的一个实例引用,说明父类完成的构造,那么父类的构造是如何被调用的呢?
object _01ExtendsOps {
def main(args: Array[String]): Unit = {
val stu = new Student(18)
stu.show()
}
}
class Person {
private var name:String = _
protected var age:Int = 0
println("父类Person的主构造器-------")
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
println("----父类Person的辅助构造器------")
}
def show(): Unit = {
println(s"person's name is $name")
}
}
class Student extends Person {
println("子类Student的主构造器")
age = 15
def this(age:Int) {
super()
this.age = age
println("----子类Student的辅助构造器------")
}
override def show(): Unit = {
super.show()
println(s"Student's age is $age")
}
}
结果:
父类Person的主构造器-------
子类Student的主构造器
----子类Student的辅助构造器------
person's name is null Student's age is 18
我们看到的是,在子类的主构造器中调用了父类的构造器,因为子类的辅助构造器必须要在第一句话上面使用this来调用本类的主构造器或者其他辅助构造器,所以就没有机会来调用父类的构造器,也就是说,子类只能通过主构造器来调用父类的构造器。
class Person1(name:String) {
var age = 12
//定义辅助构造器
def this(name:String,age:Int){
//辅助构造器首行必须调用主构造器或其它辅助构造器
this(name)
this.age=age
println(age)
}
}
class Student1(name:String,hobby:String) extends Person1(name){
var color = "red"
println(name)
println(hobby)
//定义辅助构造器
def this(name:String,hobby:String,color:String){
//子类当中辅助构造器只能调用本类的主构造器或辅助构造器
this(name,hobby)
this.color=color
println(color)
}
}
object enter111{
def main(args: Array[String]): Unit = {
val s = new Student1("zhang","running")
}
}
(五)、抽象类
1、抽象类
很简单,概念和java中的抽象类一样,所谓抽象类,指的就是一个类中的方法有抽象,或者说没有被实现,把这种类称之为抽象类。
scala中的抽象类也是使用abstract关键字来进行定义;同时该抽象类中既可以有抽象方法,也可以有非抽象方法;scala中的抽象方法可以省略abstract关键字。
abstract class Animal {
var color:String = _
/*abstract*/ def sleep()
def dead(): Unit = {
println("动物固有一死,或清蒸,或红烧~")
}
}
class Dog extends Animal {
def sleep(): Unit = {
println("小狗睡觉是趴着睡~")
}
override def dead(): Unit = {
super.dead()
println("但是,狗乃人类之伙伴,最好不要清蒸,或红烧,不道德")
}
}
class Horse extends Animal {
override def sleep(): Unit = {
println("小马驹睡觉是站着睡~")
}
}
说明一点:
子类覆盖父类的抽象方法,可以省略掉override关键。
2、抽象字段
和Java中不同的地方,就是在scala中除了有抽象方法以外,还可以有抽象的字段,啥叫做抽象字段,只有字段的定义,没有进行初始化的。
abstract class AbstractFu {
/*abstract*/ val name:String
var age:Int
}
class AbstractZi extends AbstractFu {
/*override*/ val name = "zhangsan"
var age = 16
}
定义的这个val的抽象字段,子类只能进行一次初始化,后期则不可以进行修改;而var的变量可以进行任意的操作。
(六)、Trait特质
Scala中的这个继承和java的继承有一个缺陷,只能进行单继承,但是可以进行多层继承,但是多层继承又要要求,类与类之间必须具有继承关系,这显然不一定满足,还是有局限的。所以再java中推出了接口interface这个概念来满足多重继承,只不过这里不叫继承,而称之为多实现,使用关键字implements来连接,多个接口interface之间使用","进行分割。
Scala呢?
Scala对于同样的需求,设计出了另外一个结构,什么呢——trait,特质。这个trait的功能要比java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。
所以如果说,一个trait特质中的所有方法都是抽象方法,那么该trait就可以当做Java中的接口去对待。
1、特质的定义
trait的功能要比Java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非常抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。
object _02TraitOps {
def main(args: Array[String]): Unit = {
val cLog = new ConsoleLog
cLog.log("冬天来了,春天也就不远了~")
}
}
trait Log {
def log(msg:String)
def show(): Unit = {
println("trait中的非抽象方法")
}
}
class ConsoleLog extends Log {
override def log(msg: String): Unit = {
println("console--->" + msg)
}
}
class FileLog extends Log {
override def log(msg: String): Unit = {
println("file--->" + msg)
}
}
2、特质的多扩展
trait的功能要比Java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非常抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。
//将日志信息发送到网络中区
class SocketLog extends Log with Serializable {
override def log(msg: String): Unit = {
println("socket--->" + msg)
}
}
3、特质的混入
当一个类的实例只需要进行一次符合某种特性的操作,而这种特性被定义在另外一个特质中,就可以通过Scala特质特有的一个概念来处理——混入(mix in),混入并不会对整个类造成侵入性,只会对当前的对象产生一次性的影响。
object _02TraitOps {
def main(args: Array[String]): Unit = {
println("-----------------混入--------------")
val sLog = new SocketLog with Sercurit
sLog.log("我要飞得更高~")
sLog.validate()
}
}
trait Log {
def log(msg:String)
def show(): Unit = {
println("trait中的非抽象方法")
}
}
trait Sercurit {
def validate(): Unit = {
println("安全你我他,快乐给大家~")
}
/*def log(msg: String): Unit = {
println("Sercurit--->" + msg)
}*/
}
//将日志信息发送到网络中区
class SocketLog extends Log {
override def log(msg: String): Unit = {
println("socket--->" + msg)
}
}