一.基本概念
(一).面向对象的三大特性
①封装:把属性、方法封装到一个类中
②继承:父类和子类之间(属性和方法可以重写的)
③多态:父类引用指向子类对象
比如父类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
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
⑤回到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
总结: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:重写的时候,输入某个重写的参数有提示,直接选中然后回车即可。
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()
}
}
导包方法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+改的名字即可
import com.ruozedata.scala.oo.pack1.{A => RuozedataA}
object B {
def main(args: Array[String]): Unit = {
val a3 = new RuozedataA //在main方法里面调用的时候就用新的名字了
a3.methodB()
}
}
(二).隐式转换
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 {
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")
}
}
七.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
【执行顺序】先父类再子类最后返回值(上图红色输入顺序;绿色执行顺序)
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)
【执行顺序】先父类再子类最后返回值(上图红色输入顺序;绿色执行顺序)
3.Trait的底层实现
打开Linux:
vi MemoryManager.scala
进入之后把定义trait的语句粘贴进去:
trait MemoryManager{
println("---MemoryManager---")
val name:String
val maxOnHeapStorageMemory:Long
}
scalac MemoryManager.scala
javap MemoryManager.class
由此可以看出,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博客