开发者社区 > 博文 > scala面向对象总结
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

scala面向对象总结

  • 京东城市JUST团队
  • 2021-01-22
  • IP归属:未知
  • 31080浏览

scala面向对象总结。

  1. Java是面向对象语言,但存在着非面向对象内容:基本类型、null,静态方法等;
  2. scala是天生面向对象语言,一切皆对象

语法总结:

  1. scala类默认是public的,不必声明
  2. 一个源文件可以有多个类的声明

类的修饰符

查看简单示例:

object Demo01 {
  def main(args: Array[String]): Unit = {
    var p1 = new Person
    p1.name = "jimo"
    p1.age = 18
    println(p1.name, p1.age)
  }
}

class Person {
  var name: String = _
  var age: Int = 0
}

查看编译后的class文件:

public class Person {
  private String name;
  
  public String name() {
    return this.name;
  }
  
  public void name_$eq(String x$1) {
    this.name = x$1;
  }
  
  private int age = 0;
  
  public int age() {
    return this.age;
  }
  
  public void age_$eq(int x$1) {
    this.age = x$1;
  }
}

也就是说,scala已经生成了get、set方法:

  1. get方法和属性名一样
  2. set方法为:属性_$eq()
  3. 并且是public的

创建对象

对象的内存分配

方法调用机制

构造器

Java里构造器重点就是通过方法表示;可以重载;有一个默认无参构造,自己声明了就没有了。

scala构造器分类:主构造器,辅助构造器(形参个数不同)

主构造器:

class 类名(形参列表) { // 主构造器 
  def this(形参列表1){} // 辅助构造器
  def this(形参列表2){} // 辅助构造器
}

实例:

  def main(args: Array[String]): Unit = {
    val jimo = new Student("jimo", 18)
    jimo.hello()
  }

class Student(name: String, ageIn: Int) {

  var age: Int = ageIn
  println("hello 1")

  def hello(): Unit = {
    println("hello,I am " + name + ",age = " + age)
  }

  println("hello 2")

  age += 10
}
// hello 1
// hello 2
// hello,I am jimo,age = 28

scala类里可以写任何语句,这是和java不同的地方,那原理是什么呢?
通过编译后的class文件可以看出:

public class Student {
  private final String name;
  
  private int age;
  
  public Student(String name, int ageIn) {
    this.age = ageIn;
    Predef$.MODULE$.println("hello 1");
    Predef$.MODULE$.println("hello 2");
    age_$eq(age() + 10);
  }
  
  public int age() {
    return this.age;
  }
  
  public void age_$eq(int x$1) {
    this.age = x$1;
  }
  
  public void hello() {
    Predef$.MODULE$.println((new StringBuilder()).append("hello,I am ").append(this.name).append(",age = ").append(BoxesRunTime.boxToInteger(age())).toString());
  }
}

这些语句实际上是放在构造方法里执行的。

构造器的重载

辅构造器必须调用主构造器(间接调用也可以)

class Student(name: String, ageIn: Int) {

  def this(name: String) {
    this(name, 0)
  }

  def this(age: Int) {
    this(null, age)
  }

  def this() {
    this("jimo")
  }
}

私有化主构造器

用于不让使用者new 一个实例

class Teacher private() {
  def this(name: String) {
    this()
  }
}

属性

先看以下声明:

class P1(name: String) {
  println(name)
}

class P2(val name: String) {
  println(name)
}

class P3(var name: String) {
  println(name)
}

生成的不同在于:是否会变成成员变量;
val和var变量代表是否可读

public class P1 {
  public P1(String name) {
    Predef$.MODULE$.println(name);
  }
}

public class P2 {
  private final String name;
  
  public String name() {
    return this.name;
  }
  
  public P2(String name) {
    Predef$.MODULE$.println(name);
  }
}

public class P3 {
  private String name;
  
  public String name() {
    return this.name;
  }
  
  public void name_$eq(String x$1) {
    this.name = x$1;
  }
  
  public P3(String name) {
    Predef$.MODULE$.println(name());
  }
}

Bean属性

由于javaBeans规范了getX()和setX()等规范方法,所以很多框架采用(mybatis),由于需要反射,所以
需要应用规范。

