scala-basic 基础篇
@author 鲁伟林
Scala基础知识介绍,主要包括《Scala编程实战》的基础章节
GitHub地址: https://github.com/thinkingfioa/scala-in-action
本人博客地址: http://blog.csdn.net/thinking_fioa/article/details/78265745
4. 类和属性
与Java相比,Scala在类的声明、构造和字段的访问控制存在很大的差异。Java更啰嗦,Scala更简洁。第4章项目源码阅读
4.1 创建一个主构造函数
Scala构造函数分为:主构造函数 + 辅助构造函数。一个主构造函数是以下的组合:
- 构造函数参数
- 在类内部被调的方法
- 类内部执行的语句和表达式
class Person(var firstName : String, var lastName : String) {
println("the constructor begins")
// some class field
private val HOME = System.getProperty("user.name")
var age = 0
override def toString: String = s"$firstName $lastName is $age years old"
def printHome(): Unit = {
println(s"HOME = $HOME")
}
printHome()
}
解释:
- Scala主构造函数相当模糊,Person类的整个都是主构造函数部分
- Person类的构造函数中两个参数firstName、lastName被定义为var字段,scala会默认为其添加get/set方法。而属性HOME则没有,private val相当于private final,不可以被其他对象直接访问
- Scala中描述符: private val,相当于Java中的private final
- Scala中默认添加的get/set方法,比如上诉代码中age属性,会添加方法名叫age() ----- getter方法,age_$eq(int age) ----- setter方法
4.2 控制构造函数字段的可见性(var/val/private)
Scala中构造函数参数可见性,使用var/val/private关键字修饰所控制。可通过下列口诀记住:
- 如果一个字段被声明为var,Scala会为该字段生成getter和setter方法
- 如果字段是val,Scala只会生成getter方法
- 如果一个字段没有var或val修饰符,Scala比较保守,不会生成getter方法和setter方法
- 字段有使用var或val修饰符,且同时使用private关键字修饰,也不会生成getter方法和setter方法
4.2.1 Case类
case类中构造函数参数的生成方式与其他的类略有不同。Case类的构造函数参数默认是val,生成getter方法,其他类默认不会生成getter方法和setter方法
代码:
class OtherPerson(name :String) {
}
case class CasePerson(name : String) {
}
object Class4P2 {
val otherPerson : OtherPerson = new OtherPerson("fioa")
val casePerson : CasePerson = CasePerson("ppp")
// println(s"otherPerson ${otherPerson.name}") 报错
println(s"casePerson ${casePerson.name}")
}
4.3 定义辅助构造函数
定义多个辅助构造函数,方便用户通过不同的方式创建对象实例。在类内部以this为名的方法定义辅助构造函数。通过不同的参数列表,定义多个辅助构造函数。提醒:每个构造函数必须调用之前已经定义定义好的构造函数,也就是说,任何辅助构造函数都会调用主构造函数
- 辅助构造函数必须使用this命名创建
- 每个辅助构造函数必须调用之前定义的构造函数开始
- 每个构造函数必须有不同的签名(参数列表)
代码:
class Pizza(var crustSize : Int, var crustType : String) {
def this(crustSize: Int) {
this(crustSize, Pizza.DEFAULT_CRUST_TYPE)
}
override def toString: String = s"Thinking eat Pizza $crustSize, $crustType"
}
object Pizza {
val DEFAULT_CRUST_SIZE = 12
val DEFAULT_CRUST_TYPE = "THIN"
}
4.3.1 为case类生成辅助构造函数
Case类是一个会自动生成很多模版代码的特殊类。使用类的伴生对象中的apply方法,来为Class类添加一个辅助构造函数
代码
case class Person4P2(var name : String, var age : Int)
object Person4P2 {
def apply(name : String) = new Person4P2(name, 0)
}
object Class4P3 {
var p : Person4P2 = Person4P2("thinking")
}
4.4 定义私有的主构造函数 ----- 单例模式需要
为了使用单例,需要建立一个私有主构造函数,使用private关键字。Scala中使用单例,需要用到伴生对象
代码
class Brain private (var name : String, var speed : Long) {
override def toString: String = s"$name have $speed ms"
def printBrain() : Unit = {
print(toString())
}
}
object Brain {
val brain = new Brain("thinking", 1)
def getIntance: Brain = brain
}
object Class4P4 {
def main(args: Array[String]): Unit = {
Brain.getIntance.printBrain()
}
}
伴生对象 ----- 讨论
简单来说,一个伴生对象就是定义在与类的同一个文件中,同时对象和类有相同的名字。如上object Brain是类Brain的伴生对象。伴生对象类中的方法都是该对象的静态方法,类似于Java中的静态类
4.5 设置构造函数参数的默认值
给构造函数参数提供一个默认值,在调用构造函数时可以指定或者也可以不指定参数。 eg: class Socket(val timeout : Int = 1000)
代码
class Socket (val timeout : Int = 1000) {
}
object Class4P5 {
def main(args: Array[String]): Unit = {
val socket : Socket = new Socket(timeout = 3000)
}
}
4.6 覆写默认的访问和修改方法
覆写Scala自动生成的getter或者setter。Scala中这个优点麻烦,推荐下列方法来实现覆写默认的方法
class Stock(var _symbol : String) {
def symbol() : String = _symbol
def symbol_ (s :String) : Unit = {
this._symbol = symbol
}
}
4.7 阻止生成getter和setter方法
Scala中如果某个变量定义为var,将会自动生成getter和setter方法;如果变量定义成val,将会自动生成getter方法
- 如果是class,字段没有使用var/val修饰,则不会生成getter和setter方法。如果是case class,字段没有使用var/val修饰,默认为val,生成getter方法
- 使用private或private[this]访问修饰符定义字段
private 和 private[this]对比
- 使用private修饰字段,则Scala不会自动生成getter和setter方法。
- 定义一个private[this]字段让私有化更进一步,让字段对象私有化
- 如下代码,price字段无法被相同类型的其他实例访问
代码
class Book {
private[this] var price : Double = _
def setPrice(price:Double ): Unit = {
this.price = price
}
def isHigher(that : Book): Boolean = {
// 报错,无法在this对象中访问that对象的price
// this.price > that.price
true
}
}
4.8 将代码块或者函数赋给字段
用代码块或者调用函数给类里面的字段初始化赋值,如果算法要求较长的运行时间,可加上lazy关键字
代码
class FooTest {
var text = {
var lineText="unkonw text"
try {
lineText = "try code"
} catch {
case e : Exception => lineText = "catch code"
}
lineText
}
}
4.9 设置未初始化的var字段类型
在一个类中设置一个未初始化的var字段类型
- 非String和数字类型的字段,使用Option和Some
- String类型字段,缺省为空串。var str : String = ""
- 数值类型 ----- 给定合适的类型和默认值。varB : Byte = 0; varC : Char = 0; varl : Long = 0;
代码
case class Person4P9(var userName : String, var passwd : String) {
var age : Int = 0
var address: Option[Address] = None : Option[Address]
}
case class Address(var city : String, var state : String) {
}
object Class4P9 {
def main(args: Array[String]): Unit = {
var person : Person4P9 = Person4P9("thinking", "fioa")
person.address = Some(Address("beijin", "nanjinRoad"))
person.address.foreach { a => {
println(a.city)
println(a.state)
}
}
}
}
4.10 在继承类时处理构造函数参数 ----- 继承必看
继承一个基类,需要处理定义在基类中构造函数的参数,同时也需要处理子类中的新参数。
通过将基类的构造函数参数定义为var或val。当定义在子类构造函数时,不要用var或val声明类间的公用的字段; 在子类中使用var或者val字段定义新的构造函数
代码
class Person4P11(var name : String , var age : Int) {
override def toString: String = s"name is $name, age is $age"
}
class Employee(name : String, age : Int, var address : Address) extends Person4P11(name, age){
override def toString: String = s"name is $name, age is $age, address is $address"
}
object Class4P10 {
def main(args: Array[String]): Unit = {
val employee : Employee = new Employee("ppp", 26, Address("Anhui", "anqin"))
println(employee)
}
}
4.11 调用父类的构造函数
当子类构造函数需要调用超类构造函数,这个Scala语言和Java语言有较大的不同。
Scala中只有子类的主构造函数才有资格调用超类的构造函数,子类的辅构造函数第一行必须是调用当前类的另一个构造函数,所以子类的辅构造函数不能直接调用超类的任何一个构造函数
4.12 何时使用抽象类
Scala中的特质比抽象类更灵活。基于下面两个原因使用抽象类:
- 需要创建一个有构造函数参数的基类,特质(trait)不允许有构造函数参数
- 需要被Java调用
代码
abstract class BaseController {
def save(): Unit = {
// save action
}
def update() : Unit = {
// update action
}
// abstract method
def connect()
}
4.12.1 Scala中类和对象的总结
- object ----- 伴生对象,类似于Java中的静态类
- class ----- 基本的类,与Java中类似
- abstract classs ----- 抽象类,关于何时使用抽象类,参见4.12
- case class ----- case类,比较特殊,适用于模式匹配场景使用。与普通的class有如下不同:缺省val和var修饰符,属性的可见性不同;
- trait ----- 特质,类似于Java1.8中接口。可以有属性,方法,抽象方法
- 继承Enumeration ----- 枚举类,代码参见如下
代码
object HttpMethod extends Enumeration {
type HttpMethod = Value
val UNKNOWN = Value("UNKNOWN")
val GET = Value("GET")
val OCCUR = Value("OCCUR")
val POST = Value("POST")
}
4.13 在抽象基类(或者特质)定义属性
在一个抽象类(或特质)中声明var 或者 val字段,这些字段可以是抽象的,也可以是具体的实现。在一个抽象类(或特质)里把抽象字段声明成var 或 val,取决于具体的需求
- 抽象的var生成getter和setter方法
- 抽象的val生成getter方法
- 在一个抽象类(或特质)中定义一个抽象字段时,Scala编译器不会在结果代码中创建这个字段,只会生成该var或val字段的响应方法
- override关键字不是必须的
- 开发人员可以在抽象类中定义了一个def字段,而不是val也是可以的
代码
abstract class Animal4P13 {
var greeting : String = "Hello"
val age : Int = 0
val sayHello: Unit = {println(s"Animal4P13, say $greeting")}
}
class Dog4P13 extends Animal4P13 {
greeting = "Dog"
override val sayHello: Unit = {println(s"Dog4P13, say $greeting")}
}
object Class4P13 {
def main(args: Array[String]): Unit = {
val dog : Dog4P13 = new Dog4P13()
}
}
4.14 用Case类生成模版代码
Scala生成模版代码,模版代码包括了访问和修改方法,apply,unapply,toString,equals,和hashCode等方法。通过将类定义为Case类会自动生成模版代码的诸多好处:
- 自动生成一个apply方法,这样就不用new关键字创建新的实例
- Case类的构造函数参数默认是val,自动生成getter方法
- 自动生成一个toString方法
- 自动生成一个unapply方法,在模式匹配是很好用
- 自动生成equals方法和hashCode方法
- 自动生成copy方法
4.14.1 提醒
Case类主要是为了创建"不可变的记录",非常适用于模式匹配场景使用。建议Case类的参数默认是val,不允许写成var,否则就违背了"不可变的记录"的本意
4.15 定义一个equals方法(对象的相等性)
Scala中的对象相等性判断和Java具有较大不同点。众所周知,Java语言中==方法是比较两个对象的引用的相等性;而在Scala中==方法等同于equals方法
代码
class Person4P14(val name : String, val age : Int) {
def canEqual(a : Any) : Boolean = a.isInstanceOf[Person4P14]
override def equals(that : Any) : Boolean = that match {
case that : Person4P14 => this.canEqual(that) && this.hashCode() == that.hashCode()
case _ => false
}
override def hashCode() : Int = {
// TODO 补充hashCode值
31
}
}
4.16 创建内部类
Scala内部类从属于外部类对象的,有以下四种内部类的表现形式。(java类中内部类从属于外部类)
- 外部类class ----- 内部类class
- 外部类class ----- 内部对象object
- 内部对象object ----- 外部类class
- 内部对象object ----- 内部对象object
代码
class OuterClass {
class InnerClass {
var x = 1
}
object InnerObject {
val y = 2
}
}
object OuterObject {
class InnerClass {
var m =3
}
object InnerObject {
var n = 4
}
}
object Class4P16 {
//调用外部类中的内部类
val oc = new OuterClass
var ic = new oc.InnerClass
println(ic.x)
//调用外部类中的内部类对象
println(new OuterClass().InnerObject.y)
//调用外部对象的内部类
println((new OuterObject.InnerClass).m)
//调用外部对象的内部对象
println(OuterObject.InnerObject.n)
}