Scala 抽象类 ( abstract classe ) 特质 (trait)

一、世界观

1. Scala 是纯面向对象的语言,所以在 Scala 中没有接口。

抽象类( abstract classe )可以说是面向对象的,而接口( interface )的本质是对继承机制的一个补充,从面向对象来看,接口并不属于面向对象的范畴。

理解 trait 等价于 Java 中接口加上抽象类(interface + abstract class),也就是说,trait 既可以有抽象方法,有可以有非抽象方法,普通方法,有多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。

1. Java中接口的使用,是通过类来实现接口,使用的关键字是implements。

2. Scala中类不是实现特质,而是动态混入[mixin]特质,使用的关键字是extends或with。

3. override关键字。如果重写的是普通方法,则必须加上override关键字;如果重写的是抽象方法,可以省略override关键字。

Scala中的术语mixin是指若干trait,这些trait可以用于合成一个类。

    // 抽象类A,包含一个字符串类型的成员属性message
    abstract class A {
      val message: String
    }
    // 类B继承A,并定义了具体定义了属性message的值
    class B extends A {
      val message = "I'm an instance of class B"
    }
    // trait C继承A,添加了一个loudMessage方法
    trait C extends A {
      def loudMessage = message.toUpperCase()
    }
    // 类D继承B并实现C
    class D extends B with C
    // 实例化一个D
    val d = new D
    d.message  // I'm an instance of class B
    d.loudMessage  // I'M AN INSTANCE OF CLASS B

类D有一个父类B和一个mixin C。Scala不支持多继承,也就是说一个类只能有一个父类。但可以有多个mixin。继承一个类使用extends 关键词,扩展一个mixin使用with 关键词。

动态混入可以在不影响原有继承关系地基础上,给指定的类扩展功能。Scala 加入了动态混入[mixin],就像接口解耦


    object MixINDemo {
      def main(args: Array[String]): Unit = {
        //普通类动态混入
        val oraclDB = new OracleDB with Opreater
        oraclDB.insert(100)
        //抽象类的动态混入
        val mySQL = new MySQL with Opreater
        mySQL.insert(200)
        //如果一个抽象类有抽象方法,动态混入的方法
        val mysql = new MySQL_ with Opreater {
          override def say(): Unit = {
            println("我是overwrite 抽象类的方法,")
          }
        }
        mysql.insert(90)
        mysql.say()
      }

    }
    trait Opreater{//特质
      def insert(id:Int):Unit={
        println("插入数据" + id)
      }

    }
    class OracleDB{}
    abstract class MySQL{}
    abstract class MySQL_{
      def say()
    }

2. Scala和Java,二者的抽象类的本质是一样的

- 抽象类中可以有抽象方法和普通方法。

- 有抽象方法的类则一定要定义成抽象类

- 抽象方法前面不需加abstract关键字,没有方法体的方法的便是抽象方法了,这一点跟Java中的不太一样。
    // 抽象类
    abstract class Animal(val age: Int) {

        // 抽象方法

        def eat()

        // 普通的方法

        def sleep(sound: String) = {

            println(s"sleep with $sound")

        }

    }

二、Scala 什么时候应该使用特质而不是抽象类?

  • 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
  • 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。
  • Whenever you implement a reusable collection of behavior, you will have to decide whether you want to use a trait or an abstract class. There is no firm rule, but this section contains a few guidelines to consider.
  • If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all.
  • If it might be reused in multiple, unrelated classes, make it a trait. Only traits can be mixed into different parts of the class hierarchy.
  • If you want to inherit from it in Java code, use an abstract class. Since traits with code do not have a close Java analog, it tends to be awkward to inherit from a trait in a Java class. Inheriting from a Scala class, meanwhile, is exactly like inheriting from a Java class. As one exception, a Scala trait with only abstract members translates directly to a Java interface, so you should feel free to define such traits even if you expect Java code to inherit from it. See Chapter 29 for more information on working with Java and Scala together.
  • If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class. The issue is that when a trait gains or loses a member, any classes that inherit from it must be recompiled, even if they have not changed. If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine.
  • If efficiency is very important, lean towards using a class. Most Java runtimes make a virtual method invocation of a class member a faster operation than an interface method invocation. Traits get compiled to interfaces and therefore may pay a slight performance overhead. However, you should make this choice only if you know that the trait in question constitutes a performance bottleneck and have evidence that using a class instead actually solves the problem.
  • If you still do not know, after considering the above, then start by making it as a trait. You can always change it later, and in general using a trait keeps more options open.

trait 和 abstract classe 主要区别

  • Abstract classes can have constructor parameters as well as type parameters. Traits can have only type parameters. There was some discussion that in future even traits can have constructor parameters
  • Abstract classes are fully interoperable with Java. You can call them from Java code without any wrappers. Traits are fully interoperable only if they do not contain any implementation code
  • Very important addendum: A class can inherit from multiple traits but only one abstract class. I think this should be the first question a developer asks when considering which to use in almost all cases.

三、Java抽象类和接口的对比

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。(java8以后接口可以有default和static方法,所以可以运行main方法)
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

Java 中什么时候使用抽象类和接口

  • 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。

    例如:Spring的依赖注入就使得代码实现了集合框架中的接口原则和抽象实现。

  • 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。

  • 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

什么时候该用抽象类,什么时候该用接口呢?

  1. 要解决上面的问题,我们先从弄清楚抽象类和接口之间的关系。首先,我们都知道类对事物的抽象,定义了事物的属性和行为。而抽象类是不完全的类,具有抽象方法。接口则比类的抽象层次更高。所以,我们可以这样理解它们之间的关系:类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。

  2. 从这个角度来看 java 容器类,你会发现,它的设计正体现了这种关系。不是吗?从 Iterable 接口,到 AbstractList 抽象类,再到 ArrayList 类。

  3. 在设计类的时候,首先考虑用接口抽象出类的特性,当你发现某些方法可以复用的时候,可以使用抽象类来复用代码。简单说,接口用于抽象事物的特性,抽象类用于代码复用

  4. 模式和语法是死的,人是活的

ArrayList 的类继承关系。可以看到,ArrayList 的继承关系中既使用了抽象类,也使用了接口。

  • 最顶层的接口是 Iterable,表示这是可迭代的类型。所有容器类都是可迭代的,这是一个极高的抽象。
  • 第二层的接口是 Collection,这是单一元素容器的接口。集合,列表都属于此类。
  • 第三层的接口是 List,这是所有列表的接口。