class P4 {
  var name: String = _
  @BeanProperty
  var age: Int = 0
}

val p = new P4()
p.setAge(19)
println(p.getAge)

对象创建流程

  1. 加载类信息(属性、方法)
  2. 在堆中给对象开辟空间
  3. 调用主构造器对属性进行初始化
  4. 使用辅助构造器进行初始化
class P5 {
  var name: String = "hehe"

  def this(name: String) {
    this()
    println(this.name)
    this.name = name
    println(this.name)
  }
}

new P5("jimo")
输出:
hehe
jimo

继承

scala里重写父类非抽象方法必须加 override 修饰。

构造器的调用

class J {
  var name: String = _
  println("J...")
}

class JI extends J {
  def this(name: String) {
    this
    this.name = name
    println("JI...")
  }
}

new JI()
println("=========")
new JI("jimo")

结果:

J...
=========
J...
JI...

scala里:子类的主类和辅助构造器不能直接调用父类的构造器;
只有通过类声明时直接调用。

class K(name: String) {
  println("K")
}

class KI(name: String) extends K(name = name) {
  println("KI")

  def this() {
    this(null)
    println("KI:this")
  }
}

new KI()
println("=========")
new KI("jimo")

结果:

K
KI
KI:this
=========
K
KI

类型检查与转换

classOf[T]

    println(classOf[String])
    println(classOf[P])
    val s = "s"
    println(s.getClass.getName)

isInstanceOf

    val s = "s"
    println(s.isInstanceOf[String])

asInstanceOf

class P {
}

class PI extends P {
}

val pi = new PI
val p: P = pi.asInstanceOf[P]
println(p.isInstanceOf[P], p.isInstanceOf[PI])

输出:

class java.lang.String
class com.jimo.scala.oop.P
java.lang.String
true
(true,true) // p既是子类,也是父类

字段隐藏与覆写

java的字段隐藏

java中没有字段的覆写,只有方法的覆写。

但是可以模拟覆写的效果,原因是隐藏机制。

public class JavaOverrideDemo {
    public static void main(String[] args) {
        Sub sub = new Sub();
        System.out.println(sub.s);

        Super sub2 = new Sub();
        System.out.println(sub2.s);

        System.out.println(((Super) sub).s);
    }
}

class Super {
    String s = "super";
}

class Sub extends Super {
    String s = "sub";
}
/*
sub
super
super
*/

scala字段覆写

object ScalaOverrideDemo {
  def main(args: Array[String]): Unit = {
    val a: AA = new BB
    val b: BB = new BB
    println("a.age=" + a.age)
    println("b.age=" + b.age)
  }
}

class AA {
  val age: Int = 10
}

class BB extends AA {
  override val age: Int = 20
}
/*
a.age=20
b.age=20
*/

原理很简单,看看生成的class文件:

public class AA {
  private final int age = 10;
  
  public int age() {
    return this.age;
  }
}

public class BB extends AA {
  private final int age = 20;
  
  public int age() {
    return this.age;
  }
}

也就是java的方法重写。

val字段只可以重写父类的val字段,或者 同名的无参方法

为啥不能重写 var 修饰的字段?
因为,如果重写父类的var变量,那么就会生成 field_$eq的设值方法,
也就是说子类会继承这个方法,常量拥有set方法显然不行。

object ScalaOverrideDemo {
  def main(args: Array[String]): Unit = {
    val a: AA = new BB
    val b: BB = new BB
    println("a.name=" + b.name)
    println("b.name=" + b.name)
  }
}

class AA {
  def name(): String = {
    "AA"
  }
}

class BB extends AA {
  override val name: String = "BB"
}
/*
a.name=BB
b.name=BB
*/

那什么时候可以重写 var 修饰的字段呢?

在抽象类中的字段:

abstract class CC {
  var height: Int
}

class DD extends CC {
  override var height: Int = 20
}

原理很简单,字段在抽象类里压根就不生成,只有抽象方法:

public abstract class CC {
  public abstract int height();
  
  public abstract void height_$eq(int paramInt);
}

public class DD extends CC {
  private int height = 20;
  
  public int height() {
    return this.height;
  }
  
