3. Scala中的类
Any: 抽象类,是所有类型的父类;
Nothing: 是所有类型的子类,Nothing没有对象,但是可以用来定义类型 ,当方法抛出异常或者返回一个类型犹豫不决的时候,可以用Nothing;
AnyRef: 是所有引用类型的基类。除了值类型,所有类型都继承自AnyRef 。
AnyVal: 是所有值类型的基类,它描述的是值,而不是代表一个对象。它包括9个AnyVal子类型:
scala.Double
scala.Float
scala.Long
scala.Int
scala.Char
scala.Short
scala.byte
scala.Unit (非数值类型)
scala.Boolean (非数值类型)
注意: Scala中,一个源文件可以有很多类,并且都是public级别的。
使用了private[this]定义后的内容就无法外部使用了,这起到了非常好的保护作用
3.1 Scala的构造函数
3.1.1 主构造器
class Person(name:String,val age:Int){
println("This is the primary constructor")
}
特点:
- 主构造器(默认构造器)直接在类名称后面,主构造器中的参数会被编译成类的字段;
- 主构造器执行,会执行类中的所有不包含在方法体中的语句(此时类中所有没放在方法和代码块中的代码,都是构造器中的代码);
- 如果在主构造器函数的参数没有用val或者var去声明变量,那此时变量是private[this]级别的,只能够被类内部访问,且默认是val;
注意: 无参构造器可以省略后面的括号
3.1.2 附属构造器
class Person(var name:String,val age:Int){
println("This is the primary constructor!")
var gender:String = _
def this(name:String,age:Int,gender:String){
this(name,age)
this.gender = gender
}
}
特征:
-
附属构造器是用this来声明的
-
附属构造器必须调用主构造器或者其它附属构造器
3.2 class继承
class Person(val name:String,var age:Int){
println("This is the primary constructor of person")
val school = "BEIJING"
def sleep = "8 hours"
override def toString="I am a person"
}
class worker(name:String,age:Int,val salary:Long) extends Person(name,age){
println("This is the subClass of Person,Primary constructor of Worker")
override val school = "Spark"
override def toString = "I am a Worker!"+super.sleep
}
注意: 子类在继承父类时,必须继承父类的主构造器;
子类构造函数中的参数必须同时也初始化父类主构造器中的参数;
子类重写抽象父类的抽象字段和抽象方法时,可以写override,也可以不写;但是覆盖非抽象字段和放法时,必须写override;
3.3 多重继承和Trait
3.3.1 基础用法
1) Scala中,Trait是一种特殊概念。首先,Trait可以被作为接口来使用,此时Trait与Java接口非常类似。也可以作为抽象类使用,在Trait中定义抽象方法,其与抽象类中的抽象方法一样,不给出方法的具体实现,trait还可以定义具体方法,也有的说法是Trait的功能混入了类。
2) Scala中,Trait可以定义具体字段,继承Trait的类就自动获取了Trait中定义的字段。 Trait中,可以定义抽象字段,而Trait中的具体方法可以基于抽象字段来编写,但继承Trait的类,必须覆盖抽象字段,提供具体的值。
注意: 这里与继承Class不同,如果继承Class获取的字段,实际定义在父类中,而继承Trait获取的字段,就直接添加到了类中。
3) 所有的Java接口都可以作为Scala的特质来使用,与Java一样Scala只能有一个超类,但可以拥有任意数量的特质。
4) 在解读时,extends后面的作为一个整体,然后类extends这个整体,如果一个类只扩展了特质,那么第一个关键字为extends(with关键字不能单独使用,必须和extends关键字一起使用)
注意: 在Scala中,无论继承类还是继承Trait都是用extends关键字,interface在Scala中不是关键字;Java使用extends继承类使用implement实现接口;
5) Scala中,类继承Trait后,必须实现其中的抽象方法,实现时不需要使用override关键字,而具体方法被重写需要加override,同时Scala同Java一样,不支持类多继承,但支持多重继承Trait,使用with关键字即可。
6) Trait本身不能构造,只能构造trait的实现类,具体的实现类构造时,trait也会被构造,构造顺序从左到右
class Human{
println("human")
}
trait Teacher extends Human{
println("Teacher")
// 定义抽象方法
def teach
}
trait PianoPlayer extends Human{
println("PianoPlayer")
def palyPiano = {
println("I'm playing piano")
}
}
class PianoTeacher extends Human with Teacher with PianoPlayer{
override def teach = {
println("I'm training students")
}
}
/
trait Action{
def doAction
}
trait TBeforeAfter extends Action{
abstract override def doAction{
println("Initialization")
super.doAction
println("Destory")
}
}
class Work extends Action{
override def doAction = println("Working...")
}
class TraitUse {
}
object TraitUse extends App{
val t1 = new PianoTeacher
t1.palyPiano
t1.teach
println("*********************")
val t2 = new Human with Teacher with PianoPlayer{
def teach = {
println("I'm training students")
}
}
t2.palyPiano
t2.teach
println("*********************")
val work = new Work with TBeforeAfter
work.doAction
}
运行结果:
运行结果为:
human
Teacher
PianoPlayer
I'm playing piano
I'm training students
*********************
human
Teacher
PianoPlayer
I'm playing piano
I'm training students
*********************
Initialization
Working...
Destory
3.3.2 中级用法
1) 为实例混入Trait:在创建某个类对象时,可以指定该对象混入某个Trait,这样只有这个对象混入了该Trait,其他实例对象则没有
2) Trait调用链:在Scala中,支持让类继承多个Trait,依次调用多个Trait中的同一个方法,首先会从最右边的Trait的方法开始执行,然后依次向左执行,形成一个调用条。这个相当于设计模式中的责任链模式的一种具体实现依赖, 最后都执行super方法。
3) Trait中,可以覆盖父Trait的抽象方法,但是覆盖时,如果使用了super方法的代码是无法通过编译的。如果要通过编译,就得给子Trait的方法加上abstract override修饰。
trait Logger {
def log(message: String)
}
trait MyLogger extends Logger {
abstract override def log(message: String) {
super.log(message)
}
}
3.3.3 高级用法
1) 混合使用Trait具体方法和抽象:可以让具体方法依赖于抽象方法,抽象方法放到继承Trait的类中实现
trait Valid {
def getName:String
def valid:Boolean = {
getName = "lubin"
}
}
class Person(val name:String) extends Valid{
println(valid)
def getName = name
}
scala>val person = new Person("lubin)
true
scala>val james = new Person("james)
false
2) Trait构造机制:在Scala中,Trait是有构造代码的,就是Trait中不包含在任何方法中的代码,而继承了Trait的构造机制如下:
a)父类的构造函数
b)Trait的构造代码执行,多个Trait从左向右依次执行
c)构造Trait时会先构造父Trait,如果多个Trait继承同一个父Trait,则父Trait只会构造一次
d)所有trait构造结束之后,子类的构造函数执行
class Person{
println("This is the Person constructor!")
}
trait Logger{
println("This is the Logger constructor!")
}
trait MyLogger extends Logger{
println("This is the MyLogger constructor!")
}
trait TimeLogger extends Logger{
println("This is the TimeLogger constructor!")
}
class Student extends Person with MyLogger with TimeLogger{
println("This is the Student constructor!")
}
scala> val stu = new Student
This is the Person constructor!
This is the Logger constructor!
This is the MyLogger constructor!
This is the TimeLogger constructor!
This is the Student constructor!
3) Trait 字段的初始化:在Scala中,Trait是没有接收字段的构造函数,这是Trait与Class的唯一区别,如果要求trait对字段进行初始化,只能使用Scala中的一种高级特性,即提前定义或使用Lazy Value。
4) Trait还继承class:在Scala中,Trait可以继承class,这个class就会成为所有继承该trait的类的父类
5) 没有具体方法的Trait会被编译成接口
trait Trait1{
}
trait Trait2{
def m1:Int
def m2(arg:Int):Int
}
// 编译后
public interface Trait1
{
}
public interface Trait2
{
public abstract int m1();
public abstract int m2(int i);
}
6) 有具体方法的trait会被编译成两个class
trait Trait3{
def m3(arg:Int):Int = 1
}
//编译之后得到两个class文件:Trait3.class和Trait3$class.class,查看编译后的文件可得Trait3.class是一个接口
public interface Trait3
{
public abstract int m3(int i);
}
// 方法实现在Trait$class.class文件中
public abstract class Trait3$class
{
public static int m3(Trait3 $this, int arg)
{
return 1;
}
public static void $init$(Trait3 trait3)
{
}
}
注意: trait会被编译为等价的接口;
如果trait有具体方法,则这些方法会被复制到相应的$class中,并且有下面两处变化
①类为abstract,方法为static
②trait的实例被插入到参数列表的最前端
7) 在构造对象时,可以混入该对象所具有的特质的子类,那么在调用该对象所含特质的方法时,将会执行子类的方法。
trait Logged {
def log(msg: String) { }
}
class SavingsAccount extends Account with Logged {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else ...
}
...
}
trait ConsoleLogger extends Logged {
override def log(msg: String) { println(msg) }
}
val acct = new SavingsAccount with ConsoleLogger
// 当调用acct的log方法时,执行的是ConsoleLogger特质的log方法
3.4 伴生对象和apply方法
3.4.1 伴生对象object
1) object对象中的成员变量和成员方法都是静态变量和静态方法。
2) 只有在第一次使用object时,其中的方法才会被执行(java中静态成员和方法在类加载的时候就会被执行)
3) 它拥有一个隐形的构造器,无参。
4) 如果有一个与object同名的class类则称object为伴生对象,class为伴生类。一个伴生类可以访问其伴生对象的所有成员(包括私有成员),伴生对象通常可作为相对应类的静态成员封装区域(可以看做一个集合)
package lubin
/**
* @author lenovo
*/
class ApplyTest {
def apply()={
println("I love Spark so much!")
}
def haveATry{
println(" Have a try on apply!")
}
}
object ApplyTest{
def apply()={
println("I love Scala so much!")
new ApplyTest //创建ApplyTest类对象
}
}
object Application{
def main(args:Array[String]){
val arr = Array(1,2,3,4) //通过Array类伴生对象中的apply方法来创建集合
val a = ApplyTest() //通过类ApplyTest的伴生对象中的apply方法来创建ApplyTest对象。
a. haveATry
println("***********************")
val b = new ApplyTest
b.haveATry
println(b()) //调用类的apply方法,b后面必须加括号否则就是打印地址了
}
}
程序运行结果为:
I love Scala so much!
Have a try on apply!
***********************
Have a try on apply!
I love Spark so much!
注意: 用类名再加上括号,则可以调用object中的apply方法,对象加括号则调用的是class的apply方法
3.4.2 apply方法
class UsageOfApply {
}
class ApplyTest {
def test{
println("test")
}
}
object ApplyTest{
def apply()=new ApplyTest
def static{
println("I am a static method!")
}
}
object UsageOfApply extends App {
val a = ApplyTest()
a.test()
}
结果:test
注意: 当使用”val a=ApplyTest()”时会导致伴生类对象中的apply方法的调用并返回该方法调用的值,也就是ApplyTest的实例化对象。
在apply方法中还可以实现单例的控制。
3.5 包的定义和使用
3.5.1 包的定义
使用关键字 package,后面是包名,包名可以是链式结构(嵌套结构) :包名1.包名2。
package lubin
package spark.navigation {
class Navigation
package tests {
//在spark.navigation.tests包里的类
//IDE会自动给类添加一个包名,如上面的包lubin。
class NavigatorSuite
}
}
package hadoop {
package navigation {
class Navigator
}
package launch {
class Booster {
//包中类的实例化。
val nav = new navigation.Navigator
}
}
}
3.5.2 包对象
//定义了一个object类型的package,此包为people
package object people {
val defaultName = "Scala"
}
//在people下的所有类都可以用上面的成员。
package people {
class people {
var name = defaultName
}
}
3.5.3 Scala中隐式引用的包
//Scala中自动引入,下划线相当于java中的*,表示所有内容。
import java.lang._
import scala._
import Predef._
//如果不想引入包下面的所有的成员,可以用大括号的方式。
import java.awt.{Color,Font}
//如果java中存在某个类与scala中某个类冲突,可以对java中
//的类使用别名,如HashMap的别名为JavaHashMap
import java.util.{HashMap => JavaHashMap}
//如果不想使用某个类,则使用占位符,代替。
//java中也有个StringBuilder,如果此时你使用StringBuilder
//则此时,使用的是java中的StringBuilder。
import scala.{StringBuilder => _} // 表示隐藏掉scala.StringBuilder类
3.5.4 包、类中的访问权限
// Navigation包下的类不可访问com.spark包下的;com.spark包下的不可访问com包下的
package com.spark {
package navigation {
//private[spark]表示此类的访问权限为spark包下的全体成员。
private[spark] class Navigator {
protected[navigation] def useStarChart(){}
class legOfJourney {
private[Navigator] val distance = 100
}
//限制此变量使用权限为对象。
private[this] var speed = 200
}
}
package launch {
import navigation._
object Vehicle {
private[launch] val guide = new Navigator
}
}
}
//伴生类可以访问伴生对象,伴生对象可以访问伴生类,(包括私有的)
class PackageOps_Advanced {
import PackageOps_Advanced.power
private def canMakeItTrue = power > 10001
}
object PackageOps_Advanced{
private def power = 10000
def makeItTrue(p:PackageOps_Advanced):Boolean={
val result = p.canMakeItTrue
result
}
}
注意: 在Scala对象中,定义的字段为private,则会自动生成私有的getXXX,setXXX方法。如果没有说明为private,则该字段会自动变为private,但是会生成public的getXXX,setXXX方法,外部能够访问该字段(实质是通过public类型的getXXX,setXXX方法)。
Scala中的方法默认是public的,加上private就为私有的。
3.5.5 类中getXXX和setXXX方法
class Person{
private var myName = “Flink”
// 自定义get方法
def name = this.myName
// 自定义set方法
def name_ =(newName:String){
myName = newName
println(“Hello: ”+myName)
}
}
注意: 如果val john = new Person 则john.name调用的是john.name方法;john.name=”Spark”调用的是john.name_方法。
Scala类中的属性的set方法为属性名后面加下划线;
Scala类中var定义的变量会生成getter和setter方法,val定义的变量只会生成getter方法。