Scala面向对象
Scala的面向对象思想和Java的面向对象思想和概念是一致的。 但Scala中又补充了更多的功能。
一、包对象
在Scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问。
定义示例:
package object com { val shareValue = "Share" def shareMethod() = {} }
二、导包说明
- 和Java一样,可以在顶部使用import导入,在这个文件中的所有类都可以使用;
- 局部导入:什么时候使用,什么时候导入;(在其作用范围内都可以使用 )
- 通配符导入:import java.util._ ;
- 给类起名:import java.util.{ArrayList=>JL} ;
- 导入相同包的多个类:import java.util.{HashSet, ArrayList} ;
- 屏蔽类:import java.util.{ArrayList =>_,_} ;
- 导入包的绝对路径:new _root_.java.util.HashMap
例如:
//引入com.atguigu包下的所有成员,并将Fruit更名为Shuiguo import com.atguigu.{Fruit=>Shuiguo,_} //引入com.atguigu包下屏蔽Fruit类 import com.atguigu.{Fruit=>_,_} //引入的Java的绝对路径 new _root_.java.util.HashMap
注意:Scala中有三个默认导入的包
import java.lang._
import scala._
import scala.Predef._
三、类
Java:
如果类是public的,则必须和文件名一致。一般,一个.java有一个public类 。
Scala:
Scala中没有public,一个.scala中可以写多个类。 (默认类就是公共的)
Scala中Bean属性(@BeanPropetry),可以自动生成规范的setXxx/getXxx方法,例如:
class Person{ //给属性赋默认值 var name: String = _ //注意:val不能赋默认值 val age: Int = 18 @BeanProperty var sex: String = "男" } object Person { def main(args: Array[String]): Unit = { val person = new Person() println(person.name) person.setSex("女") println(person.getSex) } }
注意:Scala中的public属性,底层实际为private,并通过get方法(obj.field())和set方法(obj.field_=(value))对其进行操作。所以Scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。但由于很多Java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为Scala的属性设置getXXX和setXXX方法(通过@BeanProperty注解实现)。
四、访问权限
在Java中,访问权限分为:public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
- Scala 中属性和方法的默认访问权限为public,但Scala中无public关键字;
- private为私有权限,只在类的内部和伴生对象中可用;
- protected为受保护权限,Scala中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问;
- private[包名]增加包访问权限,包名下的其他类也可以使用 。
五、创建对象
基本格式:
val | var 对象名 [:类型] = new 类型()
注意:
- val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值;
- var修饰对象,可以修改对象的引用和修改对象的属性值;
- 自动推导变量类型不能多态,所以多态需要显示声明。
六、构造器
和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括:主构造器和辅助构造器 。
//这就是主构造器
class Dog(){ var sex: Int = _ var name: String = _ //辅助构造器,可以有多个 //注意:每个辅助构造器都必须以一个对先欠已经定义的其他构造器调用先开始 def this(name: String){ //直接调用主构造器方法 this() println("第一个辅助构造器被调用了") this.name = name } def this(sex: Int,name: String){ //调用第一个辅助构造器 this(name) println("第二个辅助构造器被调用了") this.sex = sex } } object Dog { def main(args: Array[String]): Unit = { var d1 = new Dog("可乐")//这是第一个辅助构造器 var d2 = new Dog(2,"妞妞") //这是第二个辅助构造器 } } 输出: 第一个辅助构造器被调用了 第一个辅助构造器被调用了 第二个辅助构造器被调用了
七、继承和多态
Scala继承一个基类跟Java很相似, 但我们需要注意以下几点:
- 重写一个非抽象方法必须使用override修饰符;
- 只有主构造函数才可以往基类的构造函数里写东西;
- 在子类型重写超类的抽象方法时,不需要使用override关键字。
class Person{ //给属性赋默认值 val name: String = "zhangsan" //注意:val不能赋默认值 val age: Int = 18 @BeanProperty var sex: String = "男" def hello() = { println("这是Person类") } } class Emp extends Person { //重写父类属性 //注意:只能重写 val 属性 override val name: String = "小明" //重写父类方法 override def hello(): Unit = { super.hello() println("这里是子类") } } object Person { def main(args: Array[String]): Unit = { val person = new Person() person.hello() val emp = new Emp() emp.hello() //Scala中的多态 val emp2: Person = new Emp() println(emp2.name) } }
八、抽象类
基本语法:
- 定义抽象类:abstract class Person{} //通过abstract关键字标记抽象类 ;
- 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性 ;
- 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法。
注意事项:
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类 ;
- 重写非抽象方法需要用override修饰,重写抽象方法则可以不加override。;
- 子类中调用父类的方法使用super关键字 ;
- 子类对抽象属性进行实现,父类抽象属性可以用var修饰;
- 子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。
九、匿名子类
和Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。
abstract class Person{ //抽象属性 val name: String //抽象方法 def hello():Unit } object ObjectTest { def main(args: Array[String]): Unit = { val person = new Person { //定义匿名类 override val name: String = "zhangsan" override def hello(): Unit = println("我是匿名类") } } }
十、单例对象(伴生对象)
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
说明:
- 单例对象采用object关键字声明 ;
- 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致;
- 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
//单例类,即半生对象,不可以又参数 object Person{ } //与半生对象同名相对应的半生类 //可以有参数 class Person(){}
十一、Apply方法
说明;
- 通过伴生对象的apply方法,实现不使用new方法创建对象;
- 如果想让主构造器变成私有的,可以在()之前加上private;
- apply方法可以重载;
- Scala中obj(arg)的语句实际是在调用该对象的apply方法,即obj.apply(arg)。用以统一面向对象编程和函数式编程的风格;
- 当使用new关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的apply方法。
//注意:传入的参数名不要和属性名相同,否则程序错误 //如果想让主构造器变成私有的,可以在()之前加上private class Cat private(sName: String){ var name: String = sName } //单例类,即半生对象,不可以又参数 object Cat{ def apply(): Cat = { println("apply 空参被调用") new Cat("xx") } def apply(name: String): Cat = { println("有参 apply被调用") new Cat(name) } } object ObjectTest02 { def main(args: Array[String]): Unit = { //不使用new 创建对象 //使用伴生对象apply方法创建对象,调用无参apply方法 val cat1= Cat() //调用有参apply方法 val cat2 = Cat("xiaomiao") } }
十二、Scala Trait(特征)
Scala语言中,采用trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于Java中的抽象类。
Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
基本语法:
trait 特质名 {
trait主体
}
一 个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
基本语法:
没有父类:class 类名 extends 特质1 with 特质2 with 特质3 …
有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3…
说明:
- 类和特质的关系:使用继承的关系;
- 当一个类去继承特质时,第一个连接词是extends,后面是with;
- 如果一个类在同时继承特质和父类时,应当把父类写在extends后;
- 特质可以同时拥有抽象方法和具体方法;
- 一个类可以混入(mixin)多个特质;
- 所有的Java接口都可以当做Scala特质使用;
- 动态混入:可灵活的扩展类的功能 ;
- 动态混入:创建对象时混入trait,而无需使类混入该trait ;
- 如果混入的trait中有未实现的方法,则需要实现 。
代码示例:
trait PersonTrait{ //创建具体的属性和方法 var name: String = "" def eat() = { println("eat") } //创建抽象的属性和方法 var age: Int def say(): Unit } trait SexTrait{ var sex: String } //一个类可以实现/继承多个特质 //所有的Java接口都可以当做Scala特质使用 class Student extends PersonTrait with java.io.Serializable{ override var age: Int = 16 override def say(): Unit = println("我是一名学生") } object PersonTrait { def main(args: Array[String]): Unit = { val student = new Student() student.say() student.eat() //动态混入trait //只混入该对象,而不会混入该类 val student1 = new Student() with SexTrait{ override var sex: String = "男" } //调用混入的属性 println(student1.sex) } }
由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:
- 第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法;
- 第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。
Trait特质叠加代码示例:会将所有叠加特质输出。
trait A{ def show(): Unit = { println("我是A") } } trait B extends A{ override def show(): Unit = { super.show() println("我是B") } } trait C extends A{ override def show(): Unit = { super.show() println("我是C") } }
//特质叠加的顺序是按照继承的顺序进行叠加的,最后在叠加它们共同的父trait class test extends B with C{ override def show(): Unit = { super.show() println("测试trait叠加") } } object TraitTest { def main(args: Array[String]): Unit = { val test = new test() test.show() } } 输出: 我是A 我是B 我是C 测试trait叠加
如果想要调用特定的混入特质方法,可以增加约束super[],例如:
super[B].show()
特质自身类型还可以实现依赖注入的功能,例如:
class User(val name: String,val age: Int) trait Dao{ def insert(user: User): Unit ={ println("insert into database :" + user.name) } } trait App{ //特质类型实现依赖注入 //该特质属性名字只用一次,因此可以_代替 _: Dao => def login(user: User): Unit ={ println("login:" + user.name) insert(user) } } object TraitTest02 extends App with Dao { def main(args: Array[String]): Unit = { login(new User("zhangsan",18)) } } 输出: login:zhangsan insert into database :zhangsan
十三、类型检查和转换
说明:
- obj.isInstanceOf[T]:判断obj是不是T类型;
- obj.asInstanceOf[T]:将obj强转成T类型;
- classOf获取对象的类名。
示例:
def main(args: Array[String]): Unit = { val user = new User("zhangsan",18) //1、判断对象是否是某个类型的实例 val bool = user.isInstanceOf[User] println(bool) //2、将对象转换为某个类型的实例 val user1 = user.asInstanceOf[User] println(user1) //3、获取对象的类名 val value = classOf[User] println(value) //4、Scala中的反射用法和java一致 val clazz = user.getClass println(clazz) }
十四、枚举类和应用类
说明:
- 枚举类:需要继承Enumeration ;
- 应用类:需要继承App 。
代码示例:
//应用类 object A extends App { println("这是应用类") } //枚举类 object Color extends Enumeration{ val RED = Value(1,"red") val BLUE = Value(2,"blue") val GREEN = Value(3,"green") }
十五、Type定义新类型(类型别名)
def main(args: Array[String]): Unit = { //定义一个S类型 type S = String var name: S = "hello" println(name) }
十六、样例类
样例类首先是类,除此之外它是为模式匹配而优化的类,样例类用case关键字进行声明。例如:
case class Student(name:Stirng,age:Int,city:Sting)
样例类有以下几个特点:
- 实现了apply方法,使得该类可以不需要new;
- 自动实现了equals、hashCode、toString等方法;
- 默认是可以序列化的,也就是实现了Serializable;
- 构造函数的参数是public级别的,我们可以直接访问;
- 支持模式匹配,而普通的类是不能直接进行模式匹配的。
//定义样例类 case class Teacher(name: String, age: Int) //样例类的模式匹配 //不用new声明对象 def matchTest04(x: Teacher) = x match { case Teacher(name, age) if age < 30 => println("young") case Teacher(name, age) if age >= 30 => println("old") } //不需要提前new 对象 matchTest04(Teacher("王老师",29)) //-->young matchTest04(Teacher("李老师",31)) //-->old