  public void height_$eq(int x$1) {
    this.height = x$1;
  }
}

抽象类

  1. abstract关键字修饰class
  2. 抽象方法不能用abstract关键字修饰
  3. 方法也可实现,字段也可初始化

scala类层级关系

  1. 所有类都是AnyRef的子类,类似java的Object
  2. AnyVal和AnyRef都是Any的子类,Any是根节点
  3. Any中定义了isInstanceOd, asInstanceOf以及hash
  4. Null类型的唯一实例就是null对象,可以将null赋给任何引用,但不能赋给值类型的变量
  5. Nothing类没有实例,对于泛型有用处,例如:空列表Nil和类型是List[Nothing],他是List[T]的子类型
    var n: Long = null
    println(n)
Error:(5, 19) an expression of type Null is ineligible for implicit conversion
    var n: Long = null
Error:(5, 19) type mismatch;
 found   : Null(null)
 required: Long
    var n: Long = null

scala伴生对象–解决静态问题

按理说,scala是纯OOP的语言,不应该有静态对象和静态属性。

但是,必须要有静态概念,因为要和java打通。

于是,scala通过【伴生对象】来模拟类对象。

  def main(args: Array[String]): Unit = {
    println(CCC.name)
  }

/**
 * 当类名和object名称一样,且放在同一个scala文件中
 * 1.object CCC 为伴生对象
 * 2.class CCC 为伴生类
 * 3.将静态内容写在伴生对象里,普通属性写在伴生类
 */
object CCC {
  var name: String = "jimo"
}

class CCC {
  var sex: Boolean = true
}

通过查看class文件可以理解实现原理:

public class CCC {
  private boolean sex = true;
  
  public static void name_$eq(String paramString) {
    CCC$.MODULE$.name_$eq(paramString);
  }
  
  public static String name() {
    return CCC$.MODULE$.name();
  }
  
  public boolean sex() {
    return this.sex;
  }
  
  public void sex_$eq(boolean x$1) {
    this.sex = x$1;
  }
}

public final class CCC$ {
  public static final CCC$ MODULE$;
  
  private String name;
  
  public String name() {
    return this.name;
  }
  
  public void name_$eq(String x$1) {
    this.name = x$1;
  }
  
  private CCC$() {
    MODULE$ = this;
    this.name = "jimo";
  }
}

实际上,scala没有生成静态内容,只不过将伴生对象生成了一个新的类,实现属性的get方法调用。

单例对象

Java版

Java中的单例模式:饿汉式,懒汉式。

优化之后的版本:

public class Singleton {

    private Singleton() {
    }

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

scala版本

scala直接用object就是单例的

  def main(args: Array[String]): Unit = {
    println(SSingleton.name)
    println(SSingleton.hello())
  }

object SSingleton {
  var name: String = "hello"

  def hello(): Unit = {}
}

但底层如何实现呢?虚拟机里没有object的,编译查看class文件:

public final class SSingleton {
  public static void hello() {
    SSingleton$.MODULE$.hello();
  }
  
  public static void name_$eq(String paramString) {
    SSingleton$.MODULE$.name_$eq(paramString);
  }
  
  public static String name() {
    return SSingleton$.MODULE$.name();
  }
}

public final class SSingleton$ {
  public static final SSingleton$ MODULE$;
  
  private String name;
  
  public String name() {
    return this.name;
  }
  
  public void name_$eq(String x$1) {
    this.name = x$1;
  }
  
  public void hello() {}
  
  private SSingleton$() {
    MODULE$ = this;
    this.name = "hello";
  }
}

特质与接口

scala里没有接口,使用特质来代替接口的概念。

多个类具有相同的特征,就可以抽象出来。

trait可看作对继承的一种补充

trait的使用

