面向对象的Scala
Java是面向对象的编程语言,由于历史原因,Java中还存在着非面向对象的内容:基本类型 ,null,静态方法等。
Scala语言来自于Java,所以天生就是面向对象的语言,而且Scala是纯粹的面向对象的语言,即在Scala中,一切皆为对象。
如何定义类
基本语法
[修饰符] class 类名 { 类体 }
注意:
scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public),[修饰符在后面再详解].
一个Scala源文件可以包含多个类.
//定义一个类Cat //一个class Cat 对应的字节码文件只有一个 Cat.class ,默认是public class Cat { //定义/声明三个属性 //说明 //1. 当我们声明了 var name :String时, 在底层对应 private name //2. 同时会生成 两个public方法 name() <=类似=> getter public name_$eq() => setter var name: String = "" //给初始值 var age: Int = _ // _ 表示给age 一个默认的值 ,如果Int 默认就是0 var color: String = _ // _ 给 color 默认值,如果String ,默认是就是"" def meng(): Unit ={ } }
底层逻辑:
public class Cat { private String name = ""; private int age; private String color; public String name() { return this.name; } public void name_$eq(String x$1) { this.name = x$1; } public int age() { return this.age; } public void age_$eq(int x$1) { this.age = x$1; } public String color() { return this.color; } public void color_$eq(String x$1) { this.color = x$1; } public void meng() {} }
属性/成员变量
- 属性的定义语法:同变量:[访问修饰符] varl 属性名称 [:类型] = 属性值
- 属性的定义类型:属性是类的一个组成部分,属性的定义类型可以为任意类型,包含值类型或引用类型
- 属性的初始化:
- Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略(这点和Java不同)。
- 如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型.
- 如果在定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值.
class Person { var age: Int = 10 //给属性赋初值,省略类型,会自动推导 var sal = 8090.9 var Name = null // 是 null类型 var address1: String = null //ok null=>"" var address2 = "" //ok 会自动推导=>"" String } class A { var var1 :String = _ // null var var2 :Byte = _ // 0 var var3 :Double = _ //0.0 var var4 :Boolean = _ //false }
如何创建对象
val | var 对象名 [:类型] = new 类型()
- 如果我们不希望改变对象的引用(即:内存地址), 应该声明为val 性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。
- scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面new 对象类型有继承关系即多态时,就必须写【:类型】
方法
基本说明
Scala中的方法其实就是函数,声明规则请参考函数式编程中的函数声明。
基本语法
def 方法名(参数列表) [:返回值类型] = { 方法体... }
方法的调用机制原理
- 当我们scala开始执行时,先在栈区开辟一个main栈。main栈是最后被销毁
- 当scala程序在执行到一个方法时,总会开一个新的栈。
- 每个栈是独立的空间,变量(基本数据类型)是独立的,相互不影响(引用类型除外)
- 当方法执行完毕后,该方法开辟的栈就会被jvm机回收。
构造器
基本介绍
构造器(constructor)又叫构造方法,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
Scala构造器的介绍
和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法(即scala中构造器也支持重载)。
Scala类的构造器包括: 主构造器 和 辅助构造器
Scala构造器的基本语法
class 类名(形参列表) { // 主构造器 // 类体 def this(形参列表) { // 辅助构造器 } def this(形参列表) { //辅助构造器可以有多个... } }
Scala构造器注
- Scala构造器作用是完成对新对象的初始化,构造器没有返回值。
- 主构造器的声明直接放置于类名之后
- 主构造器会执行类定义中的所有语句,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别
- 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
- 辅助构造器名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分,辅助构造器需要在内部直接或者间接调用柱构造器,本质上辅助构造器在底层就是构造器重载。
- 如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象。
- 辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)
代码案例:
object CatDemo { def main(args: Array[String]): Unit = { var cat=new Cat("wqbin",10) // print(cat.name,cat.age) //报错 println(cat.cname) println(cat.cage) } } //定义一个类Cat class Cat(name:String,age:Int) { var cname: String =name var cage: Int =age var csex: String =_ var chobby: String =_ def this(name:String,age:Int,sex:String){ this(name,age)//主构造器或者其他构造器 this.csex=sex } def this(name:String,age:Int,sex:String,hobby:String){ this(name,age,sex) //主构造器或者其他构造器 this.chobby=hobby } }
底层逻辑:
public class Cat { private String cname; private int cage; private String csex; private String chobby; public Cat(String name, int age) { this.cname = name; this.cage = age; } public Cat(String name, int age, String sex) { this(name, age); csex_$eq(sex); } public Cat(String name, int age, String sex, String hobby) { this(name, age, sex); chobby_$eq(hobby); } public String cname() { return this.cname; } public void cname_$eq(String x$1) { this.cname = x$1; } public int cage() { return this.cage; } public void cage_$eq(int x$1) { this.cage = x$1; } public String csex() { return this.csex; } public void csex_$eq(String x$1) { this.csex = x$1; } public String chobby() { return this.chobby; } public void chobby_$eq(String x$1) { this.chobby = x$1; } }
私有主构造器,或者辅助构造器:
构造器参数
- Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
- 如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用 。
- 如果参数使用var关键字声明,那么那么Scala会将参数作为类的成员属性使用,并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法,即这时的成员属性是私有的,但是可读写。
//1. 如果 主构造器是Worker1(inName: String) // inName就是一个局部变量 class Worker1(inName: String) { var name: String = inName } //. 如果 主构造器是Worker2(val inName: String) // inName就是Worker2的一个private的只读属性 class Worker2(val inName: String) { var name: String = inName } // 如果 主构造器是Worker3(var inName: String) // inName就是Worker3的一个private 的可以读写属性 class Worker3(var inName: String) { var name: String = inName }
继承主构造器带参的父类
父类
class China(var name){}
子类
class Japan extends China(name="son"){}
注意: 我们查看Scala文件编译后的class文件,然后再通过反编译为Java文件可以看到以下代码: 在Japan的默认构造中调用了父类的带参数的构造方法。
public Japan(){super("son");}
如果我们想在Japan中定义和父类相同的属性(覆盖override)则需要注意父类 属性的参数的修饰符必须为val (var 也不行), 否者会报错,在Scala中属性覆盖针对不易变的属性,子类必须加上 override val XXX: Tpye
修改父类:
class China(val name: String){}
修改子类:
class Japan(override val name: String) extends China(name="XX")
这里父类上的参数必须加上,否者编译不通,如果不想赋值则可以:Chian(name) 直接这样也可以。
以上为原理在开发时不注意这些的话,可以直接简写:
class Japan( name: String ) extends China(name) {}
把 override val 去掉, 变量前面 不要加任何的 权限修饰符。应为Scala的构造方法 是与class写在一起的
看成java的话:
public Japan( name: String ){super.(name)}
总:
Scala的语言设计的初衷就是少写代码,如果父类中有了非val的属性,我们的子类直接就可以在创建对象的时候,默认的构造中可以传入对应的属性值,并且使用该对象也可以访问的到,所以针对于非val的属性没必要override,而val修饰符在编译后的JAVA文件中是final修饰 是最终变量不可变,所以该值在子类中想引用这个变量并且想改变他的值,则需要override val,想想Scala这样设计也是挺合理的
对象创建的流程分析
对象创建的流程分析 看一个案例 class Person { var age: Short = 90 var name: String = _ def this(n: String, a: Int) { this() this.name = n this.age = a } } var p : Person = new Person("小倩",20) 1.加载类的信息(属性信息,方法信息) 2.在内存中(堆)开辟空间 4.使用父类的构造器(主和辅助)进行初始 5.使用主构造器对属性进行初始化 6.使用辅助构造器对属性进行初始化 7.将开辟的对象的地址赋给 p这个引用
Bean属性
JavaBeans规范定义了Java的属性是像getXxx()和setXxx()的方法。许多Java工具(框架)都依赖这个命名习惯。
为了Java的互操作性。将Scala字段加@BeanProperty时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。
注意:给某个属性加入@BeanPropetry注解后,会生成getXXX和setXXX的方法并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存。
class Worker1(inName: String) { @BeanProperty var name: String = inName }
- @beanGetter
- @beanSetter