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 ();
}
}
ImplicitDemo04ForImplicitClass2
public class ImplicitDemo04ForImplicitClass$DB04$2
{
public void insert()
{
Predef..MODULE$.println("插入数据");
}
public ImplicitDemo04ForImplicitClass$DB04$2(MySQL04 mysql) {}
}
Details
- 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的
- 作用域内不能有与之相同名称的标示符
隐式转换时机总结
- 当方法中的参数类型和目标参宿好类型不一致时会触发
- 当对象调用的方法或者成员变量不存在时,会触发