  • 没有父类:class 类名 extends 特质1 with 特质2 with …
  • 有父类:class 类名 extends 父类 with 特质1 with 特质2 with …
trait Move {
  def fly(): Unit
}

abstract class Plane {}

class Airliner extends Plane with Move {
  override def fly(): Unit = {
    println("客机使用翅膀飞行")
  }
}

class Helicopter extends Move {
  override def fly(): Unit = {
    println("直升机使用螺旋桨飞行")
  }
}

查看编译后的class文件:可以看到变成了接口

public interface Move {
  void fly();
}

public class Helicopter implements Move {
  public void fly() {
    Predef$.MODULE$.println(");
  }
}

public class Airliner extends Plane implements Move {
  public void fly() {
    Predef$.MODULE$.println(");
  }
}

trait里实现方法

trait既可以定义抽象方法,也可以实现默认方法。

trait TA {
  def hello(): Unit = {
    println("TA hello()")
  }

  def go(): Unit
}

class TAI extends TA {
  override def go(): Unit = {
    println("TAI go()")
  }
}

看看怎么实现的:

public interface TA {
  void hello();
  
  void go();
}
public abstract class TA$class {
  public static void $init$(TA $this) {}
  
  public static void hello(TA $this) {
    Predef$.MODULE$.println("TA hello()");
  }
}

public class TAI implements TA {
  public void hello() {
    TA$class.hello(this);
  }
  
  public TAI() {
    TA$class.$init$(this);
  }
  
  public void go() {
    Predef$.MODULE$.println("TAI go()");
  }
}

可以看出同时生成了一个接口,还有一个抽象类,抽象类负责实现默认方法,
然后被调用。

java中的接口都可以当作trait使用

经过上面本质的分析,trait就是接口和抽象类的组合,所以当然可以使用java的接口。

trait Serializable extends Any with java.io.Serializable

trait的动态混入(mixin)

  • 不继承特质,但又想使用特质的功能:可以在创建对象时(抽象类也可以)混入特质
  def main(args: Array[String]): Unit = {
    val b = new MysqlDB with TB
    b.insert(1)
  }

trait TB {
  def insert(id: Int): Unit = {
    println("插入数据=" + id)
  }
}

class MysqlDB {}

看看如何实现:

  public void main(String[] args) {
    MysqlDB b = new TraitDemo03$$anon$1();
    ((TB)b).insert(1);
  }
  
  public final class TraitDemo03$$anon$1 extends MysqlDB implements TB {
    public void insert(int id) {
      TB$class.insert(this, id);
    }
    
    public TraitDemo03$$anon$1() {
      TB$class.$init$(this);
    }
  }

看来是生成了一个匿名类。

  • 动态混入的优点:可以在不影响原始类的情况下加功能,耦合性很低
new BB with TB {
  override def haha(): Unit = {}
}

trait TB {
  def insert(id: Int): Unit = {
    println("插入数据=" + id)
  }
}

abstract class BB {
  def haha(): Unit
}

叠加特质

看一个多层次的继承

class Mysql {}

trait Op {
  println("Op...")

  def insert(id: Int): Unit
}

trait Data extends Op {
  println("Data...")

  override def insert(id: Int): Unit = {
    println("插入数据+" + id)
  }
}

trait DB extends Data {
  println("DB...")

  override def insert(id: Int): Unit = {
    println("向数据库 ")
    super.insert(id)
  }
}

trait File extends Data {
  println("File...")

  override def insert(id: Int): Unit = {
    println("向文件 ")
    super.insert(id)
  }
}

然后看看调用:

  def main(args: Array[String]): Unit = {
    val mysql = new Mysql with DB with File
//    mysql.insert(1)
  }

先来看看构建顺序:

  • 构建顺序与声明的顺序一样(从左至右)

看看结果:

Op...
Data...
DB...
File...

接下来看看执行顺序:

mysql.insert(1)

执行顺序:

  • 刚好是相反的,只要查找到一个父类的方法就执行
向文件 
向数据库 
插入数据+1

这里我就很好奇,为什么 insert方法只执行了一遍?

  • 解释:scala特质中如果使用了super,并不表示调用方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找

看一下class:

  public void main(String[] args) {
    Mysql mysql = new TraitDemo04$$anon$1();
    ((File)mysql).insert(1);
  }
  
  public final class TraitDemo04$$anon$1 extends Mysql implements DB, File {
    public void insert(int id) {
      File$class.insert(this, id);
    }
    
    public TraitDemo04$$anon$1() {
      Op$class.$init$(this);
      Data$class.$init$(this);
      DB$class.$init$(this);
      File$class.$init$(this);
    }
  }

public abstract class File$class {

  public static void insert(File $this, int id) {
    Predef$.MODULE$.println("向文件 ");
    $this.com$jimo$scala$oop2$File$$super$insert(id);
  }
}

其实class也没有透露更多细节,发现mysql实际上是最右边的特质File的实例,当File的insert
执行到 super.insert时,会继续找到 DB的insert执行,再执行 super.insert 发现左边已经没有
特质了,所以执行父特质 Data 的insert方法。

指定父类执行

叠加特质时可以指定调用父类的特质方法

trait File extends Data {
  println("File...")

  override def insert(id: Int): Unit = {
    println("向文件 ")
    super[Data].insert(id)
    //    super.insert(id)
  }
}

这时候发现结果就只剩2句话了:

向文件 
插入数据+1

特质的抽象方法实现

注意abstract, 不加会报错

trait Op {
  println("Op...")

  def insert(id: Int): Unit
}
trait File2 extends Op {
  abstract override def insert(id: Int): Unit = {
    println("File2...")
    super.insert(id)
  }
}

富接口

特质中既有抽象方法,又有已经实现的方法。

trait中字段的继承

object TraitDemo05 {
  def main(args: Array[String]): Unit = {
    val mysql = new Mysql5 with Op5 {
      override var opType: String = "mysql-insert"
    }
    println(mysql.opType)
    val mysql1 = new Mysql5 with DB5
    println(mysql1.opType)
  }
}

trait Op5 {
  var opType: String
}

trait DB5 extends Op5 {
  override var opType: String = "db-insert"
}

class Mysql5 {}

实际上是作为了类的一个属性。

 public void main(String[] args) {
    Mysql5 mysql = new TraitDemo05$$anon$2();
    scala.Predef$.MODULE$.println(((Op5)mysql).opType());
    Mysql5 mysql1 = new TraitDemo05$$anon$1();
    scala.Predef$.MODULE$.println(((DB5)mysql1).opType());
  }
  
  public final class TraitDemo05$$anon$2 extends Mysql5 implements Op5 {
    private String opType = "mysql-insert";
    
    public String opType() {
      return this.opType;
    }
    
    public void opType_$eq(String x$1) {
      this.opType = x$1;
    }
  }
  
  public final class TraitDemo05$$anon$1 extends Mysql5 implements DB5 {
    private String opType;
    
    public String opType() {
      return this.opType;
    }
    
    @TraitSetter
    public void opType_$eq(String x$1) {
      this.opType = x$1;
    }
    
    public TraitDemo05$$anon$1() {
      DB5$class.$init$(this);
    }
  }
  
  private TraitDemo05$() {
    MODULE$ = this;
  }
}

trait构造的执行顺序

首先看看继承关系:

trait A6 {
  println("A6...")
}

trait B6 extends A6 {
  println("B6...")
}

trait C6 extends B6 {
  println("C6...")
}

trait D6 extends B6 {
  println("D6...")
}

class E6 {
  println("E6...")
}
  1. 声明类时同时混入特质
class F6 extends E6 with C6 with D6 {
  println("F6...")
}

val f1 = new F6

输出:

E6...
A6...
B6...
C6...
D6...
F6...
  1. 构建对象时动态混入
class K6 extends E6 {
  println("K6...")
}

val k = new K6 with C6 with D6
println(k)

输出:

E6...
K6...
A6...
B6...
C6...
D6...

可以理解为:第二种在混入特质时,对象已经创建了。

trait继承类

可以扩展类的功能。

object TraitDemo07 {
  def main(args: Array[String]): Unit = {
    val log = new LogExp {}
    log.log()
  }
}

trait LogExp extends Exception {
  def log(): Unit = {
    println(getMessage)
  }
}

总结

创建对象的方式有几种

  • new
  • apply
  • 动态方式
  • 匿名子类

说说apply的方式:通过伴生对象实现,为什么要伴生对象呢?

因为,伴生对象可以访问类的所有属性,包括私有的。

  def main(args: Array[String]): Unit = {
    val a = AAA.apply()
    println(a)
  }

class AAA {}

object AAA {
  def apply(): AAA = new AAA()
}
共0条评论