Scala面向对象
Scala面向对象
class
定义
基本语法
[修饰符] class 类名 {
类体
}
=注意=
1)scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
2) 一个Scala源文件可以包含多个类,而且默认都是public
属性/成员变量
1)属性的定义语法同变量 [访问修饰符] var|val 属性名称 [: 类型] = 属性值
2) Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自行推断,属性类型可以省略(这点和Java不同)
3)如果定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值
对象的创建
val| var 对象名 [: 类型] = new 类型()
- 如果我们不希望改变对象的引用(即:内存地址),应该声明为val性质的,否则声明为var,scala设计者推荐使用val,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用
2)scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型可以省略,但当类型和后面new对象类型有继承关系即多态时,就必须写了
构造器
和java一样,scala构造对象也需要调用构造方法,并且可以有任意多个构造方法(即scala中构造器也支持重载)
scala的构造器包括:主构造器和辅助构造器,辅助构造器函数名称this,可以有多个,编译器通过不同参数来区分
class 类名(形参列表) { //主构造器
类体
def this(形参列表) { //辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个
}
}
class Person(inName: String, inAge: Int) {
var name: String = inName
var age: Int = inAge
def this(name: String) {
//辅助构造器,必须在第一行显式调用主构造器(可以是直接,也可以是间接)
this("java",34)
this.name = name
}
}
=说明=
1)scala构造器作用是完成对新对象的初始化,构造器没有返回值
2)主构造器的声明直接放置于类名之后
3)主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法
class Person(inName: String, inAge: Int) {
var name: String = inName
var age: Int = inAge
println("xxxxxxxxx")//在构造的时候会执行该语句
def this(name: String) {
//辅助构造器,必须在第一行显式调用主构造器(可以是直接,也可以是间接)
this("java",34)
this.name = name
}
}
4)如果主构造器无参数,小括号可以省略,构造对象时调用的构造方法的小括号也可以省略
5)如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了
class Person private(){}
6)辅助构造器的声明不能和主构造器的声明一致,否则会发生错误,即构造器名重复
构造器参数
1)scala类的主构造器的形参未用任何修饰符修饰修饰,那么这个参数就是局部变量
2)如果参数使用val关键字声明,那么scala会将参数作为类的私有只读属性
3)如果参数使用var关键字声明,那么scala会将参数作为类的成员属性,并会提供属性对应的get/set方法,但这时的成员属性是私有的,但是可读写
object Hello {
def main(args: Array[String]): Unit = {
val worker = new Worker("xxx")
println(worker.name)
// println(worker.inName) //不能访问inName
val worker2 = new Worker2("xxx")
println(worker2.inName)
val worker3 = new Worker3("xxx")
worker3.inName = "xxxx"
}
}
//那么inName就是一个局部变量
class Worker(inName: String){
var name = inName
}
//那么inName就是一个private的只读属性
class Worker2(val inName: String){
var name = inName
}
//那么inName就是一个private的可读写属性
class Worker3(var inName: String){
var name = inName
}
Bean属性
JavaBean 规范定义了java的属性是像getXxx()和setXxx()的方法。许多Java框架都依赖这个命名习惯,为了Java的互操作。将Scala字段加@BeanProperty,这样就会自动生成规范的setXxx和getXxx方法。这时可以使用对象.getXxx()和对象.getXxx()来调用属性
Scala中的包
和java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些
Scala中包的打包
scala中包名和源码所在的系统文件目录结构可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器来完成),由于这一特性,scala就可以灵活在一个文件中创建任何结构的东西
package com.chen {
import com.chen.scala1.Person
import com.chen.scala2.Student
//包中创建包
package scala1{
class Person {
}
}
//包中创建object,trait,class
object Test {
def main(args: Array[String]): Unit = {
val p1 = new Person()
val s1 = new Student()
}
}
package scala2{
class Student{
}
}
}
scala中自动引入的包
java.lang包
scala包(并不是其下面的子包可以自动引入)
Prddef包
包权限
class Clerk {
//在底层也是私有的,底层只是有了get和set方法
var name = "100"
//只能在自己的类中和伴生对象中可以使用
private val sal = 125.52
//protected访问权限更加严格,只能子类访问,同包无法访问
protected val age = 10
//仍然是private,在visit包及其子包中可以访问
private[vistit] val add = "beijing"
//默认=public
def showInfo ={
println(" name " + name + " sal " + sal)
}
}
字段重写
public class Test {
public static void main(String[] args) {
/**
* 1.对于同一个对象,用父类的引用去取值(字段),会取到父类的值
* 2.用子类的引用去取值,会得到子类的值
* java的动态绑定机制
* 1.如果调用的是方法,则jvm会将该方法和对象的内存地址绑定,即对象是谁,就调用谁
* 2.如果调用的是一个属性,则没有动态绑定机制,引用是谁就会调用谁
*/
Super sub = new Sub();
System.out.println(sub.s); //输出chenliqi
}
}
class Super {
String s = "chenliqi";
}
class Sub extends Super {
String s = "liuligang";
}
Scala中对于字段重写
class AA {
val age: Int = 30
}
class BB extends AA {
//其实底层的本质是方法的复写,根据jvm的动态绑定机制,所以是可以实现字段的复写
override val age: Int = 60
}
抽象类
在scala中,通过abstract关键字标记不能被实例化的类,方法不用标记为abstract,只要省略方法体即可,抽象类可以拥有抽象字段,抽象字段就是没有被初始化的字段
abstract class Animal {
//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也
//声明未abstract类
var name: String //抽象的字段
var age: Int //抽象的字段
var color: String = "black" //普通属性
def cry() //抽象方法不需要标记abstract
}
伴生对象
Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互,就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态类容都可以放置在它的伴生对象中声明和调用
/**
* 1.当在同一个文件中,有class ScalaPerson和 object ScalaPerson
* 2.object ScalaPerson称为伴生类,将非静态的内容写入到该类中
* 3.object ScalaPerson称为伴生对象,将静态的内容写入到该对象中
* 4.class ScalaPerson编译后底层生成ScalaPerson.class
* 5.object ScalaPerson编译后底层生成ScalaPerson$.class
* 6.对于伴生对象中的内容,可以通过ScalaPerson.访问
*/
//伴生类
class ScalaPerson {
val name: String = ""
}
//伴生对象
object ScalaPerson {
var sex: Boolean = true
}
1)Scala中伴生对象采用object关键字声明,伴生对象中声明的全是”静态“内容,可以通过半生对象名直接调用
2)伴生对象对应的类称之为半生类,伴生对象的名称应该和半生类名一致
3)伴生对象中的属性和方法都可以通过伴生对象名直接访问
4)伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误!),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。
伴生对象-apply方法
在伴生对象中定义apply方法,可以是新啊:类名(参数)方式类创建对象实例
object Hello {
def main(args: Array[String]): Unit = {
val pig = Pig("chen") //自动 apply(pName: String)
val pig2 = Pig() //自动apply()
}
}
class Pig(pName: String){
var name: String = pName
}
object Pig {
def apply(pName: String): Pig = new Pig(pName)
def apply(): Pig = new Pig("匿名猪头")
}
如在BigInt中
def apply(x: String): scala.math.BigInt
def apply(x: String,radix: Int): scala.math.BigInt
def apply(bitlength: Int,certainty: Int,rnd: scala.util.Random): scala.math.BigInt
def apply(numbits: Int,rnd: scala.util.Random): scala.math.BigInt
def apply(x: Array[Byte]): scala.math.BigInt
def apply(i: Int): scala.math.BigInt
def apply(l: Long): scala.math.BigInt
def apply(signum: Int,magnitude: Array[Byte]): scala.math.BigInt
def apply(x: java.math.BigInteger): scala.math.BigInt
所以我们可以使用 val i = BigInt("12344")
特质
比java中interface功能更加强大,在scala中特质的地位相当重要
trait = interface + abstract class
在scala中,对于所有java的接口都可以当作trait使用
如Serializable 其trait Serializable extends Any with java.io.Serializable
动态混入
使用的特别多,Scala中特有的,java中并没有
object Hello {
def main(args: Array[String]): Unit = {
//在不修改类的定义基础上,让它们可以使用trait方法
val oracleDB = new OracleDB with Operate
oracleDB.insert(1)
val mysql = new Mysql with Operate
mysql.insert(200)
//如果一个抽象类中有抽象方法,如何混入特质?(需要实现抽象方法)
val mysql2 = new Mysql2 with Operate {
override def say(): Unit = {println("您好")}
}
mysql2.insert(23)
mysql2.say()
}
}
trait Operate {
def insert(id: Int) = {
println("插入数据: " + id)
}
}
class OracleDB {}
abstract class Mysql {}
abstract class Mysql2 {
def say()
}
叠加特质
object Hello {
def main(args: Array[String]): Unit = {
//研究第一个问题,当我们创建一个动态混入对象时,其顺序是怎么样的?
//scala在叠加特制的时候,会首先从后面的特质开始执行(从左向右)
//1.operate2...
//2.Data...
//3.DB...
//4.File...
//研究第二个问题,当我们执行一个动态混入对象的方法,其执行顺序是怎么样的?
//(1)从右向左执行,(2)当执行到super时,是指左边的特质(3)如果左边没有特质了,则super就是父特质
//向文件
//向数据库
//插入数据 = 100
val mySql4 = new MySql4 with DB with File
mySql4.insert(100)
}
}
trait Operate2 {
println("Operate2...")
def insert(id: Int)
}
trait Data extends Operate2 {
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并不是执行父Data的insert方法,而是执行从有右至左的特质,
//所以对上面的测试,会出现“向数据库”这样的结果
super.insert(id)
}
}
class MySql4{}
应用
object Hello {
def main(args: Array[String]): Unit = {
val mySql5 = new MySql5 with DB5 with File5
mySql5.insert(100)
//下面的这条就会报错,也就是super中的父类还是Operate05,其并没有实现insert方法
// val mysql5_2 = new MySql5 with File5 with DB5
// mysql5_2.insert(100)
}
}
trait Operate05 {
def insert(id: Int)
}
trait File5 extends Operate05 {
/**
* 当abstract override就是明确告诉编译器,该方法确实是重写了父特质的抽象方法
* 但是由于其内部调用了super关键字,但是父类Operate05并没有实现该方法,所以编
* 译并不能通过,因此我们需要声明为abstract,表明该方法还是一个抽象方法
* 因为没有完全的实现,需要其他的特质继续实现,通过混入顺序调用
*/
abstract override def insert(id: Int): Unit = {
println("插入数据库")
//动态混入时,当执行到super时,时指左边的特质
super.insert(id)
}
}
trait DB5 extends Operate05 {
override def insert(id: Int): Unit = {
println("将数据插入数据库")
}
}
class MySql5{}
嵌套类
object Hello {
def main(args: Array[String]): Unit = {
val outer1 = new ScalaOuter()
val outer2 = new ScalaOuter2()
//创建成员内部类的实例
//在scala中创建成员内部类和java有所不同
//java中是outer.new ScalaInner()方式,这里有所不同
//从创建语法可以得知默认情况下,内部类和外部对象关联
val inner1 = new outer1.ScalaInner()
val inner2 = new outer2.ScalaInner()
//创建静态内部类的实例,和java相同
val staticInner = new ScalaOuter.statciInner()
staticInner.info()
}
}
class ScalaOuter {
var name = "chenliqi"
private val sal = 30.25
class ScalaInner {
def info() = {
//内部类访问外部类的属性,使用外部类.this.属性
println("姓名:" + ScalaOuter.this.name +
" 薪水:" + ScalaOuter.this.sal)
}
// ScalaOuter#称为投影,作用就是屏蔽外部类对象对内部类对象的影响
//如果没有加投影的话,inner1.test(innser2)会报错
def test(c1: ScalaOuter#ScalaInner) = {}
}
}
class ScalaOuter2 {
myouter => //这里可以理解成外部类的别名,看做是外部类的一个实例,在源码中多使用这种方式
var name = "chenliqi"
private val sal = 50.5
class ScalaInner {
def infor() = {
//在内部类中访问外部类的属性,使用外部类.this.属性
println("姓名: " + myouter.name + " 薪水: " + myouter.sal)
}
}
}
object ScalaOuter {
var name = "liulignag"
class statciInner {
def info() = {
//静态内部类访问外部属性
println("姓名: " + ScalaOuter.name)
}
}
}
样例类
为模式匹配而优化的类,提供apply和unapply方法让模式匹配可以工作
将自动生成toString,equals,hashCode,copy等模板方法
abstract class Item{}
case class Book(description: String, price: Double) extends Item
case class Bundle(description: String,discount: Double, item: Item) extends Item
//统计价格
def price(it: Item): Double = {
it match {
case Book(,p) => p
case Bundle(,disc,its @ _ ) => its.map(price).sum - disc
}
}
1)样例类是为模式匹配而优化的类
2)构造器中的每个参数都称为val --- 除非它被显式地声明为var
3)在样例类对应的伴生对象中提供apply方法让我们不用new关键字就能构造出相应的对象
4)提供unapoly方法让模式匹配可以工作
5)将自动生成toString,equals,hashCode,copy等模板方法
6)样例类除上述外,与其他类完全一样,可以添加字段和方法等来拓展