Scala基础 -- 4.类、对象、继承、特质

4 类、对象、继承、特质

4.1 类

4.1.1 类的定义
object ScalaTest2_1_1{

    /**
      * 在Scala中,类并不用声明为public类型的。
      * Scala源文件中可以包含多个类,所有这些类都具有共有可见性。
      */
    def main(args: Array[String]): Unit = {
        //和Java中有些不一样,如果是无参构造,则不用括号,加上也没错
        val c = new ScalaTest2_1_1

        //val类型的不支持重新赋值,但是可以获取到值
//        c.id = "123"
        println("id: " + c.id)
        //var 可获取,可修改
        c.age = 20
        println("age: " + c.age)
        //打印name,伴生对象中可以在访问private变量
        c.name = "list"
        println("name: " + c.name)
        //由于pet字段用private[this]修饰,伴生对象中访问不到pet变量
//        c.pet
    }
}

class ScalaTest2_1_1 {
    //用val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
    val id = "9827"
    //用var修饰的变量都既有getter,又有setter
    var age = 18
    //类私有字段,只能在类的内部使用或者伴生对象中访问
    private var name = "zhangsan"
    //类私有字段,访问权限更加严格的,该字段在当前类中被访问
    //在伴生对象里面也不可以访问
    private[this] var pet = "ddd"
}
4.1.2 类的构造器
object ScalaTest2_1_2{

    def main(args: Array[String]): Unit = {
        val c1 = new ScalaTest2_1_2("zhangsan", 18)
        println("==========================")
        val c2 = new ScalaTest2_1_2("lisi", 20, "female")
    }
}

/**
  * 1.2.Scala的类的构造
  * Scala中的每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。
  */
class ScalaTest2_1_2(val name: String, var age: Int) {

    //注意:主构造器会执行类定义中的所有语句。
    println("执行主构造器")

    private var gender = "male"
    //辅助构造器,执行辅助构造器的第一句必须执行主构造器,就像java中构造器第一句默认为super一样
    def this(name: String, age: Int, gender: String) {
        this(name, age)
        println("执行辅助构造器")
        this.gender = gender
    }
}

4.2 对象

4.2.1 object
object ScalaTest9_1{
    /**
      * 3.Scala的对象object
      * object 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;
      * 在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的
      *
      * object作用:
      *  1.存放工具方法和常量
      *  2.高效共享单个不可变的实例
      *  3.单例模式
      */
    def main(args: Array[String]): Unit = {
        //通过单例工厂获取单例对象
        val test1 = TestFactory.getTest()
        println(test1)
        //通过访问单例工厂的属性直接获取单例对象
        val test2 = TestFactory.test
        println(test2)
        //两者皆为一个对象,为单例模式
    }
}
object TestFactory {
    //初始化一个session对象,并且不可改变
    val test = new Test
    //代码块的最后一个表达式即为块的最终结果,即方法的结果,返回一个单例的Session对象
    def getTest() : Test = {
        test
    }
}
class Test {}
4.2.2 伴生对象
//伴生对象
object ScalaTest2_2_2{
    /**
      *  如果有一个class文件,还有一个与class同名的object文件,那么就称这个object是class的伴生对象,class是object的伴生类;
      *  伴生类和伴生对象必须存放在一个.scala文件中;
      *  伴生类和伴生对象的最大特点是,可以相互访问;
      */

    //伴生对象中的私有属性
    private val CONSTANT = "ABC"

    def main(args: Array[String]): Unit = {
        val a = new ScalaTest2_2_2
        //访问伴生类的私有字段name
        a.name = "list"
        a.printName()
    }

}
//伴生类
class ScalaTest2_2_2 {

    val id = 1
    private var name = "zhangsan"

    def printName(): Unit = {
        //在伴生类中可以访问伴生对象的私有属性
        println(ScalaTest2_2_2.CONSTANT + " " + name)
    }
}
4.2.3 apply方法
/**
  * object 中非常重要的一个特殊方法,就是apply方法;
  * apply方法通常是在伴生对象中实现的,其目的是,通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
  * 通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,...参数n)时apply方法会被调用;
  * 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用 class(),隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
  */

/**
  *  Array 类的伴生对象中,就实现了可接收变长参数的 apply 方法,
  * 并通过创建一个 Array 类的实例化对象,实现了伴生对象的构造函数功能
  */
def main(args: Array[String]): Unit = {
    //调用了Array伴生对象的apply方法,赋值
    //arr1是长度为1,值为5的数组
    val arr1 = Array(5)
    println(arr1.toBuffer)

    //new了一个Array对象,长度为5,数组里面包含5个null
    val arr2 = new Array[Int](8)
    println(arr2.toBuffer)
}
4.2.4 main方法
object ScalaTest9_4{
    /**
      * 在 Scala 中,也必须要有一个 main 方法,作为入口;
      * Scala 中的 main 方法定义为 def main(args: Array[String]),而且必须定义在 object 中;
      * 除了自己实现 main 方法之外,还可以继承 App Trait,然后,将需要写在 main 方法中运行的代码,
      * 直接作为 object 的 constructor 代码即可,而且还可以使用 args 接收传入的参数;
      */
    def main(args: Array[String]): Unit = {
        if (args.length > 0) {
            println("hello " + args(0))
        } else {
            println("Hello World")
        }
    }
}
// 使用继承App Trait ,将需要写在 main 方法中运行的代码
// 直接作为 object 的 constructor 代码即可
object MainDemo2 extends App{
    if(args.length > 0){
        println("Hello"+ args(0))
    }else{
        println("Hello World")
    }
}

4.3 继承

4.3.1 继承概念和override关键字

  a) Scala 中,让子类继承父类,与 Java 一样,也是使用 extends 关键字;
  b) 继承就代表,子类可继承父类的 field 和 method ,然后子类还可以在自己的内部实现父类没有的,子类特有的 field 和method,使用继承可以有效复用代码;
  c) 子类可以覆盖父类的 field 和 method,但是如果父类用 final 修饰,或者 field 和 method 用 final 修饰,则该类是无法被继承的,或者 field 和 method 是无法被覆盖的。
  d) private 修饰的 field 和 method 不可以被子类继承,只能在类的内部使用;
  e) field 必须要被定义成 val 的形式才能被继承,并且还要使用 override 关键字。 因为 var 修饰的 field 是可变的,在子类中可直接引用被赋值,不需要被继承;即 val 修饰的才允许被继承,var 修饰的只允许被引用。继承就是改变、覆盖的意思。
  f) Java 中的访问控制权限,同样适用于 Scala

类内部本包子类外部包
public
protected×
default××
private×××
class ScalaTest3_1{
    /**
      * Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字;子类可以覆盖父类的 val 修饰的field,只要在子类中使用 override 关键字即可。
      * override 关键字可以帮助开发者尽早的发现代码中的错误,比如, override 修饰的父类方法的方法名拼写错误。
      * 此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用 super 关键字,显示的指出要调用的父类方法
      */
    val name = "super"
    def getName = this.name
}

class Student extends ScalaTest3_1 {
    //继承加上关键字
    override
    val name = "sub"
    //或者
//    override val name = "sub"

    //子类可以定义自己的field和method
    val score = "A"

    //覆盖父类非抽象方法,必须要使用 override 关键字
    //同时调用父类的方法,使用super关键字
    override def getName: String = super.getName
}
4.3.2 类型判断和类型转换
object ScalaTest3_2{
    /**
      * 可以使用isInstanceOf判断对象是否为指定类的对象,如果是的话,则可以使用 asInstanceOf 将对象转换为指定类型;
      * 如果对象是 null,则 isInstanceOf 一定返回 false, asInstanceOf 一定返回 null
      *
      * isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象
      * 如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 了;
      * p.getClass 可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用 == 操作符即可判断;
      */
    def main(args: Array[String]): Unit = {

        val p: Person1 = new Student1
        var s: Student1 = null
        //如果对象是 null,则 isInstanceOf 一定返回 false
        println(s.isInstanceOf[Student1])

        // 判断 p 是否为 Student1 对象的实例
        if(p.isInstanceOf[Student1]) {
            //把 p 转换成 Student1 对象的实例
            s = p.asInstanceOf[Student1]
        }
        println(s.isInstanceOf[Student1])

        //判断p的类型是否为Person1类或者Student1类
        println(p.getClass == classOf[Person1])
        println(p.getClass == classOf[Student1])
        println(p.isInstanceOf[Person1])
    }
}
class  Person1 {}
class Student1 extends Person1
4.3.3 模式匹配
object ScalaTest3_3{
    /**
      * spark 源码中,大量的地方使用了模式匹配的语法进行类型的判断,这种方式更加地简洁明了,而且代码的可维护性和可扩展性也非常高
      * 使用模式匹配,功能性上来说,与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断
      * 等同于 Java 中的 switch case 语法
      */
    def main(args: Array[String]): Unit = {
        val p = new Student2
        p match {
            // 匹配是否为Person类或其子类对象
            case p: Person2 => println("This is a Person2's Object")
            //模式匹配,当一种匹配到,则不会再往下匹配了,上面的匹配的是Person类或其子类,已经包括了Student2
            case p: Student2 => println("This is a Student2's Object")
            // 匹配所有剩余情况
            case _ => println("Unknown Type")
        }
    }
}
class Person2
class Student2 extends Person2
4.3.4 protected关键字
object ScalaTest3_4{
    /**
      * 跟 Java 一样,Scala 中同样可使用 protected 关键字来修饰 field 和 method
      * 在子类中,可直接访问父类的 field 和 method,而不需要使用 super 关键字
      * 还可以使用 protected[this] 关键字, 访问权限的保护范围:只允许在当前子类中访问父类的 field 和 method
      */
    def main(args: Array[String]): Unit = {
        val s = new Student3
        s.sayHello
        s.makeFriends()
        s.sayByeBye
    }
}
class Person3 {
    protected  var name = "tom"
    protected[this] var hobby = "game"
    protected def sayBye = println("bye")
}
class Student3 extends Person3 {
    //父类使用protected 关键字来修饰 field可以直接访问
    def sayHello = println("Hello " + name)
    //父类使用protected 关键字来修饰method可以直接访问
    def sayByeBye = sayBye
    def makeFriends() = {
        println("My Hobby is " + hobby + ", your hobby is unknown")
    }
}
4.3.5 调用父类构造
object ScalaTest3_5{
    /**
      * Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,
      * 而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;
      * 因此子类的辅助constructor是一定不可能直接调用父类的constructor的
      * 只能在子类的主constructor中调用父类的constructor
      */
    def main(args: Array[String]): Unit = {
        val s = new Student4("zhangsan", 200.0)
    }
}
class Person4(val name: String, val age: Int) {

    var score: Double = 0.0
    var address: String = "beijing"
    println(s"主构造:name:$name, age:$age, score:$score, address:$address")

    def this (name: String, score: Double) {
        this(name, 30)
        this.score = score
        println(s"辅助构造:name:$name, age:$age, score:$score, address:$address")
    }
}
class Student4(name: String, score: Double) extends Person4(name, score)
4.3.6 匿名内部类
object ScalaTest3_6{
    /**
      * 在Scala中,匿名内部类是非常常见的,而且功能强大。Spark的源码中大量的使用了匿名内部类
      * 匿名内部类,就是定义一个没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量
      * 即匿名内部类的实例化对象。然后将该对象传递给其他函数使用
      */
    def main(args: Array[String]): Unit = {
        val p = new Person("tom")
        val d = new Demo
        d.greeting(p)
    }
}
class Person(val name: String){
    def sayHello = "Hello, I'm " + name
}
class Demo{
    //接受Person参数,并规定Person类只含有一个返回String的sayHello方法
    def greeting (p: Person { def sayHello: String }) = {
        println(p.sayHello)
    }
}
4.3.7 抽象类和抽象Field
object ScalaTest3_7{
    /**
      * 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法,
      * 此时可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法
      * 一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的
      * 在子类中覆盖抽象类的抽象方法时,可以不加override关键字
      */
    def main(args: Array[String]): Unit = {
        val s = new Student5("tom")
        println(s.sayHello)
        print(s.age)
    }
}
//如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field
abstract class Person5(val name: String) {
    //抽象fields
    val age: Int
    //必须指出返回类型,不然默认返回为Unit
    def sayHello: String
}
class Student5(name: String) extends Person5(name: String) {
    //在子类中实现父类的抽象Field
    val age: Int = 50
    //必须指出返回类型,不然默认
    def sayHello: String = "Hello " + name
}

4.4 特质

4.4.1 特质trait概念
object ScalaTest4_1{
    /**
      *    Scala中的trait是一种特殊的概念
      * 首先先将trait作为接口使用,此时的trait就与Java中的接口 (interface)非常类似
      * 在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可
      * 在Scala中没有 implement 的概念,无论继承类还是trait,统一都是 extends
      * 类继承后,必须实现其中的抽象方法,实现时,不需要使用 override 关键字
      * Scala不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可
      */
    def main(args: Array[String]): Unit = {
        val c1 = new Children("tom")
        val c2 = new Children("jerry")
        c1.sayHello
        c1.mkFriends(c2)
    }
}
trait Hello {
    def sayHello
}
trait Friends {
    def mkFriends(c: Children)
}
//多重继承
class Children(val name: String) extends Hello with Friends with Cloneable with Serializable {
    def sayHello:Unit = println("Hello " + this.name)
    def mkFriends(c:Children): Unit = println("My name is " + this.name + " and your name is " + c.name)
}
4.4.2 具体方法、具体属性和抽象属性
object ScalaTest4_2{
    /**
      * Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具,可以认为trait还包含了类的功能。
      * 比如 trait 中可以包含很多子类都通用的方法,例如打印日志或其他工具方法等等
      * spark就使用trait定义了通用的日志打印方法
      *
      * Scala 中的 trait 可以定义具体的 field,此时继承 trait 的子类就自动获得了 trait 中定义的 field
      * 但是这种获取 field 的方式与继承 class 的是不同的。 如果是继承 class 获取的 field
      * 实际上还是定义在父类中的;而继承 trait获取的 field,就直接被添加到子类中了
      *
      * Scala中的trait也能定义抽象field, 而trait中的具体方法也能基于抽象field编写
      * 继承trait的类,则必须覆盖抽象field,提供具体的值
      */
    def main(args: Array[String]): Unit = {
        val p1 = new PersonForLogger("tom")
        val p2 = new PersonForLogger("jerry")
        p1.mkFriends(p2)
    }
}
trait Logger {
    //Trait中的具体Field和具体方法
    val age: Int = 40
    def log(message: String): Unit = println(message)
    //抽象field
    val msg: String
}
class PersonForLogger(val name: String) extends Logger {
    //必须覆盖
    val msg = "Hello"
    def mkFriends(other: PersonForLogger) = {
        println(this.msg + other.name + ", My name is " + this.name + ", My age is " + this.age)
        this.log("mkFriends method is invoked with parameter [name=" + other.name + "]")
    }
}
4.4.3 Trait调用链
object ScalaTest4_3{
    /**
      * Scala中支持让类继承多个trait后,可依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行 super 关键字即可
      * 类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
      * 这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现
      */
    def main(args: Array[String]): Unit = {
        val p = new PersonForRespLine("tom")
        p.sayHello
    }
}
trait HandlerTrait {
    def handle(data: String) = println("last one")
}
trait DataValidHandlerTrait extends HandlerTrait {
    override def handle(data: String): Unit = {
        println("check data: " + data)
        super.handle(data)
    }
}
trait SignatureValidHandlerTrait extends HandlerTrait {
    override def handle(data: String): Unit = {
        println("check signature: " + data)
        super.handle(data)
    }
}
class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
    def sayHello = {
        println("Hello " + this.name)
        this.handle(this.name)
    }
}
4.4.4 Trait的构造机制
object ScalaTest4_4{
    /**
      * 在Scala中,trait也是有构造代码的,即在trait中,不包含在任何方法中的代码
      * 继承了trait的子类,其构造机制如下:
      *     父类的构造函数先执行, class 类必须放在最左边;多个trait从左向右依次执行
      *     构造trait时,先构造父 trait,如果多个trait继承同一个父trait,则父trait只会构造一次
      *     所有trait构造完毕之后,子类的构造函数最后执行
      */
    def main(args: Array[String]): Unit = {
        new Student_One
    }
}
class Person_One { println("Person_One's Constructor") }
trait Logger_One { println("Logger's Constructor") }
trait MyLogger_One extends Logger_One { println("MyLogger's Constructor") }
trait TimeLogger_One extends Logger_One { println("TimeLogger_One's Constructor") }
class Student_One extends Person_One with MyLogger_One with TimeLogger_One { println("Student's Constructor") }
4.4.5 Trait继承class
object ScalaTest4_5{
    /**
      * 在Scala中trait 也可以继承 class,此时这个 class 就会成为所有继承该 trait 的子类的超级父类
      */
    def main(args: Array[String]): Unit = {
        val p = new Person_Two("tom")
        p.sayHello
    }
}
class MyUtil {
    def printMsg(msg: String) = println(msg)
}
trait Logger_Two extends MyUtil {
    def log(msg: String) = this.printMsg("log: " + msg)
}
class Person_Two(val name: String) extends Logger_Two {
    def sayHello: Unit = {
        this.log("Hello " + this.name)
        this.printMsg("Hello " + this.name + ", I'm super class")
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值