Scala学习之路----面向对象编程

本文深入探讨Scala的面向对象编程特性,强调私有成员的访问控制,指出这些成员只能在创建它们的对象内部访问,确保数据的安全性和封装性。
摘要由CSDN通过智能技术生成
一、面向对象编程——类
1、 字段的getter与setter详解
1》非自定义getter与setter
定义不带private的var 字段,此时scala生成的面向JVM的类时, 会定义为private的name字段, 并提供public的getter和setter方法
而如果使用private修饰字段, 则生成的getter和setter也是private的
如果定义val 字段, 则只会生成getter方法
如果不希望生成setter和getter方法, 则将字段声明为private[this]
例如:class Student {
var name = "leo"
}
//调用getter和setter方法, 分别叫做name和name_ =
val leo = new Student
print(leo.name)
leo.name = "leo1"
2》自定义getter与setter
如果只是希望拥有简单的getter和setter方法, 那么就按照scala提供的语法规则, 根据需求为字段
选择合适的修饰符就好: var、 val、 private、 private[this]
但是如果希望能够自己对getter与setter进行控制, 则可以自定义getter与setter方法
自定义setter方法的时候一定要注意scala的语法限制, 签名、 =、 参数间不能有空格
class Student {
private var myName = "leo"
def name = "your name is " + myName
def name_=(newValue: String) {
print("you cannot edit your name!!!")
}}
object GetAndSet {
def main(args: Array[String]): Unit = {
val leo = new Student
println(leo.name)
leo.name="leo1"
}}
3》仅暴露字段的getter方法
如果你不希望字段有setter方法, 则可以定义为val, 但是此时就再也不能更改字段的值了
但是如果希望能够仅仅暴露出一个getter方法, 并且还能通过某些方法更改字段的值, 那么需要综合
使用private以及自定义getter方法
此时, 由于字段是private的, 所以setter和getter都是private, 对外界没有暴露; 自己可以实现修改
字段值的方法;
class Student {
private var myName = "leo"
def updateName(newName: String) {
if(newName == "leo1") myName = newName
else print("not accept this new name!!!")}
def name = "your name is " + myName}
4》private[this]的使用
如果将字段使用private来修饰, 那么代表这个字段是类私有的,
在类的方法中, 可以直接访问类的其他对象的private 字段。
如果不希望字段被其他对象访问到, 那么可以使用private[this], 意味着是对象私有的字段,

只能被当前对象访问, 其他新创建的对象实例无法访问。


2、辅助constructor(构造函数)
1》Scala中, 可以给类定义多个辅助constructor, 类似于Java中的构造函数重载;
辅助constructor之间可以互相调用, 而且必须第一行调用主constructor
class Student {
private var name = ""
private var age = 0
def this(name: String) {
this()
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}}
2》主constructor(构造函数)
Scala中, 主constructor是与类名放在一起的, 与Java不同,
而且类中, 没有定义在任何方法或者代码块之中的代码, 就是主constructor的代码。
class Student(val name: String, val age: Int) {
println("your name is " + name + ", your age is " + age)}
主constructor中还可以通过使用默认参数, 来给参数默认的值
class Student(val name: String = "leo", val age: Int = 30) {
println("your name is " + name + ", your age is " + age)}
二、面向对象编程——对象
1、object
1》object, 相当于class的单个实例, 通常在里面放一些静态的字段或者方法
第一次调用object的方法时, 就会执行object的constructor, 也就是object内部不在方法中的代码;
但是object不能定义接受参数的constructor

