接口/特质
java接口回顾
定义:interface 接口名
实现接口方式: class 类名 implements 接口1,接口2
总结:
1)java 中一个类可以实现多个接口
2)java中接口之间支持多继承
3)接口中属性都是常量
4)接口中方法都是抽象的
scala特质
1)从面向对象来看,接口并不属于面相对象的范畴,scala是纯面向对象的语言,在scala中没有接口
2)scala中采用trait(特征)来代替接口的概念,多个类具有相同的特征时,可以用trait独立出来,trait功能等价于 interface +abstract class
3) scala支持特质的混入,可以在不修改类继承关系的基础上动态为类增加某个特征
4)特征的声明
trait 特质名{
trait 体
}
5)特征的命名 一般首字母大写
6)scala中,java接口都可以当做特质使用
7)一个类具有某个特质,就意味着类满足了这个特质的所有要素。使用时也采用extends关键字,如果有多个特质或存在父类则用with关键字连接
没有父类: class 类名 extends 特质1 with 特质2 with 特质3
有父类:class 类名 extends 父类 with 特质1 with 特质2
8)特质可以同时拥有抽象方法和具体方法,一个类可以实现多个特质
9)特质中更没有实现的方法就是抽象方法,通过extends
示例
trait Trait01{
def getConnect()//声明一个规范
def add(): Unit ={
println("======插入操作")
}
}
trait Trait02{
def back()//声明一个规范
def delete(): Unit ={
println("======插入操作")
}
}
class A{
def A(): Unit ={
println("---A")
}
}
class C extends A with Trait01 with Trait02 {
override def getConnect(): Unit = {
println("连接 mysql 数据库...")
}
override def back(): Unit = {
println("备份数据")
}
}
反编译结果:
public class C extends A
implements Trait01, Trait02
{
public void delete()
{
Trait02.delete$(this); }
public void add() { Trait02.add$(this); }
public void getConnect() { Predef..MODULE$.println("连接 mysql 数据库...");
}
public void back() {
Predef..MODULE$.println("备份数据");
}
public C()
{
Trait02.$init$(this); Trait02.$init$(this);
}
}
public abstract interface Trait01
{
public abstract void getConnect();
public void add()
{
Predef..MODULE$.println("======插入操作");
}
public static void $init$(Trait01 $this)
{
}
}
public abstract interface Trait02
{
public abstract void back();
public void delete()
{
Predef..MODULE$.println("======插入操作");
}
public static void $init$(Trait02 $this)
{
}
}
动态混入
1)除了可以在类声明时继承特质外,还可以在构建对象时混入特质,扩展目标类的功能
2)这种方式也可以应用于对抽象类功能进行扩展
3)动态混入是scala特有的方式,可在不改变类声明的前提下扩展类的功能,非常灵活,降低耦合性
4)动态混入可以再不影响原有继承关系的基础上,给指定的类扩展功能
5)如果抽象类有抽象方法,如何混入?
混入后,底层生成的class文件并没有变化,但是生成了匿名类,
示例
trait Trait01{
def insert(id:Int): Unit ={
println("======插入操作"+id)
}
}
class Oracle{
}
abstract class Mysql3{
def del()
}
匿名类反编译
public final class class9$$anon$1 extends Oracle
implements Trait01
{
public void insert(int id)
{
Trait01.insert$(this, id);
}
}
public final class class9$$anon$2 extends Mysql3
implements Trait01
{
public void insert(int id)
{
Trait01.insert$(this, id); }
public void del() { Predef..MODULE$.println("-----删除数据");
}
}
叠加特质
构建对象时如果同时混入多个特质,称之为叠加特质,叠加特质的声明顺序从左到右,方法执行顺序从右到左
注意事项
1)特质声明的顺序从左到右
2)Scala中执行叠加对象的方法时,会先从右边的特质开始执行
3)Scala中特质中如果调用了super,并不一定是调用父特质的方法,而是向前边(左边)继续查找特质,如果找不到才会去父特质查找
(如果多个特质方法重复了 应该就是最右边的特质方法最先被执行,如果调用了super 的方法,则查找执行左侧特质的该方法)
4)如果想要调用具体特质的方法,可以执行 super[特质].xxx() ,其中反省必须是该特质的直接超类类型
示例:
trait Trait01{
println("Trait01")
def insert(id:Int)
}
trait Trait02 extends Trait01 {
println("Trait 02")
override def insert(id:Int): Unit ={
println("trait02插入数据 id"+id)
}
}
trait Trait03 extends Trait02 {
println("Trait03")
override def insert(id:Int): Unit ={
println("向数据库"+id)
super.insert(id)
}
}
trait Trait04 extends Trait02 {
println("Trait04")
override def insert(id:Int): Unit ={
println("向文件"+id)
super.insert(id) //这里调用了super 的insert 方法,这里super在动态混入时不一定就是父类
}
}
class Oracle{
}
val oracle = new Oracle() with Trait03 with Trait04 {
}
oracle.insert(12)
输出结果
可以看到初始化结果先初始化trait03 再初始化 trait04 顺序是从左到右
Trait01
Trait 02
Trait03
Trait04
可以执行顺序为 先执行trait04 再执行 trait03 的inser方法 顺序是右到左
当有调用super时 并不是执行的父类的方法而是 执行的左面一个特质的方法,如果左边没有特质了那么super就会执行父特质的方法
向文件12
向数据库12
trait02插入数据 id12
特质中的字段
特质中可以定义具体字段,(如果初始化了就是具体字段,不初始化就是抽象字段),混入该特质的类就具有了该字段
,字段不是继承,而是直接加入类,成为自己的字段
特质中的抽象字段,必须在类中重写
特质的构造顺序
第一种特质构造顺序(声明类的同时混入特质)
1)调用当前类的超类构造器
2)第一个特质的父特质的构造器
3)第一个特质的构造器
4)第二个特质的父特质的构造器
5)第二个特质的构造器
6)重复 4、5
7)当前类的构造器
第二种特质构造顺序(构建对象时动态混入特质)
1)调用当前类的超类构造器
2)当前类的构造器
3)第一个特质的父特质的构造器
4)第一个特质的构造器
5)第二个特质的父特质的构造器
6)第二个特质的构造器
7)重复 5、6
第一种方式实际是构建类对象,在混入特质时,对象还没有完成创建
第二种方式实际是构造匿名子类,可以理解在混入特质时对象已经创建了
示例:
trait A{
println("A")
}
trait B extends A{
println("B")
}
trait C extends B{
println("C")
}
trait D extends B{
println("D")
}
class E{
println("E")
}
class F extends E with C with D{
println("F")
}
class K extends E{
println("K")
}
new F
println("=======================")
new K with C with D
输出结果:
E
A
B
C
D
F
=======================
E
K
A
B
C
D
特质继承类
特质可以继承类,拓展该特质的一些功能
所有混入该特质的类,会自动成为那个特质所继承的超类的子类
如果混入该特质的类,已经击沉过了另一个类A,则要求A类是特质超类的子类 否则就会出现多继承发生错误
示例
trait LogException extends Exception{
def log: Unit ={
println(getMessage)//继承了 Exception 就可以使用 Exception 中的方法
}
}
//IndexOutOfBoundsException 是 Exception 的子类 所以不会报错
class LogException2 extends IndexOutOfBoundsException with LogException{
}
class B{
}
//出现了多继承 执行时会报错
//class LogException3 extends B with LogException{}
自身类型
自身类型主要是为了解决特质的循环依赖问题,
同时可以确保特质在不扩展某个类的情况下依然可以做到限制混入该特质的类的类型
示例:
//LogException 就是自身类型特质,当这里做了自身类型后,
//相当于 trait LogException extends Exception ,只不过 自身类型定义后比这种写法更严格,要求使用该特质的类
//必须是Exception 的子类
trait LogException{
//明确告诉编译器我就是Exception 有了这一行 就可以直接调用 Exeption的内容,
this:Exception=>
def log: Unit ={
println(getMessage)
}
}
//class Log extends LogException{} //错误 因为 Log 不是 Exception的子类 所以不能使用该特质
class Log extends Exception with LogException{}//正确
内部类
内部类定义创建及访问外部类
示例如下:
class OutClass{
//为外部类起别名可以看做外部类的一个实例
myout=>
var name ="外部类"
private var sal =30000
class InClass{//成员内部类
def info: Unit ={
//内部类访问外部类的属性
//访问方式1:外部类.this.属性名
//外部类.this相当于一个外部类的实例,然后通过OutClass.this.name 去访问属性
println("name ="+OutClass.this.name +" sal="+OutClass.sal)//name =外部类 sal=30000
//访问方式2:通过外部类别名访问(推荐) 访问方式 外部类别名.属性
println("name ="+myout.name +" sal="+myout.sal)//name =外部类 sal=30000
}
}
}
object OutClass{
var name ="外部类"
private var sal =30000
class StaticInClass{//静态内部类
}
}
def test() {
//scala创建内部类和java 不同,将 new 关键字放置在前,使用对象.内部类的方式创建
val out1:OutClass = new OutClass
val inner1 = new out1.InClass();
inner1.info
//创建静态内部类
//val staticInner=new class08.OutClass.StaticInClass
}