scala之面向对象

1. 要点

类:

  • 类中的字段自动带有getter方法和setter方法.
  • @BeanProperty注解来生成JavaBean的getXxx/setXxx方法
  • 主构造器, 主构造器的参数直接构成类的字段
  • 辅构造器, 需要提前调用主构造器this()
  • 权限修饰符
    • protected: 修饰类的成员, 只能在子父类中访问
    • private[package]: 在package包和它的子包中可以访问.
  • 抽象类: 相比java多了抽象字段的概念
  • 类型的判断和转换

继承:

  • 关键字extends
  • 覆写方法时必须使用override关键字, 并满足两同两小一大
  • 属性的覆写,可以覆写属性, 多态中属性的引用也是使用子类对象的
    • var只能覆写抽象的var, 有定义的var不能覆写
    • val可以覆写val和没有参数def
  • 只有主构造器才可以调用父类构造器

多态:

  • Scala中, 方法有多态, 属性也有多态!

特质:

  • 特质叠加冲突2种解决方式
    1. 在子类中覆写该方法
    2. 新增父类, 使用菱形继承关系

对象:

  • 用对象存作为单例或存放工具方法
  • 类可以拥有一个同名的伴生对象
    • 可以互相访问私有成员
    • 编译后, 伴生对象中的都为静态成员, 伴生类中的成员都为非静态成员.
  • apply: scala中任何对象都可以像函数一样去调用
  • 对象可以扩展或特质
    • 除显式定义main方法外, 可以用扩展App特质的对象
    • 扩展Enumeration对象来实现枚举

2. 面向对象概念

面向对象就是通过封装, 继承, 多态(面向对象的三大特征)把程序的耦合性降低, 提高程序的可维护性, 可扩展性, 可复用性.

3. 类

定义类

class Demo

属性的默认初始化值

  • 数字 0
  • 布尔值 false
  • 引用型 null

主构造器

类和java一样, 默认有空构造器, 但是一旦有定义构造器, 则不会再默认提供空构造器.

主构造器位置紧跟类名

class User(var name: String, val age: Int)

可以增加关键字private将构造器私有化

class private User(var name: String, val age: Int)

给类定义的所有的属性都是私有. 并且会给这些私有属性自动添加公共的gettersetter, 字节码文件反编译如下:

public class com.demo.scala.User {
  private java.lang.String name; // 属性私有
  private final int age; // 属性私有
  public java.lang.String name();   // 自动添加name属性的getter
  public void name_$eq(java.lang.String);  // 自动添加name属性的setter
  public int age(); // getter
}

@BeanProperty注解可以生成JavaBean的getXxx/setXxx方法

class User2(@BeanProperty var name: String, @BeanProperty val age: Int)

主构造器的3种参数:

  1. class demo(var name: String): name自动成为类的私有属性, 并自动生成setter()getter()方法
  2. class demo(val name: String): 生成getter()方法, 但不生成setter()方法
  3. class demo(name: String):不生成getter()方法, 也不生成setter()方法

构造器重载: 辅助构造器

辅助构造器与主构造器构成重载关系

辅助构造器的首行必须调用主构造器

辅助构造函数的参数, 仅仅是一个普通的参数, 不会成为类的属性

注意: 辅助构造器只能调用主构造, 父类的构造器由主构造器调用

class Demo(name: String){
    // 定义一个无参辅助构造器
    def this() = {
        // 首行必须调用主构造器
        this("lisi")
    }

    // 定义一个无参辅助构造器
    def this(age: Int) = {
        this("lisi")
        this.age = age
    }   
}

scala 的权限修饰符

  1. 用在外部类上:
    • 默认(public, 没有public关键字)

      class A

    • private 只能在当前表使用, 其他地方无法使用
  2. 用在类的内部成员(属性, 方法, 内部类)上:
    • 默认(public, 没有public关键字)
    • protected

      这个限制更加严格. 只能在子父类中访问, 即使在同包中也不能访问

      super.foo();
    • private

      只能在当前类中访问

      scala中做了一些改造, 精细化的控制, 可以指定访问的包和它的子包

      private[mode] def speak() = println("speak...")

      mode包和它的子包中可以访问.

抽象类

含有抽象方法或者抽象字段的类为抽象类

abstract class A{
    def f():Unit = {
        println("f.....")
    }

    //抽象方法
    def eat(): Unit

    //抽象字段
    var age: Int
    val sex: String
}

注意: 相比java多了抽象字段的概念

类型的判断和转换

有时候需要在使用多态后, 需要进行类型转换, 需要先判断类型, 然后再向下转型.

a.isInstanceOf[B]: 判断a是否为B的对象

a.asInstanceOf[B]: a转换B的类型

object Extra1 {
    def main(args: Array[String]): Unit = {
        val a:A = new B
        // java中判断类型:   a instanceof B
        if (a.isInstanceOf[B]) {  // 判断a是否为B的对象
            val b = a.asInstanceOf[B] // a转换B的类型
            b.foo()
        }
    }
}


class A
class B extends A{
    def foo() = println("foo...")
}

4. 继承

继承和java一样, 是扩展类的方式

面向对象的3大特征: 封装, 继承(单继承), 多态

继承的基本语法

class A
class B extends A

继承的时候构造的处理

  1. 在子类的辅构造器中, 必须先调用自己的主构造, 不能主动去调用父类的构造器.
  2. 只有主构造器才有权力去调用父类的构造器!!!

方法的覆写

java@Overreide注解可以省略. scala中方法覆写需要关键字override, 且不可以省略.

覆写的原则