2》伴生对象
如果有一个class, 还有一个与class同名的object, 那么就称这个object是class的伴生
对象, class是object的伴生类。
伴生类和伴生对象必须存放在一个.scala文件之中。
伴生类和伴生对象, 最大的特点就在于, 互相可以访问private 字段。
class accompany {
private val name="hehe"
def getName={
println(accompany.name2)
}}
object accompany{
private val name2="pppqq"
def main(args: Array[String]): Unit = {
val acc = new accompany
println(acc.name)
acc.getName
}}
说明:1、 类的伴生对象中的方法和常量可以被访问, 但并不在作用域当中。 必须通过 半生对象名.方法半生对象名.常量来访问
3》apply方法
object中非常重要的一个特殊方法, 就是apply方法
通常在伴生对象中实现apply方法, 并在其中实现构造伴生类的对象的功能
而创建伴生类的对象时, 通常不会使用new Class的方式, 而是使用Class()的方式, 隐式地调用伴生对象的apply方法, 这样会让对象创建更加简洁。比如, Array类的伴生对象的apply方法就实现了接收可变数量的参数, 并创建一个Array对象的功能。
// 比如, 定义伴生类和伴生对象
class Person(val name: String)
object Person {
def apply(name: String) = new Person(name)}
4》用object来实现枚举功能
Scala没有直接提供类似于Java中的Enum这样的枚举特性。
如果要实现枚举, 则需要用object继承Enumeration类, 并且调用 Value方法来初始化枚举值。
object Season extends Enumeration {
val SPRING, SUMMER, AUTUMN, WINTER = Value
}
还可以通过Value传入枚举值的id和name, 通过id和toString可以获取; 还可以通过id和name来查找枚举值
object Season extends Enumeration {
val SPRING = Value(0, "spring")
val SUMMER = Value(1, "summer")
val AUTUMN = Value(2, "autumn")
val WINTER = Value(3, "winter")
}
Season(0)
Season.withName("spring")
// 使用枚举object.values可以遍历枚举值
for (ele <- Season.values) println(ele)
5》main方法
就如同Java中, 如果要运行一个程序, 必须编写一个包含main方法类一样;
在Scala中, 如果要运行一个应用程序, 那么必须有一个main方法作为入口
Scala中的main方法定义为def main(args: Array[String]), 而且必须定义在object中
object HelloWorld {
def main(args: Array[String]) {
println("Hello World!!!")}}
除了自己实现main方法之外, 还可以继承App trait, 然后将需要在main方法中运行的代码, 直接作
为object的constructor代码; 而且用args可以接受传入的参数
object HelloWorld extends App {
if (args.length > 0) println("hello, " + args(0))
else println("Hello World!!!") }
6》object继承抽象类
object的功能其实和class类似, 除了不能定义接受参数的constructor之外
object也可以继承抽象类, 并覆盖抽象类中的方法
abstract class Hello(var message: String) {
def sayHello(name: String): Unit }
object HelloImpl extends Hello("hello") {
override def sayHello(name: String) = {
println(message + ", " + name) } }
三、面向对象编程——继承
1、extends
Scala中, 子类继承父类, 与Java一样, 也是使用extends关键字
继承就代表,子类可以从父类继承父类的字段和方法; 然后子类可以在自己内部放入父类没有, 而子类特有的字段和方法; 使用继承可以有效复用代码。
子类可以覆盖父类的字段和方法;如果父类用final修饰, 字段和方法用final修饰, 则该类是无法被继承的, 字段和方法是无法被覆盖的。
class Person {
private var name = "leo"
def getName = name }
class Student extends Person {
private var score = "A"
def getScore = score }
2、override和super
1》覆盖方法:Scala中, 如果子类要覆盖一个父类中的非抽象方法, 则必须使用 override关键字
此外, 在子类覆盖父类方法之后, 如果我们在子类中就是要调用父类的被覆盖的方法,
那就可以使用 super关键字, 显式地指定要调用父类的方法
class Person {
private var name = "leo"
def getName = name }
class Student extends Person {
private var score = "A"
def getScore = score
overridedef getName = "Hi, I'm " + super.getName }
2》覆盖字段
Scala中, 子类可以覆盖父类的val 字段, 只要在子类中使用override关键字即可
class Person {
val name: String = "Person"
def age: Int = 0 }
class Student extends Person {
override val name: String = "leo"
override val age: Int = 30 }
3、isInstanceOf和asInstanceOf
如果我们创建了子类的对象, 但是又将其赋予了父类类型的变量。 则在后续的程序中, 我们又需
要将父类类型的变量转换为子类类型的变量, 应该如何做?
首先, 需要使用isInstanceOf判断对象是否是指定类的对象, 如果是, 则可以使用asInstanceOf将
对象转换为指定类型。
注意:如果对象是null, 则isInstanceOf一定返回false, asInstanceOf一定返回null。
如果没有用isInstanceOf先判断对象是否为指定类的实例, 就直接用asInstanceOf 转换, 则可能会
抛出异常。
class Person
class Student extends Person
val p: Person = new Student
var s:Student = null
if (p.isInstanceOf[Student]) s = p.asInstanceOf[Student]
4、getClass和classOf
isInstanceOf只能判断出对象是否是指定类以及其子类的对象, 而不能精确判断出, 对象就是指
定类的对象。
如果要求精确地判断对象就是指定类的对象, 那么就只能使用getClass和classOf了。
对象.getClass可以精确获取对象的类, classOf [类]可以精确获取类, 然后使用==操作符即可
判断。
class Person
class Student extends Person
val p: Person = new Student
p.isInstanceOf[Person]
p.isInstanceOf[Student]
p.getClass == classOf[Person]
p.getClass == classOf[Student]
5、模式匹配进行类型判断
在实际开发中, 比如Spark的源码中, 大量的地方都是使用了模式匹配的方式来进行类型的判断,
这种方式更加地简洁明了, 而且代码的可维护性和可扩展性也非常高。
使用模式匹配, 功能性上来说, 与isInstanceOf一样, 主要判断是该类以及该类的子类对象即可,
不是精准判断的
class Person
class Student extends Person
val p: Person = new Student
p match {
case per: Person => println("it's Person's object")
case _ => println("unknown type") //注意 case与下划线之间有一个空格 }
注:自带break
6、protected
跟Java一样, Scala中同样可以使用protected关键字来修饰字段和方法,
这样在子类中就不需要super关键字, 就直接可以访问字段和方法
如果使用protected[this], 则只能在当前子类对象中访问父类的字段和方法, 无法通过其他子类对象访问父类的字段和方法
class Person {
protected var name: String = "leo"
protected[this] var hobby: String = "game" }
class Student extends Person {
def sayHello = println("Hello, " + name)
def makeFriends(s: Student) {
println("my hobby is " + hobby + ", your hobby is " + s.hobby) } }
7、调用父类的constructor
Scala中, 每个类可以有一个主constructor和任意多个辅助constructor。
每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor, 因此 子类的辅助constructor是一定不能直接调用父类的constructor的
只能在子类的主constructor中调用父类的constructor, 以下这种语法, 就是通过子类的主构造函数来调用父类的构造函数。
注意: 如果是父类中接收的参数, 比如name和age, 子类中接收时, 就不要用任何val或var来修饰了, 否则会认为是子类要覆盖父类的字段。
class Person(val name: String, val age: Int)
class Student(name: String, age: Int, var score: Double) extends Person(name, age) {
def this(name: String) {
this(name, 0, 0) }
def this(age: Int) {
this("leo", age, 0) } }
8、抽象类
如果在父类中, 有某些方法无法立即实现, 而需要依赖不同的子类来覆盖, 重写实现自己不同的方法实现。 此时可以将父类中的这些方法不给出具体的实现, 只有方法签名, 这种方法就是抽象方法。而一个类中如果有一个抽象方法, 那么类就必须用abstract来声明为抽象类, 抽象类是不可以实例化的。 在子类中覆盖抽象类的抽象方法时, 不需要使用override 关键字
abstract class Person (val name: String) {
def sayHello: Unit }
class Student(name: String) extends Person(name) {
def sayHello: Unit = println("Hello, " + name) }
9、抽象字段
如果在抽象父类中, 定义了字段, 但是没有给出初始值, 则此字段为抽象字段
抽象字段意味着, Scala会根据自己的规则, 为var或val类型的字段生成对应的getter和setter方法
子类必须覆盖字段, 以定义自己的具体字段, 并且覆盖抽象字段, 不需要使用override关键字
abstract class Person {
val name: String }
class Student extends Person {
val name: String = "leo" }
三、面向对象编程——trait特质
1、将trait作为接口使用
Scala中的trait是一种特殊的概念。
首先可以将trait作为接口来使用, 此时的trait就与Java中的接口非常类似。
在triat中可以定义抽象方法, 就与抽象类中的抽象方法一样, 只要不给出方法的具体实现即可。
类可以使用extends关键字继承trait, 注意, 这里不是implement, 而是extends, 在scala中没有implement的概念, 无论继承类还是trait,统一都是extends。
类继承trait后, 必须实现其中的抽象方法, 实现时不需要使用override关键字
scala不支持对类进行多继承, 但是 支持多重继承trait使用with关键字即可
案例:
trait Hellotrait {
def sayHello(name: String) }
trait MakeFriendstrait {
def makeFriends(p: Person) }
class Person( val name: String) extends Hellotrait with MakeFriendstrait with Serializable {
def sayHello(name: String) = println("Hello, " + name)
def makeFriends(p: Person) = println("Hello, my name is " + name + ", your name is " + p.name) }
object TraitInter {
def main(args: Array[String]): Unit = {
val p = new Person("haha")
p.sayHello("www")
p.makeFriends(p)
} }
输出结果:
Hello, www
Hello, my name is haha, your name is haha
2、trait中定义具体方法
Scala中的trait不仅仅可以定义抽象方法, 还可以定义具体方法, 此时trait更像是包含了通用工具方法的东西。比如, trait中可以包含一些很多类都通用的功能方法, 比如打印日志等等, Spark中就使用了trait来定义了通用的日志打印方法
例1、trait Logger {
def log(message: String) = println(message) }
class Person(val name: String) extends Logger {
def makeFriends(p: Person) {
println("Hi, I'm " + name + ", your name is , " + p.name)
log("name=" + p.name) } }
例2、trait Person2 {
def eat(info: String) {
println("I eat " + info) } }
trait Workers {
def work = print("I am worker") }
class Student1 extends Person2 with Workers
object MethodTrait {
def main(args: Array[String]): Unit = {
val s = new Student1
s.eat("rice")
s.work } }
3、在trait中定义具体字段
Scala中的trait可以定义具体字段, 此时继承trait的类就自动获得了trait中定义的字段,
但是这种获取字段的方式与继承class是不同的: 如果是继承class 获取的字段, 实际是定义
在父类中的; 而继承trait获取的字段, 就直接被添加到了类中。
trait Person3{
val name:String
val age:Int = 20 }
trait Worker1{
// val name = "zhangsan"
val age = 24 }
class Student2 extends Person3 with Worker1{
val name:String = "lisi"
override val age = 19 }
object FieldsTrait {
def main(args: Array[String]): Unit = {
val s = new Student2
println(s.name)
println(s.age) } }
4、为实例混入trait
有时我们可以在创建类的对象时, 指定该对象混入某个trait, 这样, 就只有这个对象
混入该trait的方法, 而类的其他对象则没有。
trait Logged{
def log(msg: String) {} }
trait MyLogger extends Logged {
override def log(msg: String) { println("log: " + msg) } }
class Person(val name: String) extends Logged {
def sayHello {
println("Hi, I'm " + name);
log("sayHello is invoked!") } }
def main(args: Array[String]): Unit = {
val p1 = new Person("leo")
p1.sayHello
val p2 = new Person("jack") with MyLogger
p2.sayHello }
5、trait调用链
Scala中支持让类继承多个trait后, 依次调用多个trait中的同一个方法, 只要让多个trait的同一个方法在最后都执行super.方法即可。类中调用多个trait中都有的这个方法时, 首先会从最右边的trait的方法开始执行, 然后依次往左执行,形成一个调用链条。
这种特性非常强大, 其实就相当于设计模式中的 责任链模式的一种具体实现依赖。
trait Handler {
def handle(data: String) {} }
trait DataValidHandler extends Handler {
override def handle(data: String) {
println("check data: " + data)
super.handle(data) } }
trait SignatureValidHandler extends Handler {
override def handle(data: String) {
println("check signature: " + data)
super.handle(data) } }
class Person(val name: String) extends SignatureValidHandler with DataValidHandler {
def sayHello = {
println("Hello, " + name);
handle(name) } }
6、混合使用trait的具体方法和抽象方法
在 trait 中, 可以混合使用具体方法和抽象方法;可以让抽象方法放到继承trait的类中去实现
这种trait其实就是设计模式中的 模板设计模式的体现
trait Valid {
def getName: String //抽象方法
def valid: Boolean = { //具体方法
getName == "leo" } }
class Person(val name: String) extends Valid {
def getName = name
println(valid) }
7、trait的构造机制
在Scala中, trait也是有构造代码的, 也就是trait中的不包含在任何方法中的代码。
而继承了trait的类的构造机制如下:
1、 父类的构造函数执行;
2、 trait的构造代码执行, 多个trait从左到右依次执行;
3、 构造trait时会先构造父trait, 如果多个trait继承同一个父trait, 则父trait只会构造一次;
4、 所有trait构造完毕之后, 子类的构造函数执行
class Person { println("Person's constructor!") }
trait Logger { println("Logger's constructor!") }
trait MyLogger extends Logger { println("MyLogger's constructor!") }
trait TimeLogger extends Logger { println("TimeLogger's constructor!") }
class Student extends Person with MyLogger with TimeLogger {
println("Student's constructor!") }
8、trait继承class
在Scala中, trait也可以继承自class, 此时这个class就会成为所有继承该trait的类的父类
class MyUtil {
def printMessage(msg: String) = println(msg) }
trait Logger extends MyUtil {
def log(msg: String) = printMessage("log: " + msg) }
class Person(val name: String) extends Logger {
def sayHello {
log("Hi, I'm " + name)
printMessage("Hi, I'm " + name) } }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值