Scala面向对象编程

本文详细介绍了Scala的面向对象编程特性,包括类的定义、成员访问修饰符、构造器、抽象类、单例对象、特质、样例类、泛型类、类型边界以及包与包对象的使用。重点讲解了类的继承、混入特质、伴生对象的特性以及样例类在不可变数据对象中的应用。
摘要由CSDN通过智能技术生成

Scala面向对象基本概念

类(class)

类通过class关键字定义
类通过new关键字创建实例
类拥有成员变量和方法
类的成员默认为public,也支持private、protected
类中无法定义静态成员变量和方法
类无需明确定义构造方法,通过构造参数列表声明为类的一部分

类成员访问修饰符

Java

ModifierClassPackageSubclassWorld
publicYYYY
protectedYYYN
defaultYYNN
privateYNNN

** Scala**

ModifierClassCompanionPackageSubclassWorld
protectedYYYNN
defaultYYYYY
privateYYNNN

类的定义

  • 构造器
    • 主构造器
      辅助构造器
  • 成员变量与方法
// 主构造器执行类定义中的所有语句
class Point(xc:Int, yc:Int){
	var x:Int = xc    // 成员变量
	var y:Int = yc
	// 辅助构造器
	def this() = {
		this(0, 0)  // 第一句必须调用其他构造器
	}
	// 成员方法
	def move(dx:Int, dy:Int) = {
		x = x + dx
		y = y + dy
	}
}

类的实例化

var p = new Point()
p.x
p.y
p = new Point(12,11)
p.x
p.y
p.move(1,2)

类的继承

Scala使用”extends“关键字实现继承
子类重写父类方法必须使用”override“关键字

class BlackPoint() extends Point {
	private var color = "black"
	override def move(dx:Int, dy:Int) = {
		x = x + dx
		y = y + dy
		println("moved to x:" + x + "y:" + y)
	}
}
var bp = new BlackPoint()
bp.x
bp.y
bp.move(1,2)

抽象类(abstract class)

抽象类可包含未实现的方法,即抽象方法
抽象类无法实例化
抽象类使用"abstract"关键字修饰

  • 子类重写父类抽象方法时,”override“关键字可选
    子类重写父类非抽象方法,“override”关键字必写
// 父类
abstract class Shape{
	def draw():Unit
}

// 子类
class Square extends Shape{
	override def draw():Unit = {
		println("draw a square")
	}
}
var shape = new Square
shape.draw

单例对象(object)

  • Scala的类中无法定义静态成员,即无static关键字。如何像Java一样表达类的静态成员变量、成员方法与静态代码块?
  • Scala解决方案:单例对象
    使用“object”关键字声明,可包含变量、方法与代码定义
    单例对象中的成员变量、成员方法通过单例对象名直接调用
    单例对象第一次被访问时初始化,并执行全部代码块
    单例对象不能new,且无构造参数
    程序入口main()方法必须定义在单例对象中
    单例对象与同名类定义在同一文件中时形成绑定关系

定义单例对象

// Blah.scala
package test  // 定义包名
// 定义单例对象
object Blah {
	println("Blah initializing...")
	def sum(l:List[Int]):Int = l.sum
}
test.Blah.sum(List[Int](1,2,3,4,5))

伴生

单例对象与同名类定义在同一文件中时形成绑定关系

  • 同名类称为单例对象的半生类(class)
  • 单例对象成为同名类伴生对象(object)

伴生类与伴生对象可相互访问各自私有成员
伴生对象可为伴生类增加静态成员

// Student.scala
// 伴生类
class Student(n:String, a:Int){
	private var name = n  // 私有变量,伴生对象可以访问 
	private var age = a
}
// 伴生对象
object Student {
	// 使用伴生对象的apply()方法省略new关键字。Student.apply()等价于Student()
	def apply(n:String, a:Int):Student = new Student(n, a)
	def main(args:Array[String]):Unit = {
		val stu = Student("Jason",9)  // 通过伴生对象的apply()方法创建实例
		println(stu.name)
	}
}

特质(trait)

Scala中没有接口(interface)的概念
特质用于在类之间共享程序接口和字段,类似Java接口
特质是字段和方法的集合,可以提供字段和方法实现
类和单例对象都可以扩展特质(extends)
特质不能被实例化,因此没有构造参数,类似Java接口
特质使用“trait”关键字定义
实现特质中的方法使用“override”

// 使用特质
import scala.collection.mutable.ArrayBuffer
trait Pet {
	val name:String
	def cry():Unit
}
class Dog(val name:String) extends Pet {
	override def cry() = println("wow....")
}
val dog = new Dog("Harry")
val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.foreach(per = {
	println(pet.name)
	pet.cry()
})
//prints Harry wow....

混入特质(mixin)

当某个特质被用于组合类时,被称为混入
一个类只能有一个父类但是可以有多个混入(分别使用关键字extends和with)

abstract class A {
	val message:String
}
class B extends A {
	val message = "I'm an instance of class B"
}
trait C extends A {
	def loudMessage = message.toUpperCase()
}
// 构造顺序由左往右,如果前面已经构造了某个父类,后面子类的该父类不会被重复构造
class D extends B with C 
val d = new D 
println(d.message)  // I'm an instance of class B 
println(d.loudMessage)  // I'M AN INSTANCE OF CLASS B

动态混入特质

class Drawing {
	// this:Type=> 自身类型,表示该类实例化时必须混入相应特质或子特质,self是this的别名
	self:Shape => 
	def start(): Unit = draw()
}
trait Shape {
	def draw(): Unit
}
trait Square extends Shape {
	def draw(): Unit = println("draw a square")
}
trait Triangle extends Shape {
	def draw(): Unit = println("draw a triangle")
}
// 动态混入
(new Drawing() with Square).start()
(new Drawing() with Triangle).start()

特质与抽象类的选择

优先使用特质

  • 抽象类只能继承一次
  • 特质可混入多个

需要使用带参构造方法时,使用抽象类
与Java互操作性

  • 抽象类与Java完全可互操作
  • 特质只有在不包含任何实现代码时才可互操作

内部类

一个类可以作为另一个类的成员,成为内部类
Java内部类是外部类的成员
Scala内部类绑定到外部类的对象实例

class Graph {
	class Node {
		var connectedNodes: List[Node] = Nil
		def connectTo(node: Node) {
			if(connectedNodes.find(node.equals).isEmpty) {
				connectedNodes = node :: connectedNodes
			}
		}
	}
	var nodes: List[Node] = Nil
	def newNode: Node = {
		val res = new Node
		nodes = res :: nodes
		res 
	}
} 
val g:Graph = new Graph
val n1:g.Node = g.newNode
val n2:g.Node = g.newNode
n1.connectTo(n2)   // legel
val h:Graph = new Graph
val n3:h.Node = h.newNode
n1.connectTo(n3)   // illegel  n1与n3被认为是不同的类型
class Graph {
	class Node {
		var connectedNodes: List[Grade#Node] = Nil
		def connectTo(node: Grade#Node) {
			if(connectedNodes.find(node.equals).isEmpty) {
				connectedNodes = node :: connectedNodes
			}
		}
	}
	var nodes: List[Node] = Nil
	def newNode: Node = {
		val res = new Node
		nodes = res :: nodes
		res 
	}
} 

样例类(case class)

样例类常用于描述不可变的值对象(Value Object)

case class Student(name:String,age:Int)   // 定义样例类
val stu = Student("Jason",19)  // 创建样例类的实例,无需new关键字
println(stu.name)  // 访问对象属性

样例类构造参数默认声明为“val”,自动实现类构造参数的getter
样例类构造参数声明为“var”时,自动实现类构造参数的setter和getter
样例类自动创建伴生对象
样例类自动实现的其他方法

  • toString()、equals()、copy()、hashCode()
  • 伴生对象中的apply()、unapply()

unapply()接受一个对象,从对象中提取出相应的值,主要用于模式匹配中

样例类和枚举

枚举(Enumeration)

object Weekday extends Enumeration {
	// 枚举值从0开始计数
	val Mon,Tue,Wed,Thu,Fri,Sat,Sun = Value
}
// 枚举的使用
Weekday.Sun
Weekday.Sun.id  // 获取枚举值的计数值
Weekday.values.foreach(println)

样例类与枚举区别
枚举更简单,代码更少
样例类的字段比枚举的值更强大
样例类可扩展

abstract class Term(code:String)
case class Var(name:String) extends Term(name)
case class Fun(arg:String,body:Term) extends Term(arg)
case class App(f:Term, v:Term) extends Term("App")

样例类与普通类

  • 区别
    • 样例类通常用于描述不可变的数据,数据完全依赖构造参数
      样例类默认不可变,通过模式匹配可分解
      两个样例类“==”操作时,通过按值比较而不是按引用
      样例类操作更简单
  • 最佳实践
    • 如果一个对象在内部执行有状态计算,或者表现出其他类型的复杂行为,那么它应该是一个普通类

泛型类

泛型类指可以接受类型参数的类,泛型类在集合类中被广泛使用
与java不同,定义泛型类使用“[ ]”

class Stack[T]{
	var elements:List[T] = Nil
	def push(x:T){ elements = x :: elements }
	def top: T = elements.head
	def pop() {
		var t = elements.head
		elements = elements.tail
		t 
	}
	def showElements(){
		elements.foreach(x => print(s"$x"))
		println()
	}
}

val ms = new Stack[Int]()
ms.push(10)
ms.showElements()
ms.push(20)
ms.showElements()
val t = ms.pop()
ms.showElements()

类型边界

在Scala中,类型参数可以有一个类型边界约束
类型上界:将类型限制为另一种类型的子类

  • T<:A表示类型变量T应该是类型A的子类
  • A是具体类型,T是泛型

类型下界:将类型声明为另一种类型的超类

  • T>:A表示类型变量T应该是类型A的超类
  • A是具体类型,T是泛型

型变

  • 协变
    在这里插入图片描述
    对于两种类型A和B,如果A是B的子类型,那么Foo[A]就是Foo[B]的子类型
  • 逆变
    在这里插入图片描述
    对于两种类型A和B,如果A是B的子类型,那么Bar[B]就是Bar[A]的子类型
  • 不变
    在这里插入图片描述
    默认情况下,Scala中的泛型类是不变的

包与包对象

Scala包:package包名
只能包含数字、字母、下划线、圆点
不能用数字开头,不能使用关键字
可以在同一个.scala文件中,声明多个并列的package
Scala包对象
包可以包含类、对象和特质,但不能包含变量或方法的定义,应使用包对象解决这个问题

package com.kgc {
	// 对应包com.kgc.scala,每个包都可以有一个包对象
	package object scala {
		val name = "Wow"
	}...
	// 与包对象同名的包可直接使用包对象中定义的变量和方法
	package scala{...}
}

包引用

import让包和包对象的成员可以直接通过名称访问

// 易于访问Fruit
import cn.kgc.Fruit
// 易于访问cn.kgc的所有成员
import cn.kgc._
// 易于访问cn.kgc.Fruits的所有成员
import cn.kgc.Fruits._
// 只引用Apple与Orange,并且Apple重命名为Mclntosh
import cn.kgc.Fruits.{Apple => McIntosh,Orange}

def showFruit(fruit:Fruit){
	import fruit._
	println(name + color)
}

import灵活引用
可以出现在任何地方
可以是对象和包
可以重命名或隐藏一些被引用的成员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值