Scala 面向对象编程学习
Scala是一个完全面向对象的语言
package(Scala中的包)
Scala中的包声明方式默认和java是一致的。但是有其他的使用方式
- 在同一个源码文件,可以多次声明包(但是声明的类在最后的那个包中)
源码中的类所在的位置不需要和包路径相同
//第一种方式
package o.l.scala.Functions
package package1
class User {
var name: String= _
var age:Int=_
def UserString(): String ={
"name:"+this.name+"\t"+"age="+this.age
}
}
//第二种方式
package o.l.scala.Functions
package package1{
class User {
var name: String= _
var age:Int=_
def UserString(): String ={
"name:"+this.name+"\t"+"age="+this.age
}
}
}
上述这两种方式是一样的效果,
在其他类中使用User类的时候,需要导包:import o.l.scala.Functions.package1.User
package o.l.scala.Functions
package package1{
class User {
var name: String= _
var age:Int=_
def UserString(): String ={
"name:"+this.name+"\t"+"age="+this.age
}
}
}
class User2{
var name: String= _
var age:Int=_
def UserString(): String ={
"name:"+this.name+"\t"+"age="+this.age
}
如果是这种情况,User类是在包o.l.scala.Functions.package1中,而User2类是在包o.l.scala.Functions中,使用时请注意
- Scala中所有的语法都可以嵌套
package可以使用{ },{ }内声明的类在这个包中,之外声明的类不在这个包中 - scala中可以声明父包和子包,父包中的类,子包可以直接访问,不需要引入,其实就是作用域的概念
package package1 {
class User {
var name: String = _
var age: Int = _
def UserString(): String = {
"name:" + this.name + "\t" + "age=" + this.age
}
}
package package1_1 {
class User2 {
var name: String = _
var age: Int = _
def UserString(): String = {
"name:" + this.name + "\t" + "age=" + this.age
}
private val user: User = new User()
}
}
}
- scala中package中可以声明类,但是无法声明变量和函数(方法)
- scala为了弥补包的不足,使用了包对象的概念(package object),其包对象中就可以声明属性和方法。
package object package2{
var name:String = null
def getname(): String ={
name
}
}
import 说明
- import 用于导类
- import 可以在任意的地方使用
- import 可以导入一个包中的所有的类,采用_ 下划线代替java中的*(import java.util._)
- import 导入相同包中的多个类,采用大括号进行包含处理
import java.util.{ArrayList,List,Date} - import 可以采用特殊的方式来隐藏指定的类,{类名=>_}
- import 可以导包(完善Java中的import只能导类的局限性)
import java.util
关于scala中的包的特别说明
先看如下代码:
package o.l.scala.Functions
import java.util.HashMap
package java{
package util{
class HashMap{
}
object Main{
def main(args: Array[String]): Unit = {
//TODO 此处的HashMap是本包下的HashMap类
val map: HashMap = new HashMap()
println(map)
//TODO 使用 _root_ 可以从最开始的包中查找类,即可以增加绝对路径
val hashMap = new _root_.java.util.HashMap()
println(hashMap)
}
}
}
}
结果:
o.l.scala.Functions.java.util.HashMap@763d9750
{}
Process finished with exit code 0
因此这里需要注意一下,在使用import时_root_可以指定原始包中的类
- import 导类的时候,可以给类起别名
import root.java.util.{HashMap=>javaHashMap}
scala中隐式导入的一些类和对象
- java.lang._
- scala._
- scala.Predef._
类的属性
class User06{
//声明属性
//TODO scala中给类声明属性,默认为私有的,但是底层提供了公共的getter和setter的方法
var username:String = _
//TODO 如果给属性增加private修饰符,那么属性无法在外部访问,因为底层生成的setter和getter方法都是私有的
private var age:Int = _
//TODO 如果声明的属性使用val,那么属性是私有的,并且底层使用final修饰,底层只有getter方法,而没有setter方法
val email:String = "16@163.com"
}
为了能够和java bean规范统一,scala提供了注解,生成java中对应的set,get方法
@BeanProperty var address :String = _
package o.l.scala.Functions
import scala.beans.BeanProperty
class Student1 {
@BeanProperty var name:String = _
@BeanProperty var age:Int=_
}
object Test{
def main(args: Array[String]): Unit = {
val student = new Student1()
student.setName("zhangsan")
student.setAge(20)
println(student.getName)
println(student.getAge)
}
}
结果:
zhangsan
20
Process finished with exit code 0
访问修饰符
- public(默认的访问权限, 没有关键字)
- protected(访问权限只能子类访问,同包访问不了)
- private(私有访问权限,只能当前类访问)
- default(package):包访问权限需要特殊的语法规则:private[包名]
package p1 {
package p2 {
class User(){
var username="张三"
private var password = "123456789"
protected var email = "1564@163.com"
private[p2] var address = "XXXXXX"
}
//TODO 包级别访问,同包下直接能够访问
class Emp2{
def test(): Unit ={
val user = new User()
println(user.username)
println(user.address)
}
}
}
package p3{
import o.l.scala.Functions.p1.p2.User
// TODO 默认公共属性访问
class Emp{
def test(): Unit ={
val user: User = new User()
println(user.username)
}
}
//TODO protected权限访问
class Emp2 extends User{
def test(): Unit ={
println(this.email)
}
}
}
}
伴生类和伴生对象
scala 中没有static关键字,如果想要实现static的效果,就可以通过伴生对象来实现
在同一个scala文件中定义一个类,同时定义一个同名的object,那么它们就是伴生类和伴生对象的关系,可以互相直接访问私有的field。
package o.l.scala.Functions
//TODO 伴生类 (相当于成员)
class Student {
private val sname = "张三"
}
//TODO 伴生对象 (相当于静态)
object Student{
//TODO 使用伴生对象创建伴生类的对象,以实现通过类名. 可以直接调用类中的成员
def apply(name: String): Student = new Student()
def test(): String ={
Student.test()
}
}
val student = Student("张三")
apply()方法在访问Student时会自动被调用,调用伴生对象时是不需要new 伴生类的
伴生类和伴生对象的一个应用(静态工厂)
import scala.collection.mutable
// TODO 伴生类 伴生对象的应用:实现一个静态工厂
object StaticFactory {
def main(args: Array[String]): Unit = {
println(Human.getHuman("黑色"))
println(Human.getHuman("黑色"))
println(Human.getHuman("白色"))
println(Human.getHuman("黄色"))
println(Human.getHuman("黄色"))
}
}
// TODO 工厂中有一个仓库, 如果创建一个新的对象时,如果仓库里面有,就直接返回仓库中的对象
// TODO 如果仓库中没有,那么就创建一个新的对象,并将新的对象放入到仓库中
object Human {
private val map: mutable.Map[String, Human] = mutable.Map[String, Human](
"黑色" -> new Human("黑色"),
"白色" -> new Human("白色")
)
def getHuman(color: String): Human = map.getOrElseUpdate(color, new Human(color))
}
//TODO 私有化主构造器,防止外部创建对象
class Human private(color: String) {
println(s"$color.......")
override def toString: String = s"人种: $color"
}
结果:
黑色.......
白色.......
人种: 黑色
人种: 黑色
人种: 白色
黄色.......
人种: 黄色
人种: 黄色
这里需要注意的是,工厂中存在的资源只有一份,即上述两个 人种:黑色 是同一个对象。
类的方法
所谓的方法,其实就是类中声明的函数,所以声明方式和函数是一样的,调用方式上有一些区别
构造方法(主构造方法 & 辅助构造方法)
Scala的构造方法分为两类;主构造方法 & 辅助构造方法
Scala构建对象可以通过辅助构造方法创建,但是必须调用主构造方法
Scala是完全面向函数的语言,所以类也是函数
类是函数,可以使用小括号作为函数的参数列表
类所代表的的函数其实就是这个类的构造方法
默认情况下,scala也是给类提供无参构造方法,所以小括号可以省略
在类的后面声明的构造方法就是主构造方法
在主构造方法中声明的构造方法就是辅助构造方法
在函数式编程中,调用的函数需要定义在之前,先定义后才能被调用,注意顺序。
//TODO 主构造方法
class Scala09_Construction(s: String) {
//TODO 这里是类体 & 也是构造方法体
println("主构造方法")
println(s)
// TODO 这里是辅助构造方法,辅助构造方法可以定义多个,但是参数列表不能相同
// TODO 辅助构造方法中必须调用主构造方法
def this(s:String, ss:String){
this(s)
println("辅助构造方法2")
}
def this(){
this("辅助构造方法1", "XXXXXXXXX")
}
}
object test{
def main(args: Array[String]): Unit = {
new Scala09_Construction()
}
}
结果:
主构造方法
辅助构造方法1
辅助构造方法2
Process finished with exit code 0
继承(extends)
使用extends关键字进行类之间的继承
java中的动态绑定机制
成员方法在执行过程中,JVM会将方法和当前调用对象实际内存进行绑定
属性没有动态绑定机制,属性在哪里声明在哪里使用
class AAA {
public int i = 10;
public int getResult(){
return i+10;
}
}
class BBB extends AAA{
public int i = 20;
public int getResult(){
return i+20;
}
}
//TODO 动态绑定机制
public class Java_RTTI {
public static void main(String[] args) {
AAA aaa = new BBB();
System.out.println(aaa.getResult());
}
}
结果:40
class AAA {
public int i = 10;
public int getResult(){
return i+10;
}
}
class BBB extends AAA{
public int i = 20;
}
//TODO 动态绑定机制
public class Java_RTTI {
public static void main(String[] args) {
AAA aaa = new BBB();
System.out.println(aaa.getResult());
}
}
结果:20
class AAA {
public int i = 10;
public int getResult(){
return getI()+10;
}
public int getI(){
return i;
}
}
class BBB extends AAA{
public int i = 20;
public int getI(){
return i;
}
}
//TODO 动态绑定机制
public class Java_RTTI {
public static void main(String[] args) {
AAA aaa = new BBB();
System.out.println(aaa.getResult());
}
}
结果:30
Scala中的动态绑定机制
scala中的属性和方法都是动态绑定的,而Java中只有方法是动态绑定的
Scala中属性也可以重写,因为属性可以抽象
属性只有声明,没有初始化,那么就是抽象属性
抽象属性在编译为class文件时,不产生属性,但是产生抽象getter方法。
scala 虽然可以重写属性,但是不能重写父类var声明的已经初始化的属性,可以重写val声明的已经初始化的属性。
//抽象类
abstract class Person{
//TODO 抽象属性
var age:Int
}
class Emp extends Person{
//TODO 重写抽象属性
override var age: Int = 20
}
object Scala10_Extends {
def main(args: Array[String]): Unit = {
println(new Emp().age)
}
}
如果父类var声明的已经初始化的属性在子类中重写就会出现错误:
改成val 之后的运行结果:
父类构造方法(构造方法执行顺序)
class A1A(s:String){
println("A1A主构造方法="+s)
}
class B1B(s:String, s2:String) extends A1A(s){
println("B1B柱构造方法="+s2)
def this(name:String){
this(name, "B1B手工参数")
println("B1B 辅助构造函数")
}
}
object test{
def main(args: Array[String]): Unit = {
new B1B("A1A手工传入参数")
}
}
结果:
A1A主构造方法=A1A手工传入参数
B1B柱构造方法=B1B手工参数
B1B 辅助构造函数
Process finished with exit code 0
类构造方法的参数的作用域默认为整个类的主体,但是如果想要参数作为属性来使用,可以采用特殊方式来声明。
class user1(var name:String){
特质(trait)(Scala中没有接口)
Scala中没有接口的概念,但是scala中有特质Trait
- 特质是可以继承的,所以使用extends关键字
- 如果类继承多个特质, 采用with关键字
- 如果类同时存在父类和特质,依然采用extends,但是继承的是父类,用with连接(混入)特质
Java中的接口与Scala中的特质对比
Java中的接口(interface)与Scala中的特质(trait)
-
java中的接口无法直接构建对象,必须使用实现类
-
java中的接口是可以声明方法的,早期版本中声明的方法都是抽象,新版本的是可以有默认实现的
-
java中的接口是可以声明属性的,属性值无法修改(静态的)
-
scala中的特质也是无法直接构建对象
-
scala中的特质是可以执行代码的
object Trait {
def main(args: Array[String]): Unit = {
new MySql()
}
}
trait Operate{
println("operate........")
}
class MySql extends Operate{
}
结果:
operate........
Process finished with exit code 0
scala中的特质声明的属性和方法都可以在混入的类中调用
object Trait {
def main(args: Array[String]): Unit = {
new MySql().insert()
}
}
trait Operate{
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
class MySql extends Operate{
}
结果:
operate........
插入数据
Process finished with exit code 0
scala特质中声明的属性值可以修改
object Trait {
def main(args: Array[String]): Unit = {
val sql: MySql = new MySql()
sql.trait_name = "operate_trait"
print(sql.trait_name)
}
}
trait Operate{
var trait_name:String = _
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
class MySql extends Operate{
}
结果:
operate........
operate_trait
Process finished with exit code 0
特质也可以继承其他的特质
object Trait {
def main(args: Array[String]): Unit = {
val sql: MySql = new MySql()
sql.insert()
}
}
trait Operate{
var trait_name:String = _
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
trait DB extends Operate{
override def insert(): Unit ={
print("向数据库")
super.insert()
}
}
结果:
operate........
向数据库插入数据
Process finished with exit code 0
特质和父类没有关系,只和当前混入的类有关系,所以,在调用时,父类先执行,然后当前混入类的特质再执行,然后当前类再执行
如果父类混入了相同的特质,那么特质的代码只会执行一遍
object Trait {
def main(args: Array[String]): Unit = {
val sql: MySql = new MySql()
}
}
trait Operate{
var trait_name:String = _
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
trait DB extends Operate{
println("DB........")
override def insert(): Unit ={
print("向数据库")
super.insert()
}
}
class MySql extends DB with Operate {
println("MySql.........")
}
结果:
operate........
DB........
MySql.........
Process finished with exit code 0
多特质混入时,代码执行顺序为从左到右,如果有父特质,会优先执行
object Trait {
def main(args: Array[String]): Unit = {
val sql: MySql = new MySql()
}
}
trait Operate{
var trait_name:String = _
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
trait DB extends Operate{
println("DB........")
override def insert(): Unit ={
print("向数据库")
super.insert()
}
}
trait File extends Operate{
println("File........")
override def insert(): Unit ={
print("向文件")
super.insert()
}
}
结果:
operate........
DB........
File........
MySql.........
Process finished with exit code 0
多特质混入时,同名方法的调用顺序为从右往左
object Trait {
def main(args: Array[String]): Unit = {
val sql: MySql = new MySql()
val sql2: MySql2 = new MySql2()
sql.insert()
sql2.insert()
}
}
trait Operate{
var trait_name:String = _
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
trait DB extends Operate{
println("DB........")
override def insert(): Unit ={
print("向数据库")
super.insert()
}
}
trait File extends Operate{
println("File........")
override def insert(): Unit ={
print("向文件")
super.insert()
}
}
class MySql extends DB with File {
println("MySql.........")
}
class MySql2 extends File with DB {
println("MySql.........")
}
结果:
operate........
DB........
File........
MySql.........
operate........
File........
DB........
MySql.........
向文件向数据库插入数据
向数据库向文件插入数据
Process finished with exit code 0
从结果可以得出,super关键字在特质中调用是上一级特质中的方法/*class MySql extends DB with File*/
如果需要调用直接父类特质的方法,需要特殊的声明方式:super[trait].insert()
特质中super关键字不是指代父特质,而是指代上一级特质
如果希望super关键字指向父类特质,需要增加特殊操作:super[特质]
object Trait {
def main(args: Array[String]): Unit = {
val sql: MySql = new MySql()
sql.insert()
// val sql2: MySql2 = new MySql2()
// sql2.insert()
}
}
trait Operate{
var trait_name:String = _
println("operate........")
def insert(): Unit ={
println("插入数据")
}
}
trait DB extends Operate{
println("DB........")
override def insert(): Unit ={
print("向数据库")
super[Operate].insert()
}
}
trait File extends Operate{
println("File........")
override def insert(): Unit ={
print("向文件")
super[Operate].insert()
}
}
class MySql extends DB with File {
println("MySql.........")
}
class MySql2 extends File with DB {
println("MySql.........")
}
结果:
operate........
DB........
File........
MySql.........
向文件插入数据
Process finished with exit code 0
java的接口可以在scala中当成特质来使用
class MySql2 extends File with DB with Serializable{
println("MySql.........")
}
特质的动态混入(可灵活的扩展类的功能)
动态混入:创建对象时可以混入trait,而无需使类混入该trait
如果混入的trait中有未实现的方法,则需要实现
object Trait {
def main(args: Array[String]): Unit = {
// val sql: MySql = new MySql()
// sql.insert()
// val sql2: MySql2 = new MySql2()
// sql2.insert()
//TODO trait动态混入
val teacher: Teacher with SexTrait = new Teacher() with SexTrait {
override var sex: String = "女"
}
teacher.eat()
teacher.say()
println(teacher.sex)
}
}
trait PersonTrait{
var name:String = _
var age:Int
def eat(): Unit ={
println("eat...")
}
def say(): Unit
}
trait SexTrait{
var sex:String
}
class Teacher extends PersonTrait with java.io.Serializable{
override var age: Int = _
override def say(): Unit = {
println("say()....")
}
}
特质可以继承类
特质可以继承一个普通类,继承后,在特质中可以使用该类开放(可以被子类访问的那些)的那些属性和方法
trait Operate15 extends Exception{
this.getMessage()
}
特质叠加
由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。
冲突分为以下两种:
- 第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
- 第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。
所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来
trait Ball {
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.describe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.describe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"my ball is a " + super.describe()
}
}
object TestTrait {
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
运行结果:
特质叠加执行顺序
思考:上述案例中的super.describe()调用的是父trait中的方法吗?
当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法。,排序规则如下:
(1)案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball。
(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],super[Category].describe()。
trait特质自身类型:指定trait特质使用的范围(可实现依赖注入)
trait的自身类型可以限定trait混入类的范围
例如:
//TODO 指定特质使用的范围
trait Operate19{
// 这里的this 可以换成任意的合法名称, 然后通过这个名称可以访问Exception中的成员。
this:Exception =>
def insert(): Unit ={
print("插入数据")
this.getMessage()
}
}
这个特质的自身类型是:Exception类
如果某个类想要继承这个特质,这个类就必须先继承 Exception这个类
class MySql3 extends Exception with Operate19 {
println("MySql.........")
}
扩展
类型检查&类型转换&类信息
- obj.isInstanceOf[T]:判断obj是不是T类型
- obj.asInstanceOf[T]:将obj强转成T类型
- classOf[类]:获取类的信息
object Instanceof extends App {
def test(o: Object){
val bool: Boolean = o.isInstanceOf[AA2]
if(bool){
val aa: AA2 = o.asInstanceOf[AA2]
aa.p()
}
}
test(new AA2())
private val value: Class[AA2] = classOf[AA2]
print(value)
}
class AA2{
def p(): Unit ={
print("..........")
}
}
结果:
..........class o.l.scala.Functions.AA2
Process finished with exit code 0
枚举类和应用类
object Enum extends App {
print(Color1.RED)
}
object Color1 extends Enumeration{
val RED: Value = Value(1, "red")
val BLUE: Value = Value(2, "blue")
val GREEN: Value = Value(3, "green")
}
结果:
red
Process finished with exit code 0
注意:枚举类是 伴生对象
对于继承App类的伴生对象与伴生对象中定义main方法,都可以作为程序的入口函数,都可以执行。
type 定义新类型(给类起别名)
type S=String
val name:S = "zhangsan"
sealed关键修饰的类(密封类)
用sealed关键字修饰的类叫做密封类
密封类的子类只能出现在 父类的文件中,在这个文件之外无法定义密封类的子类