Scala(六)-①-面相对象高级-特质(下)-嵌套类-隐式转换和隐式参数-隐式类

① 特质(下)

在上一篇博文中,我已经介绍了Scala中静态属性和方法之伴生对象实现,以及特质入门的一部分内容.该篇博文我将会介绍特质(下)、嵌套类、隐式(上).对于特质(下).我主要介绍以下主题, 叠加特质自身类型嵌套类

Why

叠加特质

为什么我们要学习叠加特质,这是因为Scala中并无多重继承,因为多重继承会带来很多问题, 为了能够实现多重继承又能规避其问题, Scala使得特质能够叠加,通俗说是让一个类能够具备多个特质.

自身类型(强制规定能够混入特质的类)

自身类型主要是为了解决特质的循环引入,即因为混入特质而形成的依赖回环.该技术
通过再特质中明确规定哪些类能够混入该特质来解决这个问题.

How

叠加特质

规则和语法
  • 特质执行顺序由右向左.先执行File的insert方法,File中的super表示Trait1,所以会再执行Trait1的insert方法.
// 创建一个SomeObject对象,混入Trait1和Trait2特质
val obj = new SomeObject with Trait1 with Trait2
叠加特质Demo

代码

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

    val mysql = new MySQL with DB with File
    mysql.insert(100)
  }
}

class MySQL {}


trait Operator {

  def insert(id :Int)
}

trait Data extends Operator {
  override def insert(id: Int): Unit = {
    println("插入数据")
  }
}

trait DB extends Data {
  override def insert(id: Int): Unit = {
    println("向数据库插入数据")
    super.insert(id)
  }
}

trait File extends Data {
  override def insert(id: Int): Unit = {
    println("向文件中插入数据")
    super.insert(id)
  }
}

输出

向文件中插入数据
向数据库插入数据
插入数据
  • 特质构造由左到右,由父到子.由左到右构造特质,并且先构造父特质再构造子特质,如果父特质之前构造过则不再构造
    顺序
object TraitDemo05MultiplyMixin02 {
  def main(args: Array[String]): Unit = {

    val mysql = new MySQL05 with DB05 with File05
  }
}

class MySQL05 {}


trait Operator05 {
  println("Operator")
}

trait Data05 extends Operator05 {
  println("Data")
}

trait DB05 extends Data05 {
  println("DB")
}

trait File05 extends Data05 {
  println("File")
}

输出

Operator
Data
DB
File

自身类型

代码

object TraitDemo09SelfTypeForCycleRefenrece {

  def main(args: Array[String]): Unit = {
//      val oracle = new Oracle09 with Logger // 错误
    var mysql = new MySQL09 with Logger
  }

}

trait Logger {
  // 声明混入该特质的类必须是Exception或者其子类
  this:Exception=>
  def log(): Unit = {
    getMessage
  }
}

class Oracle09 {}

class MySQL09 extends Exception {

}

What

叠加特质

  • 对象中混入特质原理见上篇.构建顺序和调用顺序原理就不再做细究.

Details

叠加特质

  • 特质声明顺序左到右,构建顺序也是.
  • Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
  • Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
  • 如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
  • 富接口的概念: 具有普通方法抽象方法的特质的称呼
  • 特质中如果有抽象成员,无论字段或方法都需要混入的类来实现.
object TraitDemo06RichInterface {

  def main(args: Array[String]): Unit = {
    val mySQL = new MySQL06 with DBDriver06 {
      // 实现抽象字段
      override var numberOfThread: Int = _

      // 实现抽象方法
      override def delete: Unit = {

      }
    }

  }
}

class MySQL06 { }

trait DBDriver06 {

  var numberOfThread : Int
  private var operatorType = "insert"

  def insert(): Unit = {

  }

  def delete
}
  • 特质构造顺序之声明时混入.声明时混入先构造子类,再从左到右够着特质(并且每构造特质之时先构造父特质),最后构造本类.

特质构造分两种,一种声明时混入,一种动态混入(new的时候混入).两种唯一的区别是,动态混入先构造本类,而声明时混入最后才构造本类,其它都一样.

代码

object TraitDemo07DecalreMixin {
  def main(args: Array[String]): Unit = {
    println("声明时混入")
    
    val ff = new FF

    println("动态混入")
    // 动态混入
    val ee = new EE with CC with DD


  }
}

trait AA {
  println("A...")
}
trait BB extends  AA {
  println("B....")
}
trait CC extends  BB {
  println("C....")
}
trait DD extends  BB {
  println("D....")
}

class EE {
  println("E...")
}

// 声明时混入ee、cc、dd
class FF extends EE with CC with DD {
  println("F....")
}

// 声明时混入ee
class KK extends EE {
  println("K....")
}

输出

声明时混入
E...
A...
B....
C....
D....
F....
动态混入
E...
A...
B....
C....
D....
  • 扩展特质: 特质能够继承类,达到扩展特质的功能.混入扩展特质的类要和其有相同的父类,不然会导致多重继承的错误.
object TraitDemo08ExtendTrait {
  def main(args: Array[String]): Unit = {
    val log = new Log4Scala

  }
}


trait Log extends Exception {

  def log: Unit = {
    getMessage
  }
}
// 如果去掉LogModule继承Exception,则运行时会报错,提示多重继承
class LogModule extends Exception{

}

class Log4Scala extends LogModule with Log {

}

② 嵌套类

Java中内部类

嵌套类对应Java的内部类,所以我们先来看看Java中的内部类.在Java中,一个类总共有五大>成员.分别是

  • 属性
  • 方法
  • 内部类
  • 构造器
  • 代码块

Java中内部类
在Java中一个类里面可以再嵌套一个类,嵌套在里面的类被称为内部类,在外面的类称为

外部类.

Why

为什么Java中有内部类,为了解决什么问题?Java中的内部类主要为了解决不同类之前无法访>问其私有成员的问题.内部类就可以访问外部的私有成员.

How
class OuterClass {
  class InnserClass { // 成员内部类,位于成员位置且不为静态
  
  }
  
  static class StaticInnerClass {  // 静态内部类, 位于成员位置,且为静态
  
  }
  
  public void test() {
       class InnerClass02 { // 局部内部类· 位于方法,有类名
       
       }
       
       new Thread() {  // 匿名内部类
           @Override
           public void run() {
               super.run();
           }
       }.start();
  }
}


Why

为什么Scala中也要有嵌套类?

Scala中也有内部类的概念, Scala中又称为嵌套类.也就是说嵌套是为支持Java中的内部类概念.

How

语法和规则
  • Scala中成员内部类写在伴生类中,静态内部类写在伴生对象中(因为只有伴生对象才能模拟静态)
  • Scala中的成员内部类从属于外部类对象.所以同一个外部类的不同实例,其对应的内部类类型不一样.
  • Scala还可以通过给外部类起别名,内部类利用别名访问外部类成员
/**
  * @author sweetcs
  */
object InnerClassDemo01ForCreate {
  def main(args: Array[String]): Unit = {

    val outer01 = new ScalaOuterClass01
    val outer02 = new ScalaOuterClass01

    val inner01 = new outer01.ScalaInnerClass01
    val inner02 = new outer02.ScalaInnerClass01
    val inner03 = new ScalaOuterClass01.ScalaStaticInnerClass01

    inner01.showInfo(inner01)
    inner01.showInfoWithAlia()
//    inner01.showInfo(inner02) // 类型不匹配

  }
}

/**
  * Scala的成员内部类创建方式 `new 外部类实例.内部类`.Scala的成员内部类类型和外部类实例关联(Scala内部类是从属于外部类对象的).这两点都和Java的成员内部类有区别
  */

object ScalaOuterClass01 {

  // 静态内部类
  class ScalaStaticInnerClass01 {

    def showInfo(): Unit = {

      println("ScalaStaticInnerClass01")
    }
  }

}

class ScalaOuterClass01 {
  // 给外部类起别名为outer
  outer=>
  var name = "scott"
  private var age = 11
  // 成员内部类
  class ScalaInnerClass01 {

    def showInfo(inner: ScalaInnerClass01): Unit = {
      println(s"name=${ScalaOuterClass01.this.name} age = ${ScalaOuterClass01.this.age}")
    }

    def showInfoWithAlia(): Unit = {
      println(s"name=${outer.name} age = ${outer.age}")
    }
  }
}    
  • Scala中当要屏蔽外部对象,用外部类#内部类来屏蔽
    //下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽 外部对象对内部类对象的影响
    def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = {
      System.out.println("使用了类型投影" + ic)
    }

③ 隐式转换

① Why

为什么Scala中需要隐式转换和隐式参数
  • 其一.隐式转换自动将高精度类型自动转换为低精度类型.我们在程序中,经常会遇到类试将 高精度类型转换为低精度类型, 这时候就得用强制类型转换.但是如果程序中很多地方都要用到,就会导致到处都充斥着强制类型转换代码.Scala为了解决 高精度类型自动转换为低精度类型于是引入了隐式转换技术,该技术主要依托于隐式函数

  • 其二.隐式转换动态增加类库功能.在程序开发中我们需要不断迭代软件,给程序加入新的功能.如果加入新的功能就会需要改变源代码, 比如需要改变某一个类中的代码,这违背了OPC原则(open close prionceple,修改代码被关闭,增加功能被开发).

  • 隐式参数主要是为了让多个函数中的参数能够同时具备同一个默认值.

  • 无论一、二两点都是依托于隐式调用做的自动类型转换.

② How

语法和规则
  • 隐式函数需要用implicit声明
implicit def functionName(形参名:转换类型) : 目标类型 = { 
    // 转换逻辑
}
  • 隐式参数需要配合隐式变量,其语法为
implicit name :类型 = xxx
implicit def functionName(implicit 形参名:转换类型) : 目标类型 = { 
    // 转换逻辑
}
隐式转换emo
  • 高精度自动转低精度
/**
  * @author sweetcs
  */
object ImplicitDemo01 {

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


    implicit def f1(num:Double) :Int = { // 底层生成了f1$1
      num.toInt
    }

    val num : Int = 3.5 // 底层编译f1$1(3.5)
    println(s"num=${num}")
  }
}
  • 隐式转换动态增加类库功能.隐式转换技术还可以用来为类添加方法,实现动态增加一个类的功能.(不同对象之间的自动转换,调用对象自动转被调用对象)
/**
  * @author sweetcs
  */
object ImplicitDemo02 {
  def main(args: Array[String]): Unit = {

    implicit def addDelete(mysql :MySQL) : DB = {
      new DB
    }

    val mysql = new MySQL
    mysql.insert()
    mysql.delete()
  }
}

class MySQL {

  def insert(): Unit = {
    println("Mysql inserting")
  }
}

class DB {

  def delete(): Unit = {
    println("DB deleting")
  }
}
隐式参数DEMO
/**
  * @author sweetcs
  */
object ImplicitDemo03ForImplicitParamter {
  def main(args: Array[String]): Unit = {

      implicit var city :String = "beijing"

      implicit def queryDetailInfo(implicit city :String): Unit = {
        println(city)
      }
      queryDetailInfo
  }
}

③ What

隐式函数底层实现

如口类-ImplicitDemo01

public final class ImplicitDemo01
{
  public static void main(String[] paramArrayOfString)
  {
    ImplicitDemo01$.MODULE$.main(paramArrayOfString);
  }
}

实际入口类-ImplicitDemo01$

public final class ImplicitDemo01$
{
  public static final  MODULE$;
  
  private final int f1$1(double num)
  {
    return (int)num;
  }
  
  public void main(String[] args)
  {
    int num = f1$1(3.5D);
    Predef..MODULE$.println(new StringContext(Predef..MODULE$.wrapRefArray((Object[])new String[] { "num=", "" })).s(Predef..MODULE$.genericWrapArray(new Object[] { BoxesRunTime.boxToInteger(num) })));
  }
  
  private ImplicitDemo01$()
  {
    MODULE$ = this;
  }
  
  static
  {
    new ();
  }

④ Details

  • 隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型)有关。
  • 隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别
implicit def a(d: Double) = d.toInt
implicit def b(d: Double) = d.toInt
val i: Int = 3.5 // 编译期会不知道要调用哪个,因为具有二义性
  • 当一个隐式参数匹配不到隐式值(匹配原则就是按类型匹配),才会使用默认值
  • 优先级式 传值>大于隐式值>默认值.

④ 隐式类

Why

隐式类

How

  • 其所带的构造参数有且只有一个
  • 隐式类必须被定义在或者伴生对象包对象中.即隐式类不能是顶级.
  • 隐式类不能是case class
  • 作用域内不能有与之相同名称的标识符

Demo

  • 隐式类
/**
  * @author sweetcs
  */
object ImplicitDemo04ForImplicitClass {

  def main(args: Array[String]): Unit = {
    // 在隐式类的作用域内创建MySQL04类的对象,该隐式类自动转换就会生效
    implicit class DB04(mysql :MySQL04) {
        def insert(): Unit = {
          println("插入数据")
        }
    }

    val mysql = new MySQL04
    mysql.getConnection()
    
    mysql.insert()  // 返回了一个ImplicitDemo04ForImplicitClass$DB04$2实例,并调用其insert方法

  }
}

class MySQL04 {

  def getConnection(): Unit = {
    println("获取数据库连接")
  }

}

What

隐式类底层是如何转换成的?
隐式类-程序入口类-ImplicitDemo04ForImplicitClass

public final class ImplicitDemo04ForImplicitClass
{
  public static void main(String[] paramArrayOfString)
  {
    ImplicitDemo04ForImplicitClass$.MODULE$.main(paramArrayOfString);
  }
}

ImplicitDemo04ForImplicitClass$类


public final class ImplicitDemo04ForImplicitClass$
{
  public static final  MODULE$;
  
  private final ImplicitDemo04ForImplicitClass$DB04$2 DB04$1(MySQL04 mysql)
  {
    return new ImplicitDemo04ForImplicitClass$DB04$2(mysql);
  }
  
  public void main(String[] args)
  {
    MySQL04 mysql = new MySQL04();
    mysql.getConnection();
    DB04$1(mysql).insert();
  }
  
  private ImplicitDemo04ForImplicitClass$()
  {
    MODULE$ = this;
  }
  
  static
  {
    new ();
  }
}

ImplicitDemo04ForImplicitClassDB042

public class ImplicitDemo04ForImplicitClass$DB04$2
{
  public void insert()
  {
    Predef..MODULE$.println("插入数据");
  }
  
  public ImplicitDemo04ForImplicitClass$DB04$2(MySQL04 mysql) {}
}

Details

  • 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的
  • 作用域内不能有与之相同名称的标示符

隐式转换时机总结

  • 当方法中的参数类型和目标参宿好类型不一致时会触发
  • 当对象调用的方法或者成员变量不存在时,会触发
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355

推荐阅读更多精彩内容