第八章 面向对象编程-高级
8.1 静态属性和静态方法
8.1.1 静态属性-提出问题
8.1.2 基本介绍
8.1.3 伴生对象的快速入门
示例代码如下:
package com.atguigu.chapter08.test
object AccompanyObjectDemo {
def main(args: Array[String]): Unit = {
println(ScalaPerson.sex) // 在底层等价于 ScalaPerson$.MPDULE$.sex()
ScalaPerson.sayHi() // 在底层等价于 ScalaPerson$.MPDULE$.sayHi()
}
}
// class ScalaPerson 是伴生类,将非静态的内容写到该类中
// class ScalaPerson 编译后生成 ScalaPerson.class
class ScalaPerson {
var name: String = _
}
// object ScalaPerson 是伴生对象,将静态的内容写到该对象中
// object ScalaPerson 编译后生成 ScalaPerson$.class
// 对于伴生对象的内容,我们可以直接通过 ScalaPerson.属性 或者 ScalaPerson.方法
object ScalaPerson {
var sex: Boolean = true
def sayHi():Unit = {
println("object ScalaPerson sayHi")
}
}
输出结果如下:
true
object ScalaPerson sayHi
伴生对象的快速入门源码分析图解
8.1.4 伴生对象的小结
8.1.5 最佳实践-使用伴生对象解决小孩游戏问题
设计一个 var total Int 表示总人数,我们在创建一个小孩时,就把 total 加1,并且 total 是所有对象共享的就 ok 了。我们使用伴生对象来解决。
示例代码如下:
package com.atguigu.chapter08.test
object ChildGameTest {
def main(args: Array[String]): Unit = {
// 传统的方式
// 创建很多小孩,加入游戏
// 定义一个变量 total
// var total = 0
// 我们这个 total 没有在一个对象里面,不是面向对象的编程
// 伴生对象的方式
val child01 = new Child("白骨精")
val child02 = new Child("银角大王")
val child03 = new Child("牛魔王")
Child.joinGame(child01)
Child.joinGame(child02)
Child.joinGame(child03)
Child.showNum()
}
}
// 伴生类
class Child(cName: String) {
var name = cName
}
// 伴生对象
object Child {
// 总人数
var total: Int = 0
def joinGame(child: Child): Unit = {
printf("%s 小朋友加入游戏\n", child.name)
total += 1
}
def showNum(): Unit = {
printf("当前有 %d 个小朋友在玩游戏\n", total)
}
}
输出结果如下:
白骨精 小朋友加入游戏
银角大王 小朋友加入游戏
牛魔王 小朋友加入游戏
当前有 3 个小朋友在玩游戏
8.1.6 伴生对象-apply 方法
在伴生对象中定义 apply 方法,可以实现: 类名(参数)
方式 来创建对象实例。
示例代码如下:
package com.atguigu.chapter08.apply
object ApplayDemo01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3)
println(list) // List(1, 2, 3)
// 传统的方式:new 出来
val p1 = new Pig("小花猪")
println("p1.name=" + p1.name)
// 现在的方式
// 在伴生对象中自定义 apply 方法,可以实现: 类名(参数) 方式 来创建对象实例。
// 使用 apply 方法来创建对象
val p2 = Pig("小黑猪") // 自动触发 apply(pName: String)
val p3 = Pig() // 自动触发 apply()
println("p2.name=" + p2.name)
println("p3.name=" + p3.name)
}
}
// 伴生类
class Pig(pName: String) {
var name: String = pName
}
// 伴生对象
object Pig {
// 编写自定义的 apply 方法
def apply(pName: String): Pig = new Pig(pName)
def apply(): Pig = new Pig("默认同名猪猪")
}
输出结果如下:
List(1, 2, 3)
p1.name=小花猪
p2.name=小黑猪
p3.name=默认同名猪猪
8.2 单列对象
这个部分我们放在【第十七章 scala 设计模式】专题进行讲解。
8.3 接口
8.3.1 回顾 Java 接口
8.3.2 Scala 接口的介绍
trait 原理示意图如下:
8.3.3 trait 的声明
示例代码如下:
package com.atguigu.chapter08.mytrait
object TraitDemo01 {
def main(args: Array[String]): Unit = {
}
}
// 在 scala 中,java 中的所有接口可以当做特质使用。
// Serializable:就是 scala 的一个特质。
// trait Serializable extends Any with java.io.Serializable
object T1 extends Serializable {
}
// Cloneable:就是 scala 的一个特质。
// trait Cloneable extends java.lang.Cloneable
object T2 extends Cloneable {
}
8.3.4 Scala 中 trait 的使用
8.4 特质 trait
8.4.1 特质的快速入门案例分析
8.4.2 代码完成
示例代码如下:
package com.atguigu.chapter08.mytrait
object TraitDemo02 {
def main(args: Array[String]): Unit = {
val c1 = new C()
val e1 = new E()
c1.getConnect()
e1.getConnect()
println("----------")
c1.getConnect("张三", "123456")
e1.getConnect("李四", "000000")
}
}
// 按照要求定义一个 trait
trait trait02 {
// 定义一个规范
// 声明方法,抽象的
def getConnect()
def getConnect(user: String, pwd: String): Unit
}
class A {}
class B extends A {}
class C extends A with trait02 {
override def getConnect(): Unit = {
println("c 连接mysql")
}
override def getConnect(user: String, pwd: String): Unit = {
println(user +" 用户连接mysql")
}
}
class D {}
class E extends D with trait02 {
override def getConnect(): Unit = {
println("e 连接oracle")
}
def getConnect(user: String, pwd: String): Unit = {
println(user +" 用户连接oracle")
}
}
class F extends D {}
输出结果如下:
c 连接mysql
e 连接oracle
----------
张三 用户连接mysql
李四 用户连接oracle
8.4.3 特质 trait 的再说明
1、Scala 提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。
2、特质中没有实现的方法就是抽象方法。类通过 extends 继承特质,通过 with 可以继承多个特质。
3、所有的 java 接口都可以当做 Scala 特质使用。
示例代码如下:
package com.atguigu.chapter08.mytrait
object TraitDemo03 {
def main(args: Array[String]): Unit = {
val s1 = new Sheep
s1.sayHi()
s1.sayHello()
}
}
// 当一个 trait 有抽象方法和非抽象方法时:
// 那么一个 trait 在底层就对应生成两个东东:
// 1、Trait03.class 接口 Trait03
// 2、Trait03$class.class 抽象类 Trait03$class
trait Trait03 {
// 定义规范
// 抽象方法
def sayHi()
// 实现普通方法
def sayHello(): Unit = {
println("say hello")
}
}
// 当一个 trait 有 接口 和 抽象类 时:
// class Sheep extends Trait03 在底层对应:
// 1、class Sheep implements Trait03
// 2、当在 Sheep 类中要使用 Trait03 中已实现的方法,就通过 Trait03$class.sayHello()
class Sheep extends Trait03 {
override def sayHi(): Unit = {
println("say hi")
}
}
输出结果如下:
say hi
say hello
示例代码的底层源码分析图解:
演示一个类继承多个特质的语法:
8.4.4 带有具体实现的特质
说明:和 Java 中的接口不太一样的是特质中的方法并不一定是抽象的,也可以有非抽象方法(即:实现了的方法)。实现了的方法的术语: 默认实现。提示:在 jdk1.8 中接口也可以有默认实现,就是 scala 的 trait 的带来的特性。
示例代码如下:
package com.atguigu.chapter08.mytrait
/**
* 带有具体实现的特质
* 说明:和 Java 中的接口不太一样的是特质中的方法并不一定是抽象的,也可以有非抽象方法(即:实现了的方法)。实现了的方法的术语: 默认实现。
* 提示:在 jdk1.8 中接口也可以有默认实现,就是 scala 的 trait 的带来的特性。
*/
object TraitDemo04 {
def main(args: Array[String]): Unit = {
val mysql = new MySQL0
mysql.insert(2)
}
}
trait Operate0 {
def insert(id: Int): Unit = {
println("保存数据=" + id)
}
}
trait DB0 extends Operate0 {
override def insert(id: Int): Unit = {
print("向数据库中")
super.insert(id)
}
}
class MySQL0 extends DB0 {
}
输出结果如下:
向数据库中保存数据=2
8.4.5 带有特质的对象:动态混入(mixin)
示例代码如下:
package com.atguigu.chapter08.mixin
/**
* 1、除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能。
* 2、此种方式也可以应用于对抽象类功能进行扩展。
* 3、动态混入是 Scala 特有的方式(java 没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。
*/
object MixinDemo01 {
def main(args: Array[String]): Unit = {
// 在不修改类的定义的基础上,让该类可以使用 trait 中的方法
val oracle = new OracleDB1 with Operate1 // 动态混入
oracle.insert(999)
val mysql = new MySQL1 with Operate1
mysql.insert(400)
// 如果抽象类中有抽象的方法,如何动态混入特质?如下:
val mysql11 = new MySQL11 with Operate1 {
override def say(): Unit = {
println("say")
}
}
mysql11.insert(100)
mysql11.say()
}
}
trait Operate1 { // 特质
def insert(id: Int): Unit = { // 特质中的方法默认实现了
println("插入数据=" + id)
}
}
class OracleDB1 {
}
abstract class MySQL1 {
}
abstract class MySQL11 {
def say()
}
输出结果如下:
插入数据=999
插入数据=400
插入数据=100
say
练习:在 Scala 中创建对象共有几种方式/形式?
1、new 对象
2、apply 创建
3、匿名子类方式
4、动态混入方式
8.4.6 叠加特质
示例代码如下:
package com.atguigu.chapter08.mixin
/**
* 构建对象的同时如果混入多个特质,称之为叠加特质,那么特质的声明顺序从左到右,特质中方法的执行顺序从右到左。
* 分析叠加特质时,对象的构建顺序,和执行方法的顺序。
*/
object AddTraitsDemo02 {
def main(args: Array[String]): Unit = {
// 1、在创建 MySQL2 实例时,动态的混入 DB2 和 File2
// Operate2
// Data2
// DB2
// File2
// 向文件 向数据库 插入数据=888
val mysql1 = new MySQL2 with DB2 with File2
println(mysql1)
mysql1.insert(888)
println("----------")
// 2、在创建 MySQL2 实例时,动态的混入 File2 和 DB2
// Operate2
// Data2
// File2
// DB2
// 向数据库 向文件 插入数据=999
val mysql2 = new MySQL2 with File2 with DB2
println(mysql2)
mysql2.insert(999)
}
}
trait Operate2 { // 特质
println("Operate2")
def insert(id: Int) // 抽象方法
}
trait Data2 extends Operate2 { // 特质,继承了特质 Operate2
println("Data2")
override def insert(id: Int): Unit = { // 实现或者重写 Operate2 的 insert
println("插入数据=" + id)
}
}
trait DB2 extends Data2 { // 特质,继承了特质 Data2
println("DB2")
override def insert(id: Int): Unit = { // 重写 Data2 的 insert
print("向数据库 ")
super.insert(id)
}
}
trait File2 extends Data2 { // 特质,继承了特质 Data2
println("File2")
override def insert(id: Int): Unit = { // 重写 Data2 的 insert
print("向文件 ")
super.insert(id)
// 如果想要调用具体特质的方法,可以指定:super[特质].xxx(…) 其中的泛型必须是该特质的【直接超类类型】。
// super[Data2].insert(id) // 正确
// super[Operate2].insert(id) // 报错
}
}
class MySQL2 {} // 普通类
输出结果如下:
Operate2
Data2
DB2
File2
com.atguigu.chapter08.mixin.AddTraitsDemo02$$anon$1@23223dd8
向文件 向数据库 插入数据=888
----------
Operate2
Data2
File2
DB2
com.atguigu.chapter08.mixin.AddTraitsDemo02$$anon$2@7dc36524
向数据库 向文件 插入数据=999
叠加特质注意事项和细节
8.4.7 在特质中重写抽象方法特例
解决问题方式
示例代码如下:
package com.atguigu.chapter08.mixin
object MixinSpecialCaseDemo03 {
def main(args: Array[String]): Unit = {
// val mysql2 = new MySQL3 with DB3 // ok
// mysql2.insert(100)
// val mysql3 = new MySQL3 with File3 // error,编译报错
// mysql2.insert(100)
// val mysql4 = new MySQL3 with File3 with DB3 // error,运行报错
// mysql4.insert(100)
val mysql5 = new MySQL3 with DB3 with File3 // ok
mysql5.insert(100)
}
}
trait Operate3 {
def insert(id: Int)
}
// 错误代码
/*
trait File3 extends Operate3 {
def insert(id: Int): Unit = {
println("将数据保存到文件中")
super.insert(id) // 有问题
}
}
*/
// 解决错误如下:
// 如果我们在子特质中重写/实现了一个父类的抽象方法,但是同时又调用,因为我们没有完全的实现 insert,所以不能使用该方法。
// 可以这里理解,当我们给某个方法增加了abstract override 后,就是明确的告诉编译器,该方法确实是重写了父特质的抽象方法,
// 但是重写后,该方法仍然是一个抽象方法(因为没有完全的实现,需要其它特质继续实现[即和混入顺序有关了])。
trait File3 extends Operate3 {
abstract override def insert(id: Int): Unit = {
println("将数据保存到文件中")
super.insert(id)
}
}
trait DB3 extends Operate3 { // DB3 继承 Operate3,并实现了 Operate3 的 insert
def insert(id: Int): Unit = {
println("将数据保存到数据库中")
}
}
class MySQL3 {}
输出结果如下:
将数据保存到文件中
将数据保存到数据库中
在特质中重写抽象方法理解 abstract override 的小技巧分享:
8.4.8 当作富接口使用的特质
8.4.9 特质中的具体字段
示例代码如下:
package com.atguigu.chapter08.mixin
object MixinFieldDemo04 {
def main(args: Array[String]): Unit = {
var mysql = new MySQL4 with DB4 {
override var i = 0
}
// 通过反编译,可以看到 s
println(mysql.s) // insert
println(mysql.i) // 0
}
}
trait Operate4 {
var i: Int // 没有初始化,是抽象字段,也可以在匿名子类中初始化,如上
var s: String // 没有初始化,是抽象字段,我们可以在子特质中进行初始化,如下
def insert()
}
trait DB4 extends Operate4 {
var s: String = "insert" // 初始化
def insert(): Unit = {
}
}
class MySQL4 {}
图解如下:
8.4.10 特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写。
8.4.11 特质的构造顺序
第 1 种特质的构造顺序(声明类的同时混入特质)
第 2 种特质的构造顺序(在构建对象时,动态混入特质)
分析两种方式对构造顺序的影响
示例代码如下:
package com.atguigu.chapter08.mixin
object MixinSequenceDemo05 {
def main(args: Array[String]): Unit = {
val ff1 = new FF()
println(ff1)
// 1、调用当前类的超类构造器 E...
// 2、第一个特质构造器的父特质构造器 A...
// 3、第一个特质构造器 B...
// C...
// 4、第二个特质构造器的父特质构造器,如果已经执行过,就不再执行
// 5、第二个特质构造器 D...
// 6、.......重复4,5的步骤(如果有第3个,第4个特质的话)
// 7、当前类构造器 F...
// com.atguigu.chapter08.mixin.FF@23223dd8
println("----------")
// 在构建对象时,动态混入特质
val ff2 = new KK() with CC with DD
println(ff2)
// E...
// K...
// A...
// B...
// C...
// D...
// com.atguigu.chapter08.mixin.MixinSequenceDemo05$$anon$1@4ec6a292
}
}
trait AA {
println("A...")
}
trait BB extends AA {
println("B...")
}
trait CC extends BB {
println("C...")
}
trait DD extends BB {
println("D...")
}
class EE { // 普通类 EE
println("E...")
}
class FF extends EE with CC with DD { // 声明类 FF 的同时混入特质 CC 和 特质 DD
println("F...")
}
class KK extends EE { // KK 直接继承了 普通类 EE
println("K...")
}
输出结果如下:
E...
A...
B...
C...
D...
F...
com.atguigu.chapter08.mixin.FF@23223dd8
----------
E...
K...
A...
B...
C...
D...
com.atguigu.chapter08.mixin.MixinSequenceDemo05$$anon$1@4ec6a292
8.4.12 扩展类的特质
1、特质可以继承类,以用来拓展该特质(类)的一些功能。
2、所有混入该特质的类,会自动成为那个特质所继承的超类的子类。
3、如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误。
示例代码如下:
package com.atguigu.chapter08.extendtrait
object ExtendTraitDemo01 {
def main(args: Array[String]): Unit = {
println()
}
}
// 1、特质可以继承类,以用来拓展该特质(类)的一些功能。
trait LoggedException extends Exception {
def log(): Unit = {
println(getMessage()) // 方法来自于 Exception 类
}
}
// 2、所有混入该特质的类,会自动成为那个特质所继承的超类的子类。
// UnhappyException 就是 Exception 的子类。
class UnhappyException extends LoggedException {
// 已经是 Exception 的子类了,所以可以重写方法。
override def getMessage = "错误消息!"
}
// 3、如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误。
class UnhappyException2 extends IndexOutOfBoundsException with LoggedException {
// 已经是 Exception 的子类了,所以可以重写方法。
override def getMessage = "错误消息!"
}
class CCC {}
// 错误,因为 CCC 类不是特质超类 Exception 的子类
class UnhappyException3 extends CCC with LoggedException {
}
8.4.13 自身类型
说明
自身类型:主要是为了解决特质的循环依赖问题
,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
应用案例
举例说明自身类型特质,以及如何使用自身类型特质。
示例代码如下:
package com.atguigu.chapter08.extendtrait
object SelfTypeDemo02 {
def main(args: Array[String]): Unit = {
}
}
// trait Logger 就是【自身类型特质】等价于 trait Logger extends Exception
trait Logger {
// 明确告诉编译器,我就是 Exception,如果没有这句话,下面的 getMessage 不能调用
this: Exception => // 自身类型说明
def log(): Unit = {
// 既然我就是 Exception,那么就可以调用其中的方法
println(getMessage)
}
}
// class Console extends Logger {} // 对吗? 错误
class Console extends Exception with Logger {} // 对吗? 要求混入该特质的类 Console 也是 Exception 的子类
8.5 嵌套类
基本介绍
Java 内部类的简单回顾
Java 内部类的分类
Java 内部类回顾案例
示例代码如下:
package com.atguigu.chapter08.innerclass;
public class JavaInnerClassDemo {
public static void main(String[] args) {
// 创建一个外部类对象
OuterClass outer1 = new OuterClass();
// 创建一个外部类对象
OuterClass outer2 = new OuterClass();
// 创建 Java 成员内部类
// 说明:在 Java 中,将成员内部类当做一个属性来对待的,因此使用下面的方式来创建 outer1.new InnerClass()
OuterClass.InnerClass inner1 = outer1.new InnerClass();
OuterClass.InnerClass inner2 = outer2.new InnerClass();
// 下面的方法调用说明:在 java 中,内部类只和类型相关。
// 也就是说,只要是 OuterClass.InnerClass 类型的对象就可以传给 test 方法的形参 InnerClass ic
inner1.test(inner2);
inner2.test(inner1);
// 创建 Java 静态内部类
// 因为在 java 中静态内部类是和类相关的,使用 new OuterClass.StaticInnerClass()
OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();
}
}
// 内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
class OuterClass { // 外部类
class InnerClass { // 成员内部类
public void test(InnerClass ic) {
System.out.println(ic);
}
}
static class StaticInnerClass { // 静态内部类
}
}
输出结果如下:
com.atguigu.chapter08.innerclass.OuterClass$InnerClass@12a3a380
com.atguigu.chapter08.innerclass.OuterClass$InnerClass@29453f44
8.5.1 Scala 嵌套类的使用1
请编写程序,定义 Scala 的成员内部类和静态内部类,并创建相应的对象实例。
示例代码如下:
package com.atguigu.chapter08.innerclass
object ScalaInnerClassDemo01 {
def main(args: Array[String]): Unit = {
// 创建外部类实例
val outer1: ScalaOuterClass = new ScalaOuterClass();
val outer2: ScalaOuterClass = new ScalaOuterClass();
// Scala 创建成员内部类对象的方式和 Java 不一样,将 new 关键字放置在前,使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
println(inner1)
println(inner2)
// 创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)
}
}
class ScalaOuterClass {
class ScalaInnerClass { // 成员内部类
}
}
// 伴生对象
object ScalaOuterClass {
class ScalaStaticInnerClass { // 静态内部类
}
}
输出结果如下:
com.atguigu.chapter08.innerclass.OuterClass$InnerClass@12a3a380
com.atguigu.chapter08.innerclass.OuterClass$InnerClass@29453f44
8.5.2 Scala 嵌套类的使用2
请编写程序,在内部类中访问外部类的属性。
方式1
内部类如果想要访问外部类的属性,可以通过外部类对象访问。即:访问方式:外部类名.this.属性名
示例代码如下:
package com.atguigu.chapter08.innerclass
object ScalaInnerClassDemo01 {
def main(args: Array[String]): Unit = {
// 创建外部类实例
val outer1: ScalaOuterClass = new ScalaOuterClass();
val outer2: ScalaOuterClass = new ScalaOuterClass();
// Scala 创建成员内部类对象的方式和 Java 不一样,将 new 关键字放置在前,使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
println(inner1)
println(inner2)
// 创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)
// 调用成员内部类的方法访问外部类的属性或私有属性
inner1.info()
}
}
class ScalaOuterClass {
// 定义两个属性
var name: String = "scott"
private val sal: Double = 30000
class ScalaInnerClass { // 成员内部类
def info() = {
// 访问方式:外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别,学习 java 的同学可能更容易理解 ScalaOuterClass.class 的写法
println("name = " + ScalaOuterClass.this.name + " sal = " + ScalaOuterClass.this.sal)
}
}
}
// 伴生对象
object ScalaOuterClass {
class ScalaStaticInnerClass { // 静态内部类
}
}
输出结果如下:
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@23223dd8
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@4ec6a292
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaStaticInnerClass@1b40d5f0
name = scott sal = 30000.0
方式2
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即:访问方式:外部类名别名.属性名 【即:外部类名.this = 外部类名别名】
示例代码如下:
package com.atguigu.chapter08.innerclass
object ScalaInnerClassDemo01 {
def main(args: Array[String]): Unit = {
// 创建外部类实例
val outer1: ScalaOuterClass = new ScalaOuterClass();
val outer2: ScalaOuterClass = new ScalaOuterClass();
// Scala 创建成员内部类对象的方式和 Java 不一样,将 new 关键字放置在前,使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
println(inner1)
println(inner2)
// 创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)
// 调用成员内部类的方法访问外部类的属性或私有属性
inner1.info()
}
}
/*
class ScalaOuterClass {
// 定义两个属性
var name: String = "scott"
private val sal: Double = 30000
class ScalaInnerClass { // 成员内部类
def info() = {
// 访问方式:外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别,学习 java 的同学可能更容易理解 ScalaOuterClass.class 的写法
println("name = " + ScalaOuterClass.this.name + " sal = " + ScalaOuterClass.this.sal)
}
}
}*/
class ScalaOuterClass {
myOuter => // 这样写,你可以理解成这样:外部类的别名 myOuter 就是代表外部类的一个对象
class ScalaInnerClass { // 成员内部类
def info() = {
println("name = " + ScalaOuterClass.this.name + " sal =" + ScalaOuterClass.this.sal)
println("-------------------------")
println("name = " + myOuter.name + " sal =" + myOuter.sal)
}
}
// 注意:当给外部指定别名时,需要将外部类的属性放到别名后。
var name : String = "scott"
private val sal : Double = 30000
}
// 伴生对象
object ScalaOuterClass {
class ScalaStaticInnerClass { // 静态内部类
}
}
输出结果如下:
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@23223dd8
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@4ec6a292
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaStaticInnerClass@1b40d5f0
name = scott sal =30000.0
-------------------------
name = scott sal =30000.0
8.5.3 类型投影
类型投影完整示例代码如下:
package com.atguigu.chapter08.innerclass
object ScalaInnerClassDemo01 {
def main(args: Array[String]): Unit = {
// 创建外部类实例
val outer1: ScalaOuterClass = new ScalaOuterClass();
val outer2: ScalaOuterClass = new ScalaOuterClass();
// Scala 创建成员内部类对象的方式和 Java 不一样,将 new 关键字放置在前,使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
println(inner1)
println(inner2)
// 创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)
// 调用成员内部类的方法访问外部类的属性或私有属性
inner1.info()
inner1.test(inner1) // ok, 需要 outer1.ScalanInner
inner2.test(inner2) // ok, 需要 outer2.ScalanInner
// 在默认情况下,scala 的内部类的实例和创建该内部类的外部对象相关联。
// 如何解决这个问题呢?答:使用类型投影屏蔽掉 外部对象对内部对象的影响!即只要是内部类我都一视同仁了。
// inner1.test(inner2) // error, 需要 outer1.ScalanInner
// inner2.test(inner1) // error, 需要 outer2.ScalanInner
// 使用 类型投影 解决
inner1.test(inner2)
inner2.test(inner1)
}
}
/*
class ScalaOuterClass {
// 定义两个属性
var name: String = "scott"
private val sal: Double = 30000
class ScalaInnerClass { // 成员内部类
def info() = {
// 访问方式:外部类名.this.属性名
// 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例
// 然后通过 ScalaOuterClass.this 实例对象去访问 name 属性
// 只是这种写法比较特别,学习 java 的同学可能更容易理解 ScalaOuterClass.class 的写法
println("name = " + ScalaOuterClass.this.name + " sal = " + ScalaOuterClass.this.sal)
}
}
}*/
class ScalaOuterClass {
myOuter => // 这样写,你可以理解成这样:外部类的别名 myOuter 就是代表外部类的一个对象
class ScalaInnerClass { // 成员内部类
def info() = {
println("name = " + ScalaOuterClass.this.name + " sal =" + ScalaOuterClass.this.sal)
println("-------------------------")
println("name = " + myOuter.name + " sal =" + myOuter.sal)
}
// 这里有一个方法:可以接收 ScalaInnerClass 的实例
// 下面的 ScalaOuterClass#ScalaInnerClass 是类型投影:作用是:屏蔽掉 外部对象对内部对象的影响!即只要是内部类我都一视同仁了。
def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = {
System.out.println(ic)
}
}
// 注意:当给外部指定别名时,需要将外部类的属性放到别名后。
var name : String = "scott"
private val sal : Double = 30000
}
// 伴生对象
object ScalaOuterClass {
class ScalaStaticInnerClass { // 静态内部类
}
}
输出结果如下:
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@23223dd8
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@4ec6a292
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaStaticInnerClass@1b40d5f0
name = scott sal =30000.0
-------------------------
name = scott sal =30000.0
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@23223dd8
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@4ec6a292
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@4ec6a292
com.atguigu.chapter08.innerclass.ScalaOuterClass$ScalaInnerClass@23223dd8
对上面代码正确和错误的分析【重点】
// 说明下面调用 test 的 正确和错误的原因:
// 1、Java 中的内部类从属于外部类,因此在 java中 inner1.test(inner2) 就可以,因为是按类型来匹配的。
// 2、Scala 中内部类从属于外部类的对象,所以外部类的对象不一样,创建出来的内部类也不一样,无法互换使用。
// 3、比如你使用 idea 看一下在 inner1.test() 的形参上,它提示的类型是 outer1.ScalaOuterClass, 而不是 ScalaOuterClass
inner1.test(inner1) // ok
inner1.test(inner2) // 错误
解决方式-使用类型投影
类型投影是指:在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类的对象关系,等同于 Java 中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)。
8.6 作业05
8.6.1 对象作业
1、编写一个 Conversions 对象,加入 inchesToCentimeters、gallonsToLiters 和 milesToKilometers 方法。
示例代码如下:
package com.atguigu.chapter08.homework.objecthomework
/**
* 1、编写一个 Conversions 对象,加入 inchesToCentimeters、gallonsToLiters 和 milesToKilometers 方法。
*/
object objecthomework01 {
def main(args: Array[String]): Unit = {
println(Conversions.inchesToCentimeters(1.5))
println(Conversions.gallonsToLiters(1.5))
println(Conversions.milesToKilometers(1.5))
}
}
object Conversions {
// 英寸到厘米:1英寸(in)=2.54厘米(cm)
def inchesToCentimeters(inches: Double): Double = {
(inches * 2.54).formatted("%.2f").toDouble
}
// 加仑到升:1美制加仑(us gal)=3.7854118升(l)
def gallonsToLiters(gallons: Double): Double = {
(gallons * 3.7854118).formatted("%.2f").toDouble
}
// 英里到公里:1英里(mi)=1.609344公里(km)
def milesToKilometers(miles: Double): Double = {
(miles * 1.609344).formatted("%.2f").toDouble
}
}
输出结果如下:
3.81
5.68
2.41
2、定义一个 Point 类和一个伴生对象,使得我们可以不用 new 而直接用 Point(3, 4) 来构造 Point 实例 apply 方法的使用。
示例代码如下:
package com.atguigu.chapter08.homework.objecthomework
/**
* 2、定义一个 Point 类和一个伴生对象,使得我们可以不用 new 而直接用 Point(3, 4) 来构造 Point 实例(apply 方法的使用)。
*/
object objecthomework02 {
def main(args: Array[String]): Unit = {
val p = Point(3, 4)
println("p.a=" + p.a)
println("p.b=" + p.b)
}
}
class Point(n: Int, m: Int) {
var a: Int = n
var b: Int = m
}
object Point {
// 编写自定义的 apply 方法
def apply(n: Int, m: Int): Point = new Point(n, m)
}
输出结果如下:
p.a=3
p.b=4
3、编写一个 Scala 应用程序,使用 App 特质,以反序打印命令行参数,用空格隔开。举例来说:scala Reverse Hello World 应该打印 World Hello。
示例代码如下:
package com.atguigu.chapter08.homework.objecthomework
/**
* 3、编写一个 Scala 应用程序,使用 App 特质,以反序打印命令行参数,用空格隔开。举例来说:scala Reverse Hello World 应该打印 World Hello。
* 说明:当我们继承了App后,就可以直接在这个类中执行代码,不需要再写 main 入口了。
*/
object objecthomework03 extends App {
var a = "Hello World".reverse
println(a) // dlroW olleH
// a.foreach(println)
var b: Array[String] = "Hello World".split(" ")
for (i <- b reverse) {
print(i + " ")
}
// 正解
val args2 = args.reverse
println(args2.mkString(" ")) // mkString() 表示以什么隔开
}
4、编写一个扑克牌 4 种花色的枚举,让其 toString 方法分别返回 ♣,♦,♥,♠,并实现一个函数,检查某张牌的花色是否为红色。
示例代码如下:
package com.atguigu.chapter08.homework.objecthomework
/**
* 4、编写一个扑克牌 4 种花色的枚举,让其 toString 方法分别返回 ♣,♦,♥,♠,并实现一个函数,检查某张牌的花色是否为红色。
*/
object objecthomework04 {
def main(args: Array[String]): Unit = {
// tppe 可以用于给类型取别名
type MyInt = Int // 给 Int 类型取别名
val num: MyInt = 666
println("num=" + num)
println(Suits) // ♠,♣,♥,♦
println(Suits.isRed01(Suits.Heart)) // true
println(Suits.isRed01(Suits.Club)) // false
}
}
object Suits extends Enumeration {
type Suits = Value // 给 Value 类型取别名
val Spade = Value("♠") // 创建了一个 Value 对象
val Club = Value("♣") // 创建了一个 Value 对象
val Heart = Value("♥") // 创建了一个 Value 对象
val Diamond = Value("♦") // 创建了一个 Value 对象
override def toString(): String = { // 重写 toString 方法
Suits.values.mkString(",") // ♣,♦,♥,♠
}
def isRed01(card: Suits) = {
if (card == Heart || card == Diamond) {
true
} else {
false
}
}
def isRed02(card: Suits) = {
card == Heart || card == Diamond
}
def isRed03(card: Suits) = card == Heart || card == Diamond
}
8.6.2 继承作业
1、扩展如下的 BankAccount 类,新类 CheckingAccount 对每次存款和取款都收取1美元的手续费。
2、扩展前一个练习的 BankAccount 类,新类 SavingsAccount 每个月都有利息产生(earnMonthlyInterest 方法被调用),并且有每月三次免手续费的存款或取款。在 earnMonthlyInterest 方法中重置交易计数。
1、2题示例代码如下:
package com.atguigu.chapter08.homework.extendshomework
/**
* 1、扩展如下的 BankAccount 类,新类 CheckingAccount 对每次存款和取款都收取1美元的手续费。
* 2、扩展前一个练习的 BankAccount 类,新类 SavingsAccount 每个月都有利息产生(earnMonthlyInterest 方法被调用),
* 并且有每月三次免手续费的存款或取款。在 earnMonthlyInterest 方法中重置交易计数。
*/
object extendshomework02 {
def main(args: Array[String]): Unit = {
val c = new CheckingAccount(100)
c.query() // 100.0
c.withdraw(10)
c.query() // 89.0 = 100.0 - 10.0 - 1.0
val s = new SavingsAccount(100)
s.query() // 100.0
s.earnMonthlyInterest() // 101.0
s.withdraw(10)
s.withdraw(10)
s.withdraw(10)
s.query() // 71.0
s.withdraw(10)
s.query() // 60.0 = 71.0 - 10.0 - 1.0
}
}
class BankAccount(initialBalance: Double) {
private var balance = initialBalance
// 存款,不收手续费
def deposit(amount: Double) = {
balance += amount;
balance
}
// 取款,不收手续费
def withdraw(amount: Double) = {
balance -= amount;
balance
}
// 查询
def query(): Unit = {
println("当前余额是:" + this.balance)
}
}
class CheckingAccount(initialBalance: Double) extends BankAccount(initialBalance) {
// 存款,收手续费
override def deposit(amount: Double): Double = super.deposit(amount - 1)
// 取款,收手续费
override def withdraw(amount: Double): Double = super.withdraw(amount + 1)
}
class SavingsAccount(initialBalance: Double) extends BankAccount(initialBalance) {
private var num: Int = _
// 存款,收手续费,但是前三次免费
def earnMonthlyInterest() = { // 每个月初重置交易计数为3,每个月都有利息产生(即 earnMonthlyInterest 方法被调用)
num = 3
super.deposit(1)
}
// 取款,收手续费,但是前三次免费
override def deposit(amount: Double): Double = { // 存款时,将交易计数减1,再判断,如果 num < 0 收取手续费,否则不收
num -= 1
if (num < 0) {
super.deposit(amount - 1) // 存款,收手续费
} else {
super.deposit(amount) // 存款,不收手续费
}
}
override def withdraw(amount: Double): Double = { // 取款时,将交易计数减1,再判断,如果 num < 0 收取手续费,否则不收
num -= 1
if (num < 0) super.withdraw(amount + 1) else super.withdraw(amount)
}
}
输出结果如下:
当前余额是:100.0
当前余额是:89.0
当前余额是:100.0
当前余额是:71.0
当前余额是:60.0
3、设计一个 Point 类,其 x 和 y 坐标可以通过构造器提供。提供一个子类 LabeledPoint,其构造器接受一个标签值和 x, y 坐标,比如:new LabeledPoint("Black Thursday", 1929, 230.07)。
4、定义一个抽象类 Shape,一个抽象方法 centerPoint,以及该抽象类的子类 Rectangle 和 Circle。为子类提供合适的构造器,并重写 centerPoint 方法。
示例代码如下:
package com.atguigu.chapter08.homework.extendshomework
/**
* 3、设计一个 Point 类,其 x 和 y 坐标可以通过构造器提供。提供一个子类 LabeledPoint,其构造器接受一个标签值和 x, y 坐标,
* 比如:new LabeledPoint("Black Thursday", 1929, 230.07)
*/
object extendshomework03 {
def main(args: Array[String]): Unit = {
}
}
class Point(x: Double, y: Double) {
}
class LabeledPoint(tag: String, x: Double, y: Double) extends Point(x, y) {
}
4、定义一个抽象类 Shape,一个抽象方法 centerPoint,以及该抽象类的子类 Rectangle 和 Circle。为子类提供合适的构造器,并重写 centerPoint 方法。
示例代码如下:
package com.atguigu.chapter08.homework.extendshomework
/**
* 4、定义一个抽象类 Shape,一个抽象方法 centerPoint,以及该抽象类的子类 Rectangle 和 Circle。为子类提供合适的构造器,并重写 centerPoint 方法。
*/
object extendshomework04 {
def main(args: Array[String]): Unit = {
}
}
abstract class Shape {
def centerPoint()
}
class Rectangle(startX: Int, startY: Int, endX: Int, endY: Int) extends Shape {
override def centerPoint(): Unit = {
}
}
class Circle(x: Int, y: Int, radius: Double) extends Shape {
override def centerPoint(): Unit = {
}
}
5、提供一个 Square 类,扩展自 java.awt.Rectangle 并且是三个构造器:一个以给定的端点和宽度构造正方形:一个以 (0,0) 为端点和给定的宽度构造正方形,一个以 (0,0) 为端点和 0 为宽度构造正方形。
示例代码如下:
package com.atguigu.chapter08.homework.extendshomework
/**
* 5、提供一个 Square 类,扩展自 java.awt.Rectangle 并且是三个构造器:一个以给定的端点和宽度构造正方形:
* 一个以 (0,0) 为端点和给定的宽度构造正方形,一个以 (0,0) 为端点和 0 为宽度构造正方形。
*/
object extendshomework05 {
def main(args: Array[String]): Unit = {
}
}
import java.awt.{Point, Rectangle}
class Square(point: Point, width: Int) extends Rectangle(point.x, point.y, width, width) {
def this() {
this(new Point(0, 0), 0)
}
def this(width: Int) {
this(new Point(0, 0), width)
}
}
第九章 隐式转换和隐式值
9.1 隐式转换
9.1.1 提出问题
先看一段代码,引出隐式转换的实际需要=>指定某些数据类型的相互转化。
示例代码如下:
package com.atguigu.scala.conversion
object Scala01 {
def main(args: Array[String]): Unit = {
// val num: Int = 3.5 // ?错 高精度->低精度
println(num)
}
}
9.1.2 隐式函数基本介绍
隐式转换函数是以 implicit 关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型。
9.1.3 隐式函数快速入门
使用隐式函数可以优雅的解决数据类型转换。
示例代码如下:
package com.atguigu.chapter09
object ImplicitDemo01 {
def main(args: Array[String]): Unit = {
// val num: Int = 3.5 // // 错 高精度->低精度
// 编写一个隐式函数完成 Double -> Int 的转换
// Double 是输入类型,Int 是转换后的类型
implicit def f1(d: Double): Int = { // 底层生成 f1$1(double d)
d.toInt
}
val num: Int = 3.5 // 底层生成 int num = f1$1(3.5D);
println("num="+num)
}
}
隐式函数的底层工作原理
反编译后的代码如下:
public final class ImplicitDemo01$
{
public static final MODULE$;
static
{
new ();
}
public void main(String[] args)
{
int num = f1$1(3.5D);
Predef..MODULE$.println(new StringBuilder().append("num=").append(BoxesRunTime.boxToInteger(num)).toString());
}
private final int f1$1(double d)
{
return (int)d;
}
private ImplicitDemo01$()
{
MODULE$ = this;
}
}
9.1.4 隐式转换的注意事项和细节
9.2 隐式转换丰富类库功能
开闭原则:Open Closed Principle, OCP)
定义:Software entities like classes, modules and functions should be open for extension but closed for modifications.
(一个软件实体,如类,模块和函数应该对扩展开放,对修改关闭。)
9.2.1 快速入门案例
使用隐式转换方式动态的给MySQL类增加 delete 方法。
9.2.2 案例代码
示例代码如下:
package com.atguigu.chapter09
object ImplicitDemo02 {
def main(args: Array[String]): Unit = {
// 编写一个隐式函数,丰富 MySQL 的功能
implicit def addDelete(mysql: MySQL): DB = { // 底层生成 addDelete$(MySQL mysql)
new DB
}
val mysql = new MySQL
mysql.insert()
mysql.delete() // addDelete$1(mysql).delete();
}
}
class MySQL {
def insert(): Unit = {
println("insert")
}
}
class DB {
def delete(): Unit = {
println("delete")
}
}
反编译后的代码如下:
public final class ImplicitDemo02$
{
public static final MODULE$;
static
{
new ();
}
public void main(String[] args)
{
MySQL mysql = new MySQL();
mysql.insert();
addDelete$1(mysql).delete();
}
private final DB addDelete$1(MySQL mysql)
{
return new DB();
}
private ImplicitDemo02$()
{
MODULE$ = this;
}
}
9.3 隐式值
9.3.1 基本介绍+快速入门案例
示例代码如下:
package com.atguigu.chapter09
/**
* 隐式值也叫隐式变量,将某个形参变量标记为 implicit,
* 所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数。
*/
object ImplicitDemo03 {
def main(args: Array[String]): Unit = {
implicit val str1: String = "jack" // 这个就是隐式值
// implicit name: String name就是隐士参数
def hello(implicit name: String): Unit = {
println(name + " hello")
}
hello // 底层 hello$1(str1); 使用隐式值不要带()
}
}
9.3.2 测试题
1、下面的代码是正确,并解释 scala 底层实现机制 和 底层的函数名
示例代码如下:
package com.atguigu.chapter09
object ImplicitValDemo04 {
def main(args: Array[String]): Unit = {
implicit val str1: String = "jack" // 隐式值
def hello(implicit name: String): Unit = { // 底层 hello$1(String name)
println(name + " hello")
def hello(): Unit = { // hello$2()
println("xxx")
}
}
hello // hello$1(str1); 使用隐式值不要带()
}
def hello(): Unit = { // 底层 hello() 函数名
println("***")
}
}
9.3.3 一个案例说明 隐式值、隐式参数默认值 的优先级
小结:
(1)在隐式值匹配时,不能有二义性。
(2)当同时有隐式值和隐式参数的默认值的时候,隐式值的优先级高。
(3)当隐式参数默认值匹配不到隐式值时,如果有隐式参数默认值的话,会使用默认值。如果没有隐式参数默认值的话,会运行报错。
9.4 隐式类
9.4.1 基本介绍
9.4.2 应用案例
示例代码如下:
package com.atguigu.chapter09
object ImplicitClassDemo06 {
def main(args: Array[String]): Unit = {
// DB1 会对应生成隐式类
// 当我们在该隐式类的作用域范围创建 MySQL1 实例时,该隐式类就会生效
implicit class DB1(val m: MySQL1) {
def addSuffix(): String = {
m + " scala"
}
}
val mysql1 = new MySQL1
mysql1.sayOk()
// mysql1.addSuffix() ==> DB1$1(mysql1).addSuffix() DB1$1(mysql1) 返回的类型是 ImplicitClass$DB1$2
println(mysql1.addSuffix())
}
}
class MySQL1 {
def sayOk(): Unit = {
println("sayOk")
}
}
输出结果如下:
sayOk
com.atguigu.chapter09.MySQL1@35bbe5e8 scala
9.5 隐式的转换时机
1、当方法中的参数的类型与目标类型不一致时,会发生隐式转换。
2、当对象调用所在类中不存在的方法或成员时,编译器会自动将对象进行隐式转换(根据类型)。
3、即:当对象调用所在类中存在的方法或成员时,编译器不会将对象进行隐式转换。(即隐式转换没有起作用)
示例代码如下:
implicit def f1(d: Double): Int = {
d.toInt
}
// 1、当方法中的参数的类型与目标类型不一致时,会发生隐式转换。
def test1(n1: Int):Unit = {
println("ok")
}
test1(10)
test1(10.1)
9.6 隐式解析机制
9.7 在进行隐式转换时,需要遵守两个基本的前提
在进行隐式转换时,需要遵守两个基本的前提:
1、不能存在二义性
2、隐式操作不能嵌套使用,如:隐式转换函数
示例代码如下:
package com.atguigu.chapter09
/**
* 在进行隐式转换时,需要遵守两个基本的前提:
* 1、不能存在二义性
* 2、隐式操作不能嵌套使用,如:隐式转换函数
*/
object ImplicitNoticeDemo07 {
def main(args: Array[String]): Unit = {
implicit def f1(d: Double): Int = {
d.toInt
// val num: Int = 7.8 // 错误,f1$(7.8) f1$ 对应的就是f1,就会形成死递归
}
val num: Int = 5.6 // ok,f1$(5.6)
}
}
9.8 作业06
1、java.awt.Rectangle 类有两个很有用的方法 translate 和 grow,但可惜的是像 java.awt.geom.Ellipse2D 这样的类没有。在 Scala 中,你可以解决掉这个问题。定义一个 RenctangleLike 特质,加入具体的 translate 和 grow 方法。提供任何你需要用来实现的抽象方法,以便你可以像如下代码这样混入该特质:
val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RenctangleLike
egg.translate(10, -10)
egg.grow(10, 20)
思路分析:
(1)考察点:使用特质(自身类型),编写方法,以动态混入的方式,来扩展一个类的功能。
(2)RenctangleLike 名字.translate(将x,y的值重置) 和 grow方法(对x,y的值进行增长)
示例代码如下:
package com.atguigu.chapter09
import java.awt.geom.Ellipse2D
/**
* 1、java.awt.Rectangle 类有两个很有用的方法 translate 和 grow,但可惜的是像 java.awt.geom.Ellipse2D 这样的类没有。
* 在 Scala 中,你可以解决掉这个问题。定义一个 RenctangleLike 特质,加入具体的 translate 和 grow 方法。
* 提供任何你需要用来实现的抽象方法,以便你可以像如下代码这样混入该特质:
*
* val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RenctangleLike
* egg.translate(10, -10)
* egg.grow(10, 20)
*
* 思路分析:
* (1)考察点:使用特质(自身类型),编写方法,以动态混入的方式,来扩展一个类的功能。
* (2)RenctangleLike 名字.translate(将x,y的值重置) 和 grow 方法(对x,y的值进行增长)
*/
object homework {
def main(args: Array[String]): Unit = {
// val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RenctangleLike
val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RenctangleLike2
println("egg.x=" + egg.x + " egg.y=" + egg.y)
egg.translate(10, -10)
println("egg.x=" + egg.x + " egg.y=" + egg.y)
egg.grow(10, 20)
println("egg.x=" + egg.x + " egg.y=" + egg.y)
}
}
// 方式一:传统写法
trait RenctangleLike extends Ellipse2D.Double {
def translate(x: Double, y: Double): Unit = {
this.x = x
this.y = y
}
def grow(x: Double, y: Double): Unit = {
this.x += x
this.y += y
}
}
// 方式二:使用自身类型
trait RenctangleLike2 {
// 明确告诉编译器,我就是 Ellipse2D.Double
this: Ellipse2D.Double =>
def translate(x: Double, y: Double): Unit = {
this.x = x
this.y = y
}
def grow(x: Double, y: Double): Unit = {
this.x += x
this.y += y
}
}
输出结果如下:
egg.x=5.0 egg.y=10.0
egg.x=10.0 egg.y=-10.0
egg.x=20.0 egg.y=10.0