Scala面向对象

Scala面向对象

class

定义

基本语法
[修饰符] class 类名 {
类体
}
=注意=
1)scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
2) 一个Scala源文件可以包含多个类,而且默认都是public

属性/成员变量

1)属性的定义语法同变量 [访问修饰符] var|val 属性名称 [: 类型] = 属性值
2) Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自行推断,属性类型可以省略(这点和Java不同)
3)如果定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值

对象的创建

val| var 对象名 [: 类型] = new 类型()

  1. 如果我们不希望改变对象的引用(即:内存地址),应该声明为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)样例类除上述外,与其他类完全一样,可以添加字段和方法等来拓展

posted @ 2019-02-19 19:38  刘丽刚  阅读(174)  评论(0)    收藏  举报