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 = 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)
对象创建流程
- 加载类信息(属性、方法)
- 在堆中给对象开辟空间
- 调用主构造器对属性进行初始化
- 使用辅助构造器进行初始化
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 = 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...")
}
- 声明类时同时混入特质
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()
}