Scala面向对象编程

一.基本概念

(一).面向对象的三大特性

①封装:把属性、方法封装到一个类中

②继承:父类和子类之间(属性和方法可以重写的)

③多态:父类引用指向子类对象

比如父类Animal,子类Dog

一个多态:Animal animal = new Dog( )

包含了父类的Animal animal

引用指向子类对象的new Dog( )

(二).类和对象

比如好朋友们可以看成一个类,名字、年龄、学号可以看成属性,爱好可以看成方法;

每一个好朋友可以看成一个对象,那么每个对象都是由名字、年龄、学号等属性和爱好等方法构成;

而好朋友们就是一个类,一个类可以有一个或多个对象,每个对象也可以有一个或多个属性和方法。

类是一个抽象的、概念层面的东西

对象是具体的,其实对象就是一个类的实例化的操作,即类是对象的模板,对象是类的具体实例。(一个类可以实例化多个对象哟)

对象 duixiang = new 类( )

二.类的封装、继承和重写

(一).封装

类的定义与使用1

scala→New→Package:com.ruozedata.scala.oo

oo→New→Scala class:ClassApp

类的定义用class修饰

①object ClassApp{

①   def main(args: Array[String]):Unit = {  //定义一个main方法

⑤   val user = new User  //开始实例化:new一个类 val/var 对象名:[类型]=new 类()

⑥   user.name="PK"  //对应终端name_$eq,也可以写成user.name_$eq("PK")

      user.eat()  //调用eat()方法

}

}