两同, 两小, 一大

  • 两同: 方法名相同, 参数列表相同
  • 两小: 返回值, 异常类型小(相同或为子类)
  • 一大: 权限相同或更大

    java中的权限: public, protected(同包或子类), friendly(同包中访问), private

    scala中的权限: 默认(public, 但没该关键字), protected(子类), private

属性的覆写

scala中, 属性也可以覆写, 也具有多态!!!

属性覆写的规则:

  1. var只能覆写抽象的var, 有定义的var不能覆写
  2. val可以覆写val和没有参数def

5. 多态

scala中属性也有多态, 多态中属性的引用也是使用子类对象的属性.

6. 特质trait

trait的本质: 字节码之后, 还是接口,

trait是支持多混入

class A extends t1 with t2 with t3...

注意: trait的特性要比接口多, 但最常用的方式还是作为接口来使用.

叠加冲突

多个trait有相同实现好的方法的时,会产生冲突

  1. 解决方案1: 在子类中把冲突的方法覆写

1587905478770

  1. 新增一个父trait, 成菱形关系, 最终使用最后叠加的那个!!!
    1. 初始化的是, 一个trait最多初始化一次
    2. 初始化的时候是从父开始, 然后从左到右
    3. super.foo()不是真正的找父类, 而是按照叠加的顺序向前找
    4. super[T12].foo() 明确指定这个super应该是哪个类

1587905679513

特质继承类

  1. 方法1
    class A{
       def foo() = {
           println("A... foo")
       }
    }
    
    
    trait B extends A{
       def eat() = {
           println("B ... eat")
           foo()
       }
    }
    
    // 但继承: extends 要么是A要么A的子类
    class C extends A with B
  2. 使用自身类型(selftype)
    trait B{
       // s就是A类型的对象
       s: A =>
    
       def eat()= {
           println("B ... eat")
           s.foo()
       }
    }

动态叠加

java中所有的继承关系都应该在定义类是确定好.

scala支持特质的动态叠加. 在创建对象的时候, 可以临时只针对整个对象来叠加特质

```scala
object Trait5 {
def main(args: Array[String]): Unit = {
val h = new H with F1
h.foo()
}
}

class H

trait F1 {
def foo() = println("f1 foo...")
}
```

特质的字节码文件

Scala将特质编译成JVM的类和接口. 如果特质有具体方法, Scala会创建出一个伴生类, 该伴生类用静态方法存放特质的方法.

例如:

trait ConsoleLogger extends Logger {
    def log(msg: String) { println(msg)}
}

编译后

public interface ConsoleLogger extends Logger { // 生成java接口
    void log(String msg);
}

public class ConsoleLogger$class { // 生成java伴生类
    public static void log(ConsoleLogger self, String msg)
        println(msg);

}

7. 对象

单例对象

object 对象名{

    def main(args: Array[String]){

    }
}

java中的单例有两种, 饿汉式, 懒汉式, 涉及到多线程还需要双重判断

伴生类和伴生对象

一个类可以有一个和其同名的对象, 该对象就称为该类的伴生对象

  1. class的名字和object的名字相同
  2. 可以互相访问对方的私有成员
  3. 伴生类和伴生对象必须在同一个.scala文件中
  4. 将来编译成字节码之后, 站在java的角度, 伴生对象中的都是为成为静态成员, 伴生类中的成员都会成为非静态成员.

apply

函数的特点是可以直接调用, 在scala中, 任何对象也可以像函数一样去调用

// 函数调用:
函数名(参数)

// 对象调用
对象名(参数)

注意:

  1. 其实函数也可以通过apply进行调用. (方法不行), 如果是方法, 先把方法转成函数在使用.

    在Scala语言中, 函数也是对象, 每一个对象都是scala.FunctionN(1-22)的实例

  2. 伴生对象apply, 通常情况是返回伴生类的对象, 然后在外面创建对象的时候, 可以省略new
  3. 普通类中的apply, 一般根据具体的业务逻辑来实现.
  4. apply 也可以重载

8. 枚举

继承Enumeration

示例:

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

        println(Color.RED)
    }
}

// 枚举类
object Color extends Enumeration {
    val RED = Value(1, "red")
    val YELLOW = Value(2, "yellow")
    val BLUE = Value(3, "blue")
}

9. 内部类

类型投影: Outer#Inner

scala
def foo(obj: Outer#Inner)

示例:

两种方式在内部类中调用外部类成员:

  1. 通过Outer.this
  2. 通过自身类型that =>
object InnerDemo1 {
    def main(args: Array[String]): Unit = {
        val outer1 = new Outer("outer1")
        val inner1 = new outer1.Inner

        val outer2 = new Outer("outer2")
        val inner2 = new outer2.Inner

        inner1.foo(inner1)

        inner1.foo(inner2)
    }
}
class Outer(val outerName: String) {
    // 自身类型
    that =>
    val a = 20
    class Inner {
        val a = 10

        def foo(obj: Outer#Inner) = {
            println("Inner ... foo")
            println("inner = " + a)
            println("inner = " + this.a)
            // 外部类的对象
            println("outer = " + Outer.this.a)  //通过Outer.this, 调用外部类对象成员方法一
            println("outerName = " + that.outerName) //通过自身类型that =>, 调用外部类对象成员方法方法二
        }
    }

}

输出

Inner ... foo
inner = 10
inner = 10
outer = 20
outerName = outer1
Inner ... foo
inner = 10
inner = 10
outer = 20
outerName = outer1
原文地址:https://www.cnblogs.com/bitbitbyte/p/12782541.html