Scala特证/特质【6.7 特质(Trait)】
(任意内容)
此处输入任意想输入的内容 |
6.7 特质(Trait)
- Scala 语言中,采用特质 trait(特征)来代替接口的概念 ,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
- Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可
以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。 - Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种
补充。
Java 的接口
Java中的接口(Interface)是一种抽象类型,用于定义类应该具有的行为。接口可以看作是一组方法的集合,但是接口不能包含实现代码,只能定义方法的签名。类可以实现一个或多个接口,并提供接口中定义的方法的具体实现。
接口的定义使用interface
关键字,语法如下:
public interface MyInterface {
// 抽象方法
void abstractMethod();
// 默认方法
default void defaultMethod() {
// 方法实现
}
// 静态方法
static void staticMethod() {
// 方法实现
}
}
在Java中,类通过implements
关键字来实现接口:
public class MyClass implements MyInterface {
// 实现接口中的抽象方法
public void abstractMethod() {
// 方法实现
}
}
一个类可以实现多个接口,通过逗号分隔:
public class MyClass implements Interface1, Interface2 {
// 实现接口中的抽象方法
// ...
}
接口的作用
接口中的方法可以是抽象方法、默认方法(默认实现)和静态方法。抽象方法由实现类提供具体实现,而默认方法和静态方法可以直接在接口中提供实现代码。默认方法可以被实现类重写,静态方法则不能。
接口在Java中用于实现多态性和代码复用,它提供了一种契约机制,用于定义类的行为规范。通过实现接口,可以在不同的类中共享一组方法,提高代码的灵活性和可扩展性。接口在Java中被广泛应用于各种编程场景,例如回调机制、事件处理、依赖注入等。
接口在编程中起到了以下几个重要的作用:
-
定义契约:接口定义了一组方法的契约或规范,指定了类应该具备的行为。通过接口,可以明确地定义类应该提供哪些方法,以及这些方法应该如何被实现。接口提供了一种契约机制,帮助程序员在不同的类之间建立统一的行为规范。
-
实现多态:接口是实现多态性的关键。通过接口,可以实现类的多态性,使得不同的对象可以以统一的方式对待。通过接口的引用,可以引用不同实现类的对象,并调用它们实现的接口方法,实现了代码的灵活性和可扩展性。
-
代码复用:接口提供了一种代码复用的机制。通过接口,可以定义一组方法,并在多个类中实现这些方法。其他类可以实现相同的接口,从而复用接口中定义的方法。这种方式可以减少代码的重复编写,提高代码的可维护性和可复用性。
-
解耦和组件化:接口可以帮助解耦和实现组件化。通过接口,不同的模块或组件之间可以通过接口进行通信,而不需要了解具体的实现细节。这样可以降低模块之间的耦合度,提高系统的灵活性和可扩展性。
-
定义规范:接口可以被用作定义规范的工具。通过接口,可以定义一组方法和常量,并约束其他类去实现这些方法或使用这些常量。这样可以保证代码的一致性和可靠性。
总而言之,接口在编程中具有重要的作用,它定义了类的行为规范、实现多态性、实现代码复用、解耦和组件化,以及定义规范等。通过接口,可以提高代码的灵活性、可维护性和可扩展性,使得程序更加可靠和可重用。
抽象类的作用
抽象类在面向对象编程中起着重要的作用,它具有以下几个主要作用:
-
定义通用行为:抽象类可以定义一组通用的方法和属性,这些方法和属性可以被其子类继承和重写。抽象类可以提供一种通用的行为规范,让子类按照自己的需求进行具体实现。
-
实现代码复用:抽象类可以包含一些通用的方法的实现,这样子类就可以直接继承这些方法的实现,避免了代码的重复编写。通过抽象类的继承关系,可以实现代码的复用,提高代码的可维护性和可复用性。
-
实现多态性:抽象类可以作为父类,被其子类进行继承和扩展。通过抽象类的引用,可以引用不同子类的对象,实现多态性。这样可以在运行时根据实际对象的类型调用相应的方法,实现灵活的代码结构。
-
定义抽象方法:抽象类可以定义抽象方法,这些方法只有方法的声明而没有具体的实现。抽象方法必须由子类进行具体实现,通过抽象方法的定义,可以强制子类去实现特定的行为。
-
作为模板类:抽象类可以作为模板类,定义一些通用的方法和属性,提供给子类进行继承和使用。子类可以通过继承抽象类,并根据自己的需求进行具体实现和扩展,从而达到代码复用和模块化的目的。
总而言之,抽象类在面向对象编程中具有重要作用,它可以定义通用行为、实现代码复用、实现多态性、定义抽象方法和作为模板类。通过抽象类,可以提高代码的可维护性、可复用性和可扩展性,使得程序设计更加灵活和可靠。
6.7.1 特质声明
1)基本语法
trait 特质名 {
trait 主体
}
2)案例实操
trait PersonTrait {
// 声明属性
var name:String = _
// 声明方法
def eat():Unit={
}
// 抽象属性
var age:Int
// 抽象方法
def say():Unit
}
通过查看字节码,可以看到特质=抽象类+接口
6.7.2 特质基本语法
- 一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了 extends 关键字,如果有多个特质或存在父类,那么需要采用 with关键字连接。
2)说明
(1)类和特质的关系:使用继承的关系。
(2)当一个类去继承特质时,第一个连接词是 extends,后面是 with。
(3)如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。
3)案例实操
(1)特质可以同时拥有抽象方法和具体方法
(2)一个类可以混入(mixin)多个特质
(3)所有的 Java 接口都可以当做 Scala 特质使用
(4)动态混入:可灵活的扩展类的功能
- (4.1)动态混入:创建对象时混入 trait,而无需使类混入该 trait
- (4.2)如果混入的 trait 中有未实现的方法,则需要实现
trait PersonTrait {
//(1)特质可以同时拥有抽象方法和具体方法
// 声明属性
var name: String = _
// 抽象属性
var age: Int
// 声明方法
def eat(): Unit = {
println("eat")
}
// 抽象方法
def say(): Unit
}
trait SexTrait {
var sex: String
}
//(2)一个类可以实现/继承多个特质
//(3)所有的 Java 接口都可以当做 Scala 特质使用
class Teacher extends PersonTrait with java.io.Serializable {
override def say(): Unit = {
println("say")
}
override var age: Int = _
}
object TestTrait {
def main(args: Array[String]): Unit = {
val teacher = new Teacher
teacher.say()
teacher.eat()
//(4)动态混入:可灵活的扩展类的功能
val t2 = new Teacher with SexTrait {
override var sex: String = "男"
}
//调用混入 trait 的属性
println(t2.sex)
}
}
6.7.3 特质叠加
- 由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。
冲突分为以下两种:
-
第一种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且
两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
-
第二种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且
两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala
采用了特质叠加的策略。
所谓的特质叠加,就是将混入的多个 trait 中的冲突方法叠加起来,案例如下,
trait Ball {
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.describe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.describe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"my ball is a " + super.describe()
}
}
object TestTrait {
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
结果如下:
6.7.4 特质叠加执行顺序
思考: 上述案例中的 super.describe()调用的是父 trait 中的方法吗?
- 当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行
排序,而此案例中的 super.describe()调用的实际上是排好序后的下一个特质中的 describe()
方法。,排序规则如下:
结论:
6.7.5 特质自身类型
1)说明
- 自身类型可实现依赖注入的功能。
2)案例实操
class User(val name: String, val age: Int)
trait Dao {
def insert(user: User) = {
println("insert into database :" + user.name)
}
}
trait APP {
_: Dao =>
def login(user: User): Unit = {
println("login :" + user.name)
insert(user)
}
}
object MyApp extends APP with Dao {
def main(args: Array[String]): Unit = {
login(new User("bobo", 11))
}
}
6.7.6 特质和抽象类的区别
1.优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
2.如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,
而特质不行(有无参构造)。
3.特质(Trait)和抽象类(Abstract Class)是Scala语言中两种常见的代码组织方式,它们有一些区别和特点。
-
继承关系:特质和抽象类都可以被其他类继承。但是,一个类只能继承一个抽象类,而可以同时继承多个特质(多重继承)。
-
单继承 vs. 多重混入:由于Scala只支持单继承,所以通过继承一个抽象类,子类只能继承一个父类的特性。而通过混入特质,可以将多个特质的功能组合到一个类中,实现多重混入。
-
构造器:抽象类可以有构造器,而特质不能直接拥有构造器。特质可以通过在类中混入时传递参数的方式来影响类的行为。
-
抽象方法和具体方法:抽象类可以包含抽象方法和具体方法。抽象方法只有声明而没有具体实现,由子类实现。而特质可以包含抽象方法和具体方法,特质中的方法默认是抽象的,但也可以提供默认的实现。
-
实例化:抽象类不能直接实例化,只能被继承。而特质也不能直接实例化,但可以通过混入到具体类中来影响类的行为。
-
使用场景:抽象类通常用于定义一些共性的行为和状态,被子类继承和扩展。特质通常用于定义一些可复用的代码片段,可以被多个类混入,实现代码的复用和模块化。
总而言之,特质和抽象类在Scala中有不同的用途和特点。抽象类用于定义类的继承关系和共性行为,而特质用于实现代码的复用和模块化,可以多重混入到类中。根据具体的需求,可以选择合适的方式来组织代码结构。