②class User{  //定义一个类(类里面封装属性和方法)

③   var name:String = ""  //定义属性

       val age:Int = 30

④def eat():Unit={  //定义属性

    println(s"$name...eating..")

}

为了弄清楚上述代码到底在底层干了啥事,将上述class User类里面的代码拷贝进Linux:

cd /home/hadoop001/app/scala

vi User.scala,粘贴内容然后scalac User.scala

User.scala

javap -p User

终端显示底层代码

Compiled from "User.scala"

public class User {

  private java.lang.String name;  //相当于java get

  private final int age;

  public java.lang.String name();  //相当于java get

  public void name_$eq(java.lang.String);  //相当于java set

  public int age();

  public void eat();

  public User();

}

scala里面,var修饰的,对应这里java的get、set;

scala里面,val修饰的,对应这里java的get,但是没有set,所以不能被修改

采用java bean来理解

①import scala.beans.BeanProperty  //手动导入包

②在class类前面添加一个注解:@BeanProperty

③在Linux最前面加一行import scala.beans.BeanProperty,并在相应的位置加@BeanProperty

④scalac User.scala

    javap -p User

与上一次结果对比,就有写get、set了

⑤回到idea,调用user.setName("大佬")

上述操作仅仅是底层的东西,实际操作不会这样使用。

类的定义与使用2

1.在class类里面继续定义方法:(方法都是先定义再调用的)

def watch(footballName:String):Unit = {

      println(s"$name is watching match of $footballName ")   \ }

然后在object里面调用:user.watch("阿森纳")

拓展:名字和年龄都是用var或val修饰的,并没有加public或者private

2.在class类里面,继续

定义属性:private val money = 10000000000L

定义方法:def printInfo():Unit={

                   println(s"$name ==> $money") \ }

然后在object里面调用:user.printInfo()

【问题:】上述操作能够调用出来,但是不能直接通过user.money来访问多少钱,为什么不能够呢?

答:因为money在class类里面定义的private属性,只能在class类里面的方法里面用,外面是访问不到的。

验证:将上述操作粘贴进Linux相应的地方,再scalac User.scala \ javap -p User

money是private属性

总结:class+类名{定义类的属性和方法}

1.类的使用:直接new出来,想修改的可以修改,不能修改的可以获取,然后去访问它的一些方法

2.在一个.scala文件中,可以定义多个class+类名{定义类的属性和方法}

占位符“_”的使用

_:赋予一个初始值的意思,默认初始值为null

var name:String = ""等价于var name:String = _

【面试题】:此处占位符_代表什么?默认值是什么?

答:_是赋予一个初始值的意思,默认初始值为null

验证:scala> var name:String = _ 返回:name:String = null

【面试题】:占位符_能用val修饰吗?

答:不能。因为只是放了一个代表初始值的占位符,但是它的值是需要修改的。

【问题】:var i = _,为什么返回报错?

答:因为不知道数据类型,所以不知道初始值是什么。

验证占位符所表示的内容

(二).scala中构造器的使用

基本概念

1.什么是构造器? 

val user = new User 其中User就是构造器,即class定义的类后面的,比如class Person

2.构造器可以是无参的,如class User{};构造器也可以是有参数的,如class Person(val name:String)

3.构造器的作用:就是实例化对象

实例

主构造器:定义在类上的构造器叫主构造器

object ConstructApp {

  def main(args: Array[String]): Unit = {

    val person = new Person(name="LK",age=24)  //new一个Person,传name.age进来(通过构造器去实例化一个对象)

    println(s"name is ${person.name},age is ${person.age},school is ${person.school}")

  }

}

class Person(val name:String,val age:Int){  //这里的Person就是构造器,它定义在class类上面的,所以是一个主构造器。这里Person后面加了参数:(val name:String,val age:Int),这些参数的内容会自动翻译成构造器的属性。构造器的属性在这里参数的位置上和{}里面都能够定义。属性可以定义很多个。

  println("Person Construct enter...")

  val school = "nudt"

  println("Person Construct leave...")

}

主构造器

附属构造器:定义在类里面的构造器叫做附属构造器,附属构造器可以有很多个(由入参的个数和类型决定的)

①附属构造器固定的用def this修饰

②附属构造器里面的第一行必须要调用主构造器或者其他附属构造器,用this()调用

package com.ruozedata.scala.oo

object ConstructApp {

  def main(args: Array[String]): Unit = {

  val person = new Person(name="LK",age=24,gender = "男")

    println(s"name is ${person.name},age is ${person.age},school is ${person.school},gender is ${person.gender}")

  }

}

class Person(val name:String,val age:Int){

  println("Person Construct enter...")

  val school = "nudt"

  var gender:String=_

  def this(name:String,age:Int,gender:String){  //定义附属构造器,用def this修饰

    this(name,age)  //调用主构造器(调用主构造器定义的name、age)

    this.gender=gender  //主构造器有的属性,附属构造器再调用的时候就不需要var和val修饰了

  }

  println("Person Construct leave...")

}

附属构造器

【面试题1】:上述代码的执行顺序

答:new的时候先执行class主构造器里面的,再执行外面的。

【面试题2】:如果把主构造器后面参数的val或者val删了,编译还能通过吗?

答:不能。因为如果删了,后面附属构造器就无法调用var或者val修饰的内容了。

查看源码,多多理解两种构造器:以SparkContext为例。

(三).继承和重写

继承

继承的关键字extends用于继承

父类有的属性,子类无需再用var和val修饰,若果父类没有的则需要用val和var去修饰,或者用override去重写。如果子类需要而父类没有的方法,一定要用override去重写。

用上面ConstructApp:scala里的Person作为父类构造器

object ConstructApp {

  def main(args: Array[String]): Unit = {

    val student = new Student(name="阿坤",age=24,major = "communication")

    println(student.major)

  }

}

class Person(val name:String,val age:Int){

  println("Person Construct enter...")

  val school = "nudt"

  var gender:String=_

  def this(name:String,age:Int,gender:String){

    this(name,age)

    this.gender=gender

  }

  println("Person Construct leave...")

}

class Student(name:String,age:Int,var major:String) extends Person(name,age){  //定义一个子类构造器

  println("Student Construct enter...")

  println("Student Construct leave...")

}

重写

重写父类属性

在子类构造器里面去重写父类的属性,要用关键词override

技巧1:重写的时候,输入某个重写的参数有提示,直接选中然后回车即可。

重写属性小技巧1

package com.ruozedata.scala.oo

object ConstructApp {

  def main(args: Array[String]): Unit = {

    val student = new Student(name="阿坤",age=24,major = "communication")

    println(student.school)

  }

}

class Person(val name:String,val age:Int){

  println("Person Construct enter...")

  val school = "nudt"

  var gender:String=_

  def this(name:String,age:Int,gender:String){

    this(name,age)

    this.gender=gender

  }

  println("Person Construct leave...")

}

class Student(name:String,age:Int,var major:String) extends Person(name,age){

  println("Student Construct enter...")

  override val school: String = "hubu"

  println("Student Construct leave...")

}

重写父类属性

问题:println(student)会如何?

答:默认返回:包名+类名,可以通过重写父类方法解决

默认返回

重写父类方法

重写父类方法时,直接def有提示,直接选中回车,然后修改内容即可

重写方法小技巧

package com.ruozedata.scala.oo

object ConstructApp {

  def main(args: Array[String]): Unit = {

    val student = new Student(name="阿坤",age=24,major = "communication")

    println(student)

  }

}

class Person(val name:String,val age:Int){

  println("Person Construct enter...")

  val school = "nudt"

  var gender:String=_

  def this(name:String,age:Int,gender:String){

    this(name,age)

    this.gender=gender

  }

  println("Person Construct leave...")

}

class Student(name:String,age:Int,var major:String) extends Person(name,age){

  println("Student Construct enter...")

  override val school: String = "hubu"

  override def toString: String = {"重写OK了"}

  println("Student Construct leave...")

}

重写方法

此时println(student)的返回值就不一样了。

【面试题】:上述代码的执行顺序

答:new子类时构造器的执行顺序:先执行父类的构造器,再执行子类的构造器。

三.抽象类的定义与使用

(一).抽象类

基本概念

抽象类是指类中的一个或多个方法、属性没有完整的定义。(只声明了,但是没有实现)

抽象类的使用:

abstract class 类名{

val/var name:String

def xxx()}

看源码:MemoryManager 理解抽象类的使用。

查看某个抽象类的具体实现方法:鼠标选中抽象类构造器名,然后"Ctrl+t"

注意:如果父类是抽象类,那么子类里面是具体的,需要把里面的属性和方法全部重写

实例

抽象类的调用:抽象类是不能直接被实例化的,抽象类必须要有具体的实现类才能被实例化。直接实例化会报错的哟。

【面试题】:如何调用抽象类呢?

答:①通过子类构造器调用,即定义一个子类构造器去调用.

②通过匿名子类来实现

1.通过子类构造器调用

object AbstractClassAPP {

  def main(args: Array[String]): Unit = {

    val student=new Student2

    student.speak()

}

class Student2 extends Person2{  //通过子类构造器调用

  override def speak():Unit= {

    println("speak...")

  }

  override val name:String = "LK"

  override val age:Int = 24

}

abstract class Person2{ //定义抽象类

  def speak()  //只定义方法并未实现

  val name:String  //只定义属性并未实现 

  val age:Int  //只定义属性并未实现

}  //{}里面的必须把该类定义为abstract抽象类,否则构造器会报错。

class Student2 extends Person2{

  override def speak():Unit= {

    println("speak...")

  }

  override val name:String = "LK"

  override val age:Int = 24

}

通过子类构造器调用

2.通过匿名子类调用

在main方法下面,输入new Person2根据提示回车,把刚才子类构造器里面的重写内容拷贝到这里,然后直接用stu.speak()调用:

object AbstractClassAPP {

  def main(args: Array[String]): Unit = {

  val stu=new Person2 { //通过匿名子类调用实现

      override def speak():Unit= {

        println("speak...")

      }

      override val name:String = "LK"

      override val age:Int = 24

    }

    stu.speak()

  }

}

abstract class Person2{ //定义抽象类

  def speak()  //只定义方法并未实现

  val name:String  //只定义属性并未实现

  val age:Int  //只定义属性并未实现

}

通过匿名子类调用

(二).枚举类型

枚举类型也是一种抽象类(用的时候直接进源码看如何使用的)

oo右键→new→scala class:EnumerationApp

不会打开源码怎么办?选中单词Ctrl+鼠标看看

拷贝一个例子:

object EnumerationApp {

  def main(args: Array[String]): Unit = {

    println(WeekDay.isWorkingDay(WeekDay.Mon))  //调用

    println(WeekDay.isWorkingDay(WeekDay.Sat))  //调用

  }

}

object WeekDay extends Enumeration{

  type WeekDay = Value

  val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value

  def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)  //判断是否为工作日

}

枚举类型判断是否为工作日

四.伴生类和伴生对象

概念

在同一个package中,object xxx和class xxx中如果两处xxx名字一样,name二者互为伴生关系:object xxx是class xxx的伴生对象,class xxx是object xxx的伴生类。

实例1

object ApplyApp {

  def main(args: Array[String]): Unit = {

    println(Timer.currentCnt())

    println(Timer.currentCnt())

    println(Timer.currentCnt())

  }

}

object Timer{ //object定义的内容可以直接object的名字直接使用

  var cnt = 0

  def currentCnt():Int={

    cnt += 1

    cnt

  }

实例2:调用执行顺序先进class再出class最后调用test

package com.ruozedata.scala.oo

object ApplyApp {

  def main(args: Array[String]): Unit = {

    val a = new ApplyTest

    println(a)

    a.test()

  }

}

class ApplyTest{

  println("ApplyTest class enter...")

  def test():Unit={

    println("ApplyTest class test")

  }

  println("ApplyTest class leave...")

}

object ApplyTest{

  println("ApplyTest object enter...")

  println("ApplyTest object leave...")

}

绿色是执行顺序,红色是输入顺序

实例3:没有new,直接调用object里面的

在class里面定义一个apply()方法,同时在object里面定义一个apply()方法

object ApplyApp {

  def main(args: Array[String]): Unit = {

    val b = ApplyTest  //没有new,直接调用object里面的

    println(b)  

    }

}

class ApplyTest{

  println("ApplyTest class enter...")

  def test():Unit={

    println("ApplyTest class test")

  }

  def apply():Unit={

    println("ApplyTest class Apply...")

  }

  println("ApplyTest class leave...")

}

object ApplyTest{

  println("ApplyTest object enter...")

  def apply():Unit={

    println("ApplyTest class Apply...")

  }

  println("ApplyTest object leave...")

实例4:调用的class里面的东西

object ApplyApp {

  def main(args: Array[String]): Unit = {

    val c = new ApplyTest  //new了,调用的class里面的东西

    println(c)

    }

}

class ApplyTest{

  println("ApplyTest class enter...")

  def test():Unit={

    println("ApplyTest class test")

  }

  def apply():Unit={

    println("ApplyTest class Apply...")

  }

  println("ApplyTest class leave...")

}

object ApplyTest{

  println("ApplyTest object enter...")

  def apply():Unit={

    println("ApplyTest class Apply...")

  }

  println("ApplyTest object leave...")

实例5:调用class里面的Apply()方法

object ApplyApp {

  def main(args: Array[String]): Unit = {

    val c = new ApplyTest

    println(c())  //调用class里面的Apply()方法

    }

}

class ApplyTest{

  println("ApplyTest class enter...")

  def test():Unit={

    println("ApplyTest class test")

  }

  def apply():Unit={

    println("ApplyTest class Apply...")

  }

  println("ApplyTest class leave...")

}

object ApplyTest{

  println("ApplyTest object enter...")

  def apply():Unit={

    println("ApplyTest class Apply...")

  }

  println("ApplyTest object leave...")

总结

类名()→调用object apply()

对象名()→调用class apply()

【使用场景】:在object的apply方法中实例化对象(new class),比如val gf = new GirlFriend,

再比如val array = new Array[Int](5)等价于val array2 = new Array[Int](xs = 5)

可以用鼠标选中Array,然后Ctrl+t查看。

如果以后看到某个东西没有new,那必然在apply方法中new过了。

五.case class

不用new,直接使用。在spark中使用的特别多,特别是spark SQL和模式匹配中。

实例

object CaseClassApp {

  def main(args: Array[String]): Unit = {

    println(Dog("旺财").name) //不用new,直接调用

  }

}

case class和class对比

【面试题1】:class和case class的区别

object CaseClassApp {

  def main(args: Array[String]): Unit = {

  val per1=new Per1(name="lk",age=24)  //调用class

    val per2=new Per1(name="lk",age=24)  //调用case class

    println(per1==per2)  

    println(per1)  

    println(per2)

    val per21=Per2("ak",24)

    val per22=Per2("ak",24)

    println(per21==per22)

    println(per21)

    println(per22)

  }

}

class Per1(name:String,age:Int)  //定义class

case class Per2(name:String,age:Int)  //定义case class

【思考题1】:上述程序中per1和per2相等吗?

答:肯定不相等,因为二者的验证地址不一样。

验证:

   println(per1==per2)  //返回false

    println(per1)  //返回打印的hasecode

     println(per2) //返回打印的hasecode

【反问】:如果要让它们相等,底层应该做什么事呢?

答:与class不同,case class底层必然重写了toString、equals、hashcode方法。此外case class还实现了序列化。

【思考题2】:上述程序中per21和per22相等吗?

答:相等。

验证:

   println(per21==per22)  //返回true

    println(per21)  //返回Per2的内容

     println(per22) //返回Per2的内容

case class和case object对比

【面试题2】:case class和case object的区别

答:case class是一定带上参数列表的,用的较为广泛;

     case object是一定不能带上参数列表的,用的很少。

六.导包

(一).导包

建包

定义的时候,一般都写个包名(包名一般就是把公司域名倒着写)

建包1

右键oo→new→Package:pack1→new→scala class:A→class

package com.ruozedata.scala.oo.pack1

class A {

  val name = "LK"

  val age = 24

  def methodA():Unit={

    println("---A.methodA---")

  }

  def methodB():Unit={

    println("---A.methodB---")

  }

}

建包2

右键oo→new→Package:pack2→new→scala class:AA→class

(此时建包可以直接copy pack1,然后改名字改内容)

package com.ruozedata.scala.oo.pack1

class AA {

  val city = "北京"

  def methodC():Unit={

    println("---AA.methodC---")

  }

  def methodB():Unit={

    println("---AA.methodD---")

  }

}

导包

右键oo→new→Package:pack1→new→scala class:A→class

导包方法1:

import com.ruozedata.scala.oo.pack1.A

object B {

  def main(args: Array[String]): Unit = {

    val a1 = new A()  //如果没有导包,直接这样会报错,此时需要导包:“Enter+Alt”,选择import class,选择刚刚建立的A包,此时object B{}上面会自动出现导包代码:import com.ruozedata.scala.oo.pack1.A 

    a1.methodA()  //调用A包里面的methodA()

  }

}

自动导包
也可以直接在object{}前输入代码import com.ruozedata.scala.oo.pack1.A 手动导包

导包方法2:

直接在new后面导入包(new+完整的包名)

object B {

  def main(args: Array[String]): Unit = {

    val a2 = new com.ruozedata.scala.oo.pack1.A()

    a2.methodB()  ////调用A包里面的methodA()

  }

}

new+完整的包名

改名字

导包的时候把名字改了,再new+改的名字即可

import com.ruozedata.scala.oo.pack1.{A => RuozedataA}

object B {

  def main(args: Array[String]): Unit = {

    val a3 = new RuozedataA  //在main方法里面调用的时候就用新的名字了

    a3.methodB()

  }

}

在object{}前面输入:import com.ruozedata.scala.oo.pack1.{A => RuozedataA}

(二).隐式转换

Java里面:(不能识别<-

package com.ruozedata.scala.oo.pack2

import java.util

object B {

  def main(args: Array[String]): Unit = {

    val list = new util.ArrayList[String]()  //这里直接输入ArrayList根据提示找到相应的地方回车即可

    list.add("lk")

    list.add("阿坤")

    for(ele <- list){ //此时“<-”是不能够识别的。因为这个语法是scala的,而list是java里面的。这时候就涉及到了隐式转换:scala和java的混合编程。

      println(ele)

    }

  }

}

不能识别<-

隐式转换能识别<-

隐式转换:此时导包:import scala.collection.JavaConverters._实现scala与java的混合编程

注意:需要把for(ele <- list)改成for(ele <- list.asScala)哟!

package com.ruozedata.scala.oo.pack2

import java.util

object B {

  def main(args: Array[String]): Unit = {

    val list = new util.ArrayList[String]()

    list.add("lk")

    list.add("阿坤")

    import scala.collection.JavaConverters._

    for(ele <- list.asScala){

      println(ele)

    }

  }

}

隐式转换后就能识别<-了

(三).Pack object

自动生成package object pack

右键oo→new→Package:pack,然后把pack1和pack2拉进去

右键oo→new→Package Object此时自动生成package object pack

【注意】1.一个包下面有且仅有一个Package Object;

2.在包下面直接建立的package object pack是可以直接在类里面直接使用的。

自动生成package object pack

自动生成的里面定义方法

package object pack {

  def dataframe2HBase(dataframe:String):Unit={

    println("---pack.dataframe2HBase---")   //2表示to, a to b表示把a转成b;dataframe2HBase表示把dataframe转成HBase

  }

}

自动生成定义方法

调用自动生成的里面的定义的方法

方法1:定义main方法直接调用不用导包

object PackageObjectApp {

  def main(args: Array[String]): Unit = {

    dataframe2HBase(dataframe = "df1")

  }

}

直接调用不用导包

方法2:定义一个方法(这种方式使用的时候还需要调用 PackageObjectApp.foo())

package com.ruozedata.scala.oo.pack

object PackageObjectApp {

  def main(args: Array[String]): Unit = {

    PackageObjectApp.foo()

}

  def foo():Unit={

      dataframe2HBase(dataframe = "df1")

  }

}

需要调用 PackageObjectApp.foo()

七.type

先定义几个类:

class Animal{

  override def toString: String = "这是一个动物"

}

class Pig extends Animal{

  override def toString: String = "这是一只猪"

}

调用1:

object TypeApp {

  def main(args: Array[String]): Unit = {

    val pig = new Pig

    println(pig.isInstanceOf[Pig])  //判断前面对象是不是Pig类

    println(pig.isInstanceOf[Animal])  //判断前面对象是不是Animal类

    println(pig.isInstanceOf[Object])  //判断前面对象是不是Object类

  }

}

【分析】首先有个动物,其次有只猪,然后这只猪继承自动物,最后都来自老大Object。

此时返回3个true

调用2:

object TypeApp {

  def main(args: Array[String]): Unit = {

    val animal = new Animal

    println(animal.isInstanceOf[Pig])  //返回false(因为Pig是Animal,而Animal不是Pig)

    println(animal.isInstanceOf[Animal])  //返回true

    println(animal.isInstanceOf[Object])  //返回true

    println(animal.isInstanceOf[AnyRef])  //返回true

    println(animal.isInstanceOf[Any])  //返回true

  }

}

调用3:

object TypeApp {

  def main(args: Array[String]): Unit = {

    val pig=new Pig

    val bool = pig.isInstanceOf[Pig]

    if(bool){

      println(pig.asInstanceOf[Pig])  //返回true 这是一只猪

    }

    }

}

调用4:调用的时候可以new一个类去用上述三种调用,也可以val pig=classOf[Pig]调用(输入技巧:直接classOf[Pig].val回车即可)

package com.ruozedata.scala.oo

object TypeApp {

  def main(args: Array[String]): Unit = {

    val pig=classOf[Pig]  //classOf:返回一个运行时,代表类的一个类型。

    println(pig.getClass)

    }

}

Java.jdbc编程时,第一步class.forname把驱动加进来;而scala编程时,直接classOf[驱动]。

[附]:调用5*:

object TypeApp {

  def main(args: Array[String]): Unit = {

    val age=100

    println(age.isInstanceOf[Int])

    }

}

总结:

type实际上就是用来定义新的数据类型名称的(有点类似于Linux里面的alais别名),如:

type S=String  //给String类型重新取名叫S

val value:S="LK"

def test():S="阿坤"

八.Trait

Java里面有一个interface接口,而Scala中没有接口的概念,但是它有trait的概念,类似于java的interface。

(一).单Trait

下面定义的trait是一个顶层的东西,有具体实现的子类

定义一个trait:

trait MemoryManager{  //这其实就相当于一个接口

  println("---MemoryManager---")

  val name:String  //定义属性(抽象类的,只定义不实现)

  val maxOnHeapStorageMemory:Long   //定义属性(抽象类的,只定义不实现)

}

1.单trait以前spark内存管理(Static)

定义一个Static静态类的trait:

class StaticMemoryManager01 extends MemoryManager{  //刚输入的时候StaticMemoryManager01下面有红色波浪线,直接点击提示的小灯泡,实现一下就OK了。或者手动重写也可以

  println("---StaticMemoryManager01子类---")

  override val name: String = "静态内存管理"

  override val maxOnHeapStorageMemory: Long = {

    println(s"$name 获取存储内存")

    100L

  }

}

调用:trait是不能直接使用的,所以需要new一下再调用

    val memoryManager = new StaticMemoryManager01

    println(memoryManager.maxOnHeapStorageMemory

Static内存管理

【执行顺序】先父类再子类最后返回值(上图红色输入顺序;绿色执行顺序)

2.单trait现在的spark内存管理(Unified)

定义一个Unified内存管理的trait:

class UnifiedMemoryManagerextends MemoryManager{

println("---UnifiedMemoryManager子类---")

override val name:String ="统一内存管理"

  override val maxOnHeapStorageMemory:Long = {

println(s"$name 获取存储内存")

200L

  }

}

调用:

val unifiedMemoryManager =new UnifiedMemoryManager

println(unifiedMemoryManager.maxOnHeapStorageMemory)

Unified内存管理

【执行顺序】先父类再子类最后返回值(上图红色输入顺序;绿色执行顺序)

3.Trait的底层实现

打开Linux:

vi MemoryManager.scala

  进入之后把定义trait的语句粘贴进去:

    trait MemoryManager{ 

        println("---MemoryManager---")

         val name:String

           val maxOnHeapStorageMemory:Long  

      }

scalac MemoryManager.scala

javap MemoryManager.class

单trait的底层实现

由此可以看出,scala中trait的底层其实就是java中的interface

(二).多Trait混合输入

在scala里面,无论是类还是抽象类或者trait,第一个用extends关键字,后面的用with关键字

object TraitApp {

def main(args: Array[String]):Unit = {

val logger =new StaticMemoryManager02  //new StaticMemoryManager02.var回车并取名logger 

logger.print()  //调用

}

}

trait MemoryManager{  //定义一个trait

println("---MemoryManager---")

val name:String

  val maxOnHeapStorageMemory:Long

}

class StaticMemoryManager02 extends RuozedataLogger with RuozedataException with  MemoryManager{  //定义一个StaticMemoryManager02类后面第一继承自实现它的RuozedataLogger类,然后依次with两个trait端口RuozedataException 、MemoryManager(extents后面的实现它的类不能变哟)

println("---StaticMemoryManager02子类---")

override val name:String ="静态内存管理"

  override val maxOnHeapStorageMemory:Long = {

println(s"$name 获取存储内存")

100L

  }

override def exception:Exception = {

new RuntimeException(s"$name 获取存储内存")

}

}

class RuozedataLogger{

println("--RuozedataLogger--")

def print():Unit={

println("开始打印日志")

}

}

trait RuozedataException{

println("--RuozadataException--")

def exception:Exception

}

【面试题】上述代码多trait混合输入的输出顺序。

【思考题】如果交换上述extents后面(extents后面的实现它的类不能变哟)用with连接的类的顺序,输出结果如何?

答:按新的extents后面的顺序输出,最后再输出它自己里面的东西。

(三).多Trait之间建立联系及顺序问题

1.三个trait之间无关联

定义三个类:

trait AAA{

def printInfo():Unit={

println("AAA")

}

}

trait BBB{

def printInfo():Unit={

println("BBB")

}

}

trait CCC{

def printInfo():Unit={

println("CCC")

}

}

定义具体实现:

class AAABBBCCC extends AAA with BBB with CCC{    }

调用:

val aaabbbccc =new AAABBBCCC

aaabbbccc.printInfo()

但是此时执行代码

解决办法:在具体实现的类里面重写定义一下

AAABBBCCC extends AAA with BBB with CCC{ 

override def printInfo():Unit = {

println("AAABBBCCC.printInfo")

}

但是此时返回AAABBBCCC.printInfo与AAA、BBB、CCC三个trait无关,这该如何解决呢?

【解决办法】

方法一:子类重写去实现一个

方法二:用trait建立联系,即让另外两个trait都继承自其中一个(要记得重写里面的内容哟)。

2.多个trait之间建立联系

object TraitApp {

def main(args: Array[String]):Unit = {

val aaabbbccc =new AAABBBCCC

aaabbbccc.printInfo()

}

trait AAA{

def printInfo():Unit={

println("AAA")

}

}

trait BBB extends AAA {  //让traitBBB继承自AAA

override def printInfo():Unit = {

println("BBB")

}

}

trait CCC extends AAA{  //让traitCCC继承自AAA

override def printInfo():Unit = {

println("CCC")

}

}

class AAABBBCCC extends AAA with BBB with CCC{

override def printInfo():Unit = {

println("AAABBBCCC.printInfo")

}

}

3.多trait输出顺序问题

【问题1】如果不实现class AAABBBCCC extends AAA with BBB with CCC{}里面的内容,输出如何?

【问题2】如果打印super.printInfo()结果怎样?

答:依然是with连接的最后一个trait。因为它是从左往右执行的(从后面往前面输出的)。

【问题3】如果用super[CCC].printInfo()去指定输出,结果怎样?

答:输出super指定的内容。

(四).动态混入

在Scala里面单独定义类时,不需要混入其他东西,而在使用时混入其他东西,可以起到解耦的作用。比如用new实例化产生对象的时候可以直接用with混入trait。

例:(此处借用(三)里面的代码)

class PK  //定义一个最普通的类,而没有混入其他东西

val pk =new PKwith AAA  //动态混入traitAAA

val pk =new PKwith AAAwith BBBwith CCC  //这个输出顺序和多trait输出顺序一样

(五).App Trait

scala中可以直接用extents App,这样可以不用main方法。App的底层包含了Trait。可以进入到App源码中查看。

使用:

object AppAppextends App{

println("水到渠成")

}

此外App除了里面定义了trait,还可以实现更为简洁的计时功能。如:

object AppAppextends App{

util.Properties.setProp("scala.time","true")

println("水到渠成")

Thread.sleep(5000)  //注意:括号里面直接输入输入时间就好了,不然millis会标红



已同步至:Scala面向对象编程_comer_liu的博客-CSDN博客

你可能感兴趣的:(Scala面向对象编程)