scala面向对象总结。
- Java是面向对象语言,但存在着非面向对象内容:基本类型、null,静态方法等;
- scala是天生面向对象语言,一切皆对象
语法总结:
- scala类默认是public的,不必声明
- 一个源文件可以有多个类的声明
类的修饰符
查看简单示例:
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方法:
- get方法和属性名一样
- set方法为:属性_$eq()
- 并且是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 = 28scala类里可以写任何语句,这是和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)对象创建流程
- 加载类信息(属性、方法)
- 在堆中给对象开辟空间
- 调用主构造器对属性进行初始化
- 使用辅助构造器进行初始化
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;
  }
}抽象类
- abstract关键字修饰class
- 抽象方法不能用abstract关键字修饰
- 方法也可实现,字段也可初始化
scala类层级关系
- 所有类都是AnyRef的子类,类似java的Object
- AnyVal和AnyRef都是Any的子类,Any是根节点
- Any中定义了isInstanceOd, asInstanceOf以及hash
- Null类型的唯一实例就是null对象,可以将null赋给任何引用,但不能赋给值类型的变量
- 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 = nullscala伴生对象–解决静态问题
按理说,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.Serializabletrait的动态混入(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...")
}- 声明类时同时混入特质
class F6 extends E6 with C6 with D6 {
  println("F6...")
}
val f1 = new F6输出:
E6...
A6...
B6...
C6...
D6...
F6...- 构建对象时动态混入
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()
}






 
  
  1024浏览
1024浏览
      

 
  
  
  
 