scala(九) 封装、继承与多态

2022-04-14 16:00:10 浏览数 (1)

封装

封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。 java封装操作如下:

  1. 将属性进行私有化
  2. 提供一个公共的set方法,用于对属性赋值
  3. 提供一个公共的get方法,用于获取属性的值

定义一个对象

代码语言:javascript复制
class Person{
  /**
   * id
   */
  private var id:Int=_
  /**
   * 姓名
   */
  private var name:String=_
  /**
   * 年龄
   */
  private var age:Int=_
  /**
   * 性别
   */
  private var sex:Char=_

}

在 java 中可以通过第三方工具快速生成 get/set方法;在scalal中(idea)不行。若需要添加get/set方法需要自己写。 如:

代码语言:javascript复制
  def getId():Int=this.id 
  
  def setId(id:Int)=this.id=id

在scala 为了符合JavaBean规范,专门提供了一个 注解 @BeanProperty 用于对属性生成 getter/setter 方法。 使用 @BeanProperty 是有限制的。

  1. 属性不能被 private 修饰
  2. 属性也不能声明成 val
代码语言:javascript复制
class Person{
  /**
   * id
   */
  @BeanProperty var id:Int=_
  /**
   * 姓名
   */
  @BeanProperty var name:String=_
  /**
   * 年龄
   */
  @BeanProperty var age:Int=_
  /**
   * 性别
   */
  @BeanProperty  var sex:Char=_
  
}

使用

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val p=new Person()
    p.setId(1001)
    p.setName("张三")
    p.setAge(19)
    p.setSex('男')

    val info=s"id=${p.getId},姓名=${p.getName},年纪=${p.getAge},性别=${p.getSex}"

    println(info) // id=1001,姓名=张三,年纪=19,性别=男

  }

疑问?使用get/set 不就是用来访问和操作私有属性的吗? 使用 @BeanProperty 居然还必须时 public 那么定义该注解的有何用? 如下:不使用 get/set 可以进行操作。

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val p=new Person()
    p.id=1001
    p.name="张三"
    p.age=19
    p.sex='男'

    val info=s"id=${p.id},姓名=${p.name},年纪=${p.age},性别=${p.sex}"

    println(info) // id=1001,姓名=张三,年纪=19,性别=男
  }

有没有小伙伴和我有一样的疑问? @BeanProperty 只是用于符合JavaBean规范,java很多api都遵循这个规范,scala若要去调用,也不得不去准寻这规范。严格意义上来说,scala的封装并不是封装。

还是用个案例来说明吧。json 可以用于将 JavaBean转为JSON或者将 JSON格式数据转为JavaBean。

导入 fastjson

代码语言:javascript复制
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.72</version>
</dependency>

对象 转 json

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val p=new Person()
    p.id=1001
    p.name="张三"
    p.age=19
    p.sex='男'
    
    // 对象 转 json 
    val jsonStr=JSON.toJSON(p).toString 

    println(jsonStr) // {"sex":"男","name":"张三","id":1001,"age":19}
  }

若没有 get/set 方法,将无法解析 json 转对象

代码语言:javascript复制
  class Person{
    /**
     * id
     */
     var id:Int=_
    /**
     * 姓名
     */
     var name:String=_
    /**
     * 年龄
     */
     var age:Int=_
    /**
     * 性别
     */
      var sex:Char=_
  }

  def main(args: Array[String]): Unit = {
    // 对象 转 json  
    //  classOf[Person] 用于获取  Person 的 class
    val p:Person=JSON.parseObject("""{"sex":"男","name":"张三","id":1001,"age":19}""",classOf[Person])

    val info=s"id=${p.id},姓名=${p.name},年纪=${p.age},性别=${p.sex}"

    println(info) // id=0,姓名=null,年纪=0,性别= 

  }

重新指定 @BeanProperty

代码语言:javascript复制
class Person{
    /**
     * id
     */
    @BeanProperty var id:Int=_
    /**
     * 姓名
     */
    @BeanProperty var name:String=_
    /**
     * 年龄
     */
    @BeanProperty var age:Int=_
    /**
     * 性别
     */
    @BeanProperty var sex:Char=_

  }

  def main(args: Array[String]): Unit = {
    // 对象 转 json  
    //  classOf[Person] 用于获取  Person 的 class
    val p:Person=JSON.parseObject("""{"sex":"男","name":"张三","id":1001,"age":19}""",classOf[Person])

    val info=s"id=${p.id},姓名=${p.name},年纪=${p.age},性别=${p.sex}"

    println(info) // id=1001,姓名=张三,年纪=19,性别=男

  }

关于 @BeanProperty 注解,我也不太明白为什么开发者会这么设计,对于我来说就是不合理。但他的作用就是用于兼容JavaApi。


继承

java中的继承 语法:

[修饰符] class 类名 extends 父类名{类体}

特性:

  1. 使用 extends 关键字用于继承
  2. 被标识为 final的类不能被继承,
  3. 只能单继承
  4. 被继承的类可以获取它所有非 private 修饰的属性和方法。
  5. 子类可以重写父类的方法

scala中的继承 语法:

class 类名[(参数列表)] extends 父类名[参数列表]

特性:

  1. 使用 extends 关键字用于继承
  2. 同java一致,scala也只能用于单继承。
  3. extends 并不是继承一个类的标志,也可用于 特质类上。
  4. private 修饰的不能被继承。

定义

代码语言:javascript复制
  class Person{
    
    var name="your name?"
    
    def add(x:Int,y:Int)=x y
    
  }

  class Student extends Person 

调用

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val stu=new Student
    println(stu.name) //  your name?

    println(stu.add(1,2)) // 3
  }

方法重写:Person 中有个add 方法,可能该方法并不是我们想要,我需要的功能是 x-y 此时就需要对该方法进行重写。 在scala 中重写父类中的方法,需要使用 override 关键字修饰。

代码语言:javascript复制
  class Student extends Person {
    // 重写 父类的 add 方法
    override def add(x: Int, y: Int): Int = x-y
  }

调用:

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val stu=new Student
    println(stu.name) // your name?

    println(stu.add(1,2)) // -1
  }

属性重写 在scala中除了可以对方法进行重写外,还可以对属性进行重写

代码语言:javascript复制
  class Student extends Person {
    
    name="王富贵"

    override def add(x: Int, y: Int): Int = x-y

  }
  def main(args: Array[String]): Unit = {
    val stu=new Student
    println(stu.name) // 王富贵

    println(stu.add(1,2)) // -1
  }

注意:以上这种不能称为重写,而只是在子类定义了一个与父类相同的属性,覆盖了父类中的属性,可以称为改了父类中该属性的值。 那么什么才叫属性重写呢?

  1. 父类中的属性是必须定义成 val ,不能使用private 修饰。
  2. 重写属性和重写方法一样,都会用到 override 关键字。

重新在Person中定义一个属性hobby

代码语言:javascript复制
  class Person{

    var name="your name?"
    val hobby="唱跳,rap"    
    

    def add(x:Int,y:Int)=x y

  }

Student中重新父类的 hobby 属性,比如打篮球

代码语言:javascript复制
  class Student extends Person {

    name="坤坤"
    override val hobby: String = "打篮球"

    override def add(x: Int, y: Int): Int = x-y

  }

运行

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val stu=new Student
    println(stu.name) // 坤坤
    println(stu.hobby) // 打篮球

    println(stu.add(1,2)) // -1
  }

温馨提示:没有冒犯任何姓名中带的人,不过我觉得,能看到此文章的人应该都是程序员吧?没有哪个程序员会是某垃圾的粉丝吧。


调用父类中的方法 在java中若要调用父类中的 方法,会使用supper 关键字,在scala中也是一样。

代码语言:javascript复制
  class Student extends Person {

    name="坤坤"
    override val hobby: String = "打篮球"

    override def add(x: Int, y: Int): Int = {
      // 调用父类中的 add 方法
      println(s"supper add=:${super.add(x,y)}")
      x-y
    }

  }

构造器 继承中父子类的构造器知识点也是很重要的。

  1. 若父类中声明了主构造器,子类必须向父类的主构造器中传参
代码语言:javascript复制
  // 父类
  class Person(val id:Int,val name:String){
  }
  // 子类
  class Student(override val id:Int, override val name:String, age:Int) extends Person(id,name){

  }
  1. 若父类中主构造器无参,副构造器有参数,继承父类时,可以不用对父类构造器赋值;表示默认使用父类的无参构造器。
代码语言:javascript复制
  class Person{

    def this(id:Int,name:String){
      this()
    }

  }

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

  }

也可以指定父类的副构造器;也是可以的。

代码语言:javascript复制
  class Person{

    def this(id:Int,name:String){
      this()
    }

  }

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

  }
  1. 调用父类构造器 在java中,使用 supper 关键字,并且必须指定在子类的第一行。 如:
代码语言:javascript复制
class A{}

class B extends A{
    // 调用父类构造器
    public B(){
        super();
    }    
}

在scala中也是如此。此时有个,有个问题,在scala中定义副构造器必须调用主构造器或其他副构造器。正常的情况下,this()会放在构造器的第一行,此时需要调用父类的构造器,是super()放在第一行还是this()放在第一行呢?

答案:都不行,scala不支持这么做。

多态

多态:父类的引用指向子类的实例。

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    
    val person:Person=new Student()
  }

思考一:person.add(1,2) 调用是父类的还是子类的?

代码语言:javascript复制
  def main(args: Array[String]): Unit = {
    val person:Person=new Student()
   println(person.add(1,2)) 
  }

不用想,肯定是子类的,上面的案例中,Student 重写了 Personadd方法。所以答案是 -1;这个应该知道。

思考二:person.name 是 "坤坤" 还是 "your name?"?

代码语言:javascript复制
  def main(args: Array[String]): Unit = {

    val person:Person=new Student()
    println(person.name) 

  }

答案是 "坤坤" 在scala 中 属性也具有多态性,这点和java一定要区分开。

回想java 中,对于方法,运行看右(new 的部分),对于属性,运行看左(引用部分)。

代码语言:javascript复制
public class Demo101 {

    public static void main(String[] args) {
        A a=new B();
        System.out.println(a.name);
        a.say();

    }
}

class A{

    String name="aaaa";

    public void say(){
        System.out.println("a...");
    }
}

class B extends A{

    String name="bbbb";
    public void say(){
        System.out.println("b...");
    }
}

虽然A和B都有 name属性,但是执行中结果仍然是父类A的name,而方法不一样,需要看new出来最终的对象是什么。 结果:

代码语言:javascript复制
aaaa
b...

这里涉及到了java 中静态分派与动态分派相关的概念

思考三: person.hobby 又是什么?

答案:肯定是 Student 中的 "打篮球";这个我就写,可以下去动手试试。

总结:

这就是 scala 中的 三大特性继承封装多态; 基本上和java类似,为了区分开的是 java中 属性不具备多态性,scala中属性具备多态性

0 人点赞