① 伴生对象和伴生类
①-① Why
- Scala语言是完全面相对象的,并
不支持静态
这个概念,也就没有静态成员
(静态成员变量和静态成员方法). - 但是
java又支持静态
这个概念,有些需求需要用到静态.所以Scala就使用伴生对象
这个技术来模拟静态
,使得我们能够实现和静态一样的效果
.
①-② How
语法和规则
- 伴生对象必须和伴生类同名.
class Person { // 半生类Person
}
object Person { // 伴生对象Person.写在里面的成员可以模拟Java的static效果.
}
- 可以在
伴生对象
实现apply方法
.之后直接用类名(apply形参列表)
就能够触发apply
方法.所以一般创建对象.
object 类名 {
def apply(形参列表) : 类名 = new 类名(形参列表)
}
Demo
伴生对象模拟静态方法
package com.sweetcs.bigdata.scala.day05.chapter08._01_accompobject
/**
* @author sweetcs
*/
object ChildGameDemo {
def main(args: Array[String]): Unit = {
val child0 = new Child("lisi")
val child1 = new Child("zhangsan")
val child2 = new Child("wangwu")
Child.count(child0) // 使用类能够直接调用伴生对象中的成员
Child.count(child1)
Child.count(child2)
Child.showNumberOfChild()
}
}
class Child(inName :String) {
var name :String = inName
}
object Child {
var numberOfChild : Int = 0
def count(child: Child): Unit = {
numberOfChild += 1
}
def showNumberOfChild(): Unit = {
println(s"numberOfChild = $numberOfChild")
}
}
伴生对象里apply
object ApplyDemo {
def main(args: Array[String]): Unit = {
val pig1 = Pig()
val pig2 = Pig("pei qi")
println(pig1.name)
println(pig2.name)
}
}
class Pig(inName :String) {
var name :String = inName
def this() {
this("")
}
}
object Pig {
def apply(inName: String): Pig = new Pig(inName)
def apply(): Pig = new Pig()
}
①-③ What
伴生对象
是怎么实现让对应的伴生类
能通过类
直接调用
伴生对象
里面的成员
?
如下分析,先看源代码
和对应的反编译的代码
源代码
/**
* @author sweetcs
*/
object AccompObjectDemo {
def main(args: Array[String]): Unit = {
PersonOfAccompanyClass.name = "test"
PersonOfAccompanyClass.show()
}
}
// 半生类
class PersonOfAccompanyClass {
}
// 半生对象
object PersonOfAccompanyClass {
var name :String = ""
def show(): Unit = {
println(s"name = ${name}")
}
}
反编译的AccompObjectDemo代码
public final class AccompObjectDemo$
{
public static final MODULE$;
static
{
new ();
}
public void main(String[] args)
{
PersonOfAccompanyClass..MODULE$.name_$eq("test");
PersonOfAccompanyClass..MODULE$.show();
}
private AccompObjectDemo$()
{
MODULE$ = this;
}
}
public final class PersonOfAccompanyClass$
{
public static final MODULE$;
private String name;
public String name()
{
return this.name;
}
public void name_$eq(String x$1)
{
this.name = x$1;
}
public void show()
{
Predef..MODULE$.println(new StringContext(Predef..MODULE$.wrapRefArray((Object[])new String[] { "name = ", "" })).s(Predef..MODULE$.genericWrapArray(new Object[] { name() })));
}
private PersonOfAccompanyClass$()
{
MODULE$ = this;this.name = "";
}
static
{
new ();
}
}
从上面代码可以看出,底层是通过以下两句实现了我们写的代码.而这里的MODULE$
是核心所在
,它是一个静态实例
,类型就是PersonOfAccompanyClass$
类型.所以底层其实是创建了一个对应的类名$
的类,并将伴生对象中的成员分按访问控制权限放入, 并且仅有一个类名$
的类型的实例(因为MODULE$是静态的).
PersonOfAccompanyClass$.MODULE$.name_$eq("test");
PersonOfAccompanyClass$.MODULE$.show();
①-④ Details
-
伴生对象
必须和伴生类
同名.如果只有伴生对象,其会自动生成一个对应的空的半生类,如果只有伴生类其不会自动生成伴生对象. - 如果
半生对象中
定义一个apply(apply形参列表)
,则可以不用new
就创建对象,直接通过类名(apply形参列表)
即可以触发对应的apply方法返回对象.
② 特质
学习特质之前我们先来看下Java中的接口.
Java中的接口特点
- 在Java中,
一个类
可以实现多个接口
。 - 在Java中,
接口
之间支持多继承
- 接口中
属性
都是常量
- 接口中的
方法
都是抽象的
Java中的接口有以下缺点
- 如果B实现了Ia接口,并且C继承自B,则C中会多出哪个被实现的方法,但是很多时候我们并不想要.而
Scala可以通过mixin动态混入技术实现该功能
-
无法在接口中实现方法
.(在Java8之前), Scala的trait可以实现该功能.
②-① Why
-
trait拥有接口和抽象类的特点
.学习trait,是因为trait拥有java中接口的特点
和抽象类的特点
,其可以很方便的解决以上接口存在的问题.所以我们可以用trait来替代接口. -
trait可以动态扩展类的功能而不通过继承
.trait可以让一个类(包含抽象类),能够不通过继承trait
就可以动态的扩展
trait里的非抽象方案,增强这个类的功能
.
②-② How
规则和语法
- 如果使用
传统方法(extends)
,则子类依旧会继承父类实现的接口方法 - 如果使用
mixin(动态混入)
,则子类不会继承父类实现的接口方法
获取数据库连接(trait的接口特点)-传统方法
object TraitDemo02 {
def main(args: Array[String]): Unit = {
val mySQLDriver = new MySQLDriver
mySQLDriver.getConnection()
val oracleDriver = new OracleDriver
oracleDriver.getConnection()
}
}
trait DBDriver{
def getConnection()
}
class A {}
class B extends A {}
class MySQLDriver extends A with DBDriver {
override def getConnection(): Unit = {
println("连接MySQL成功")
}
}
class D {}
class OracleDriver extends D with DBDriver {
override def getConnection(): Unit = {
println("连接Oracle成功")
}
}
class F extends D {}
动态混入(mixin)
/**
* @author sweetcs
*/
object TraitDemo04Mixin {
def main(args: Array[String]): Unit = {
val oracle = new OracleWithMixin with DBDriver02
oracle.insert()
}
}
class OracleWithMixin {
}
trait DBDriver02 {
def insert(): Unit = {
println("正在插入数据到数据库中")
}
}
②-③ What
- 特质中如果还有普通方法,在底层是如何实现这个普通方法的调用呢?其在底层是放在哪个类里?
反编译后的代码
- Animal.class
public abstract interface Animal
{
public abstract void sayHi();
public abstract void eat();
}
- Animal$class.class
public abstract class Animal$class
{
public static void eat(Animal $this)
{
Predef$.MODULE$.println("I am eating~~~");
}
public static void $init$(Animal $this) {}
}
- sheep.class
public class Sheep
implements Animal
{
public void eat()
{
Animal$class.eat(this);
}
public Sheep()
{
Animal$class.$init$(this);
}
public void sayHi()
{
Predef$.MODULE$.println("hello human");
}
}
可以看到trait中的普通方法其实真正的实现是在特质名$class
这个类里,并且其将普通方法转换成一个公共的静态方法
.Sheep中调用普通方法,本质是是去调用特质名$class.普通方法名()
- Scala底层是如何实现Mixin动态混入技术的,即如何做到不继承trait而又能用其功能?如何做到使用动态混入创建出来的类不会影响其子类?
如下代码是动态混入TraitDemo04Mixin
中的反编译代码
public final class TraitDemo04Mixin$
{
public static final MODULE$;
static
{
new ();
}
public void main(String[] args)
{
OracleWithMixin oracle = new OracleWithMixin()
{
public void insert()
{
DBDriver02$class.insert(this);
}
};
((DBDriver02)oracle).insert();
}
private TraitDemo04Mixin$()
{
MODULE$ = this;
}
}
- DBDriver02$class
public abstract class DBDriver02$class
{
public static void insert(DBDriver02 $this)
{
Predef$.MODULE$.println("正在插入数据到数据库");
}
public static void $init$(DBDriver02 $this) {}
}
- OracleWithMixin
public class OracleWithMixin {}
分析:
- 我们知道trait中的普通方法最终在底层是在
trait名$calss
这个抽象类中,并且会将其变成一个静态的方法.这里对应的就是DBDriver02$class
中的insert公共静态方法
. -
TraitDemo04Mixin$
是程序真正的main入口,这里可以看到mixin的底层技术本质其实就是new了一个匿名子类
,并且实现了对应的insert接口,并且在接口中调用的是DBDriver02$class.insert
方法. - 因为使用的是
new 匿名子类
,所以这种方式并不是继承trait(如上OracleWithMixin
并没有继承DBDriver02
).自然其子类也就不会有
对应的insert
方法.
②-④ Details
- scala的trait即
特质
需要首字母大写
- scala中java中的接口都可以当做trait使用.
- scala中特质的使用,用
extends
关键字,如果有多个就使用extends 特质1 with 特质2 with 特质3
,如果有父类要继承,那么extends后面发父类.