包
scala包的作用:
(1)区分相同名字的类
(2)当类很多时,可以很好的管理类
(3)控制访问范围
包的命名
命名规则
只能包含数字、字母、下划线、小圆点.,但不能用数字开头,也不要使用关键字。
命名规范
一般是小写字母+小圆点
com.公司名.项目名.业务模块名
包的管理
Scala有两种包的管理风格。一种方式是每个源文件一个包(包名和源文件所在路径不要求必须一致),包名用“”进行分隔以表示包的层级关系,如com.heria.scala
。
另一种风格,通过嵌套的风格表示层级关系
import com.heria.scala.Inner
package com{
object Outer {
var out:String="out"
def main(args: Array[String]): Unit = {
println(Inner)
}
}
package heria{
package scala{
//内层包中定义单类对象
object Inner{
var in:String = "in"
def main(args: Array[String]): Unit = {
println(Outer.out)//out
Outer.out="Outer"
println(Outer.out)//Outer
}
}
}
}
}
该风格具有两个特点:
1)一个源文件中可以声明多个package
(2)子包中的类可以直接访问父包中的内容,而无需导包
导入包
import com.heria.scala | 引入com.heria包下scala ( class和object ) |
---|---|
import com.heria._ | 引入com.heria下的所有成员 |
import com.heria.scala_ | 引入scala(object)下的所有成员 |
import com.heria.{sca,la} | 引入com.heria下的sca和la |
import com.heria.{sca=>scala} | 引入com.heria下的sca并更名为scala |
import com.heria.{sca=>scala,_} | 引入com.heria下的所有成员,并将sca更名为scala |
import com.heria.{scala=>,} | 引入com.heria包下屏蔽scala类 |
new_root_.java.util.HashMap | 引入Java的绝对路径 |
包对象
在scala中可以为每个包定义一个同名的包对象,包对象的名字需要和该包下的子包一样。在包对象中可以定义变量,方法。在包对象中定义的变量和方法,就可以在对应的包中的任何类中访问、使用。在底层这个包对象会生成两个类 package.class
和 package$.class
。
idea在一个包下可以直接创建package object文件
案例实操
新建名为chapter08的包,新建package object文件,创建一个常量一个方法用于测试
package object chapter08 {
val commonValue:String = "heria"
def commonMethod():Unit = {
println("This is a test")
}
}
在同一个包下新建一个名为Test01的object,可以看到Test01可以直接调用属性和方法
object Test01 {
def main(args: Array[String]): Unit = {
println(commonValue)//heria
commonMethod()//This is a test
}
}
注意:包对象申明时必须要和当前这个包的定义在同一层级下
类和对象
类是对象的抽象,可以看成一类事物的模板,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
基本和语法
[修饰符] class 类名{
类体
}
(1) Scala语法中,类修饰符可以省略,默认为public
(2)一个scala源文件可以包含多个类
案例实操
object Test01 {
def main(args: Array[String]): Unit = {
//创建一个对象
val student = new Student()
//student.name //error,不能访问private属性
println(student.age)//0
println(student.sex)//null
student.sex="female"
println(student.sex)//female
}
}
//创建类
class Student{
private var name:String ="alice"
@BeanProperty
var age:Int= _
var sex:String = _
}
封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
java封装操作如下:(1)将属性进行私有化(2)提供一个公共的set方法,用于对属性赋值(3)提供一个公共的get方法,用于获取属性的值
Scala中的public属性,底层实际为private,并通过get方法(obj.field()
)和set方法(obj.field_=(value)
)对其进行操作。因此Scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。但由于很多Java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为Scala的属性设置getXXX和setXXX方法(通过@BeanProperty
注解实现)。
访问权限
(1) scala中属性和方法的默认访问权限为 public,但Scala中无public关键字。
(2) private为私有权限,只在类的内部和伴生对象中可用。
(3) protected 为受保护权限,Scala 中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问。
(4) private[包名]增加包访问权限,包名下的其他类也可以使用
案例实操
在同一包下新建两个object,一个为Test04_access,一个为Test04_classForAccess
Test04_classForAccess
package chapter08
object Test04_classForAccess {
}
//定义一个父类
class Person{
private var idCard:String = "3525" //私有
protected var name:String="Alice" //受保护
var sex:String="female"
private[chapter06] var age : Int = 18
def printInfo(): Unit = {
println(s"Person: $idCard $name $sex $age")
}
}
Test04_access
package chapter08
object Test04_access {
def main(args: Array[String]): Unit ={
//创建对象
val person: Person = new Person()
//person.idCard //error,私有属性只在自己类的内部和伴生对象中可用
//person.name //error,同类、子类可以访问,同包无法访问
println(person.age)
println(person.sex)
}
}
class Worker extends Person{
println("worker:")
//println(idCard) //私有属性只在自己类的内部和伴生对象中可用
name="bob"
age=25
sex="male"
}
构造器
scala提供主构造器和任意数量的辅助构造器。
基本语法
class 类名(形参列表) { // 主构造器
// 类体1
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
注意细节:
1.主构造器
(1)主构造器的声明直接放置于类名之后
(2)主构造器会执行类定义中的所有语句,这里可以体会到 Scala 的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别
(3)如果想让主构造器变成私有的,可以在()之前加上 private,这样用户只能通过辅助构造器来构造对象了
Scala 构造器作用是完成对新对象的初始化,构造器没有返回值。
2.辅助构造器
(1)辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数及类型来区分。
(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
案例实操
package chapter08
object Test05_Constructor {
def main(args: Array[String]): Unit = {
val student1 = new Student()
student1.Student()
//主构造方法被调用
//一般方法被调用
val student2 = new Student("alice")
//主构造方法被调用
//辅助构造器一被调用
//name:alice age: 0
val student3 = new Student("alice",25)
//主构造方法被调用
//辅助构造器一被调用
//name:alice age: 0
//辅助构造方法二被调用
//name:alice age: 25
}
}
//定义一个类
class Student{
//定义属性
var name:String= _
var age:Int= _
println("主构造方法被调用")
//声明辅助构造方法
//辅助构造器一
def this(name:String){
this() //直接调用主构造器
println("辅助构造器一被调用")
this.name=name
println(s"name:$name age: $age")
}
//辅助构造器二
def this(name : String,age: Int) {
this(name)
println("辅助构造方法二被调用")
this.age= age
println(s"name:$name age: $age")
}
//一般方法
def Student():Unit={
println("一般方法被调用")
}
}
3.参数
单独定义属性的形式
object Test06_ConstructorParams {
def main(args: Array[String]): Unit = {
val student2 = new Student2
println(s"student2:name=${student2.name},age=${student2.age}")
}
}
//定义类
//无参构造器
class Student2{
//单独定义属性
var name:String = _
var age:Int = _
}
(1)如果参数使用 var 关键字声明,那么 Scala 会将参数作为类的成员属性使用,并会提供属性对应的 xxx()[类似 getter]/xxx_$eq()[类似 setter]方法,即这时的成员属性是私有的,但是可读写。
传参构造器
object Test06_ConstructorParams {
def main(args: Array[String]): Unit = {
val student3 = new Student3("bob",23)
println(s"student3:name=${student3.name},age=${student3.age}")
}
}
//定义类
//传参构造器
class Student3(var name:String,var age:Int)
(2)Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量,无法在主函数中直接打印,只能在构造器中
提供printInfo
方法
object Test06_ConstructorParams {
def main(args: Array[String]): Unit = {
val student4 = new Student4("bob",23)
student4.printInfo()
}
}
//定义类
//传参构造器
class Student4(name:String,age:Int){
def printInfo(){
println(s"student4:name=${name},age=$age")
}
}
(3)构造器参数用val关键字声明,那么 Scala 会将参数作为类的私有的只读属性使用,无法再做更改
继承和多态
继承
基本语法:class 子类名 extends 父类名 (类体)
(1)子类继承父类的属性和方法
(2) scala是单继承
object Test07_inheritance {
def main(args: Array[String]): Unit = {
val student1 = new Student7("alice",18)
//1.父类的主构造器调用
//3.子类的主构造器调用
println("--------------")
val student2 = new Student7("alice",18,"std001")
//1.父类的主构造器调用
//3.子类的主构造器调用
//4.子类的辅助构造器调用
}
}
class Person7(){
var name:String= _
var age:Int = _
println("1.父类的主构造器调用")
def this(name:String,age:Int){
this()
println("2.父类的辅助构造器调用")
this.name = name
this.age = age
}
def printInfo():Unit = {
println(s"Person:$name $age")
}
}
class Student7(name:String,age:Int) extends Person7{
var stdNo:String = _
println("3.子类的主构造器调用")
def this(name:String,age:Int,stdNo:String){
this(name,age)
println("4.子类的辅助构造器调用")
this.stdNo=stdNo
}
override def printInfo(): Unit = {
println(s"Person:$name $age $stdNo")
}
}
val student1 = new Student7("alice",18)
调用了子类的主构造器,但前提是先调用父类的主构造器,因此顺序是1,3
val student2 = new Student7("alice",18,"std001")
调用了子类的辅助构造器,由于this(name,age)
,需要先调用子类的主构造器,才能打印4.子类的辅助构造器调用
这句话,而调用子类的主构造器前要先调用父类的主构造器,因此顺序是1,3,4
多态
多态也叫"动态绑定",指在执行期间判断所引用对象的实际类型,根据其实际类型调用其相应的方法。(编译期间不绑定方法,而是在运行时判断是对象是哪个类的实例,在调用该类方法)
跟java的多态的区别是:java静态绑定属性,动态绑定方法;scala的属性和方法全部实现动态绑定
案例实操
object Test08_DynamicBind {
def main(args: Array[String]): Unit = {
val student:Person8 = new Student8
println(student.name)//student
student.hello()//hello student
}
}
class Person8{
val name:String = "person"
def hello():Unit = {
println("hello person")
}
}
class Student8 extends Person8 {
override val name:String = "person"
override def hello():Unit = {
println("hello person")
}
}
抽象类
在Scala中一个被abstract
修饰的类,类中包含没有实现的成员,即对象没有初始值,定义的方法没有方法体,则该类就是抽象类。
基本语法
// 定义抽象类
abstract class 抽象类名 {
// 定义抽象字段
val 抽象字段名:类型
// 定义抽象方法
def 方法名(参数:参数类型,参数:参数类型…):返回类型
}
继承和重写
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明
为抽象类
(2)重写非抽象方法需要用override修饰。重写抽象方法则可以不加override。(3)子类中调用父类的方法使用super关键字
(4)子类对抽象属性进行实现,父类抽象属性可以用var修饰;子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。因为var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写
object Test09_AbstractClass {
def main(args: Array[String]): Unit = {
}
}
//定义抽象类
abstract class Person9{
//非抽象属性
val name:String = "Person"
//抽象属性
var age:Int
//非抽象方法
def eat():Unit={
println("person eat")
}
//抽象方法
def sleep():Unit
}
//定义具体实现的子类
class Student9 extends Person9{
var age:Int = 18
def sleep():Unit={
println("student sleep")
}
//重写非抽象属性和方法
override def eat(): Unit = {
println("student eat")
}
}
单例对象
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在scala中没有静态的概念,没有static
关键字)。但是为了能够和Java语言交互,产生了一种特殊的对象来模拟这类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
语法
object 伴生对象名称(必须跟伴生类名称一致){
//定义静态属性和方法
}
说明
(1))单例对象采用object关键字声明
(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
(3)单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
案例实操1
还是Student
类的例子,输出学生的姓名,年龄和学校。考虑到学校相同,是个静态属性学校不需要定义在类的属性里了,可以定义在伴生对象中。
object Test11_Object {
def main(args: Array[String]): Unit = {
val student = new Student11("alice",18)
student.printerInfo()
}
}
//定义类
class Student11(val name:String,age:Int){
def printerInfo(): Unit ={
println(s"student:name = $name,age = $age,school = ${Student11.school}")
}
}
object Student11{
val school:String = "school11"
}
案例实操2
在实际项目中,我们会采用工厂方法模式(FACTORY METHOD)编程。我们不希望直接创建类实例,可以采用将构造器私有化,要想调用方法,在伴生对象中新建工厂方法创建出来。
object Test11_Object {
def main(args: Array[String]): Unit = {
//val student = new Student11("alice",18)
val student = Student11.newStudent("alice",18)
student.printerInfo()
}
}
//定义类
class Student11 private(val name:String,age:Int){
def printerInfo(): Unit ={
println(s"student:name = $name,age = $age,school = ${Student11.school}")
}
}
object Student11{
val school:String = "school11"
//创建类的对象实例的创建方法
def newStudent(name:String,age:Int):Student11 = new Student11(name,age)
}
伴生对象中,也可以定义apply
方法,在调用伴生对象的apply方法时可以省略apply
object Test11_Object {
def main(args: Array[String]): Unit = {
//val student = new Student11("alice",18)
val student = Student11("alice",18)
student.printerInfo()
}
}
//定义类
class Student11 private(val name:String,age:Int){
def printerInfo(): Unit ={
println(s"student:name = $name,age = $age,school = ${Student11.school}")
}
}
object Student11{
val school:String = "school11"
//创建类的对象实例的创建方法
def apply(name:String,age:Int):Student11 = new Student11(name,age)
}
单例设计模式
一个类只能有一个实例,并提供对该实例的全局访问点。通俗地说,就是一个类只能创建一个对象,并且在程序的任何地方都能够访问到该对象。
如下代码new出来的Student11
对象实例全局只有一份,可以创建student1和student2,通过println( student1)
和println( student2)
代码打印出的地址是相同的
object Test11_Object {
def main(args: Array[String]): Unit = {
//val student = new Student11("alice",18)
val student1 = Student11.getInstance
student1.printerInfo()
val student2 = Student11.getInstance
student2.printerInfo()
println( student1)
println( student2)
}
}
//定义类
class Student11 private(val name:String,age:Int){
def printerInfo(): Unit ={
println(s"student:name = $name,age = $age,school = ${Student11.school}")
}
}
//饿汉式
//object Student11{
// val school:String = "heria"
// private val student:Student11 = new Student11("alice",18)
//
// def getInstance:Student11 = student
//}
object Student11{
private var student:Student11 = _
def getInstance():Student11 = {
if(student==null){
student = new Student11("alice",18)
}
student
}
}
特质
Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同
的特质(特征)时,就可以将这个特质(特征)独立出来,采用关健字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可
以混入(mixin)多个特质。这种感觉类似于Java中的抽象类。
Scala 引入trait
特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
基本语法
trait 特质{
trait 主体
}
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,
所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with
关键字连接。
说明
(1)类和特质的关系:使用继承的关系。
(2)当一个类去继承特质时,第一个连接词是extends,后面是with。
(3)如果一个类在同时继承特质和父类时,应当把父类写在extends后。
//没有父类
class 类名 extends 特质1 with 特质2 with特质3 ...
//有父类
class 类名 extends 父类 with 特质1 with 特质2 with特质3 ...
案例实操
object Test13_Trait {
def main(args: Array[String]): Unit = {
val student = new Student13
student.sayHello()
// hello from:heria
// hello from :student heria
student.study()//student heria is studying
student.play()//young people can play
}
}
class Person13{
val name:String = "person"
var age:Int = 18
def sayHello():Unit={
println("hello from:" +name)
}
}
//定义特质
trait Young{
//声明抽象和非抽象属性
var age:Int
val name:String = "young"
def play():Unit = {
println("young people can play")
}
def dating():Unit
}
class Student13 extends Person13 with Young{
//重写冲突的属性
override val name: String ="heria"
def dating():Unit = println(s"student $name is dating")
def study():Unit = println(s"student $name is studying")
//重写父类方法
override def sayHello(): Unit = {
super.sayHello()
println(s"hello from :student $name")
}
}
特质混入
还是以上一个学生的例子为例,我们不希望在定义某一个类时就把特质混入进去,而是在创建实例时将特质加入。在创建有Young
特质的学生的时候,再用val student = new Student13 with Young{}
将特质混入
object Test13_Trait {
def main(args: Array[String]): Unit = {
val student = new Student13 with Young{
//重写冲突的属性
override val name: String ="heria"
def dating():Unit = println(s"student $name is dating")
def study():Unit = println(s"student $name is studying")
}
student.sayHello()
// hello from:heria
// hello from :student heria
student.study()//student heria is studying
student.play()//young people can play
}
}
class Person13{
val name:String = "person"
var age:Int = 18
def sayHello():Unit={
println("hello from:" +name)
}
}
//定义特质
trait Young{
//声明抽象和非抽象属性
var age:Int
val name:String = "young"
def play():Unit = {
println("young people can play")
}
def dating():Unit
}
class Student13 extends Person13{
//重写父类方法
override def sayHello(): Unit = {
super.sayHello()
println(s"hello from :student $name")
}
}
特质叠加
由于一个类可以混入(mixin)多个trait,且trait 中可以有具体的属性和方法,若混入
的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。
冲突分为以下两种:
第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。方法的重写需要super
语句,子类在继承父类或者特质时,后一个将前一个同名的方法覆盖,最终super
返回调用的是最后一个特质对应的方法。
第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait (TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。
案例实操
object Test15_TraitOverLying {
def main(args: Array[String]): Unit = {
var ball = new MyFootBall
println(ball.describe())
}
}
//定义球类特征
trait Ball{
def describe():String = "ball"
}
//定义颜色特征
trait ColorBall extends Ball{
var color:String = "red"
override def describe():String = color + "-" +super.describe()
}
//定义种类特征
trait CategoryBall extends Ball{
var category:String = "foot"
override def describe():String = category + "-" +super.describe()
}
class MyFootBall extends CategoryBall with ColorBall{
override def describe():String ="my ball is a "+super.describe()
}
最终输出的是my ball is a red-foot-ball
。当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()
调用的实际上是排好序后的下一个特质中的 describe()
方法。class MyFootBall extends CategoryBall with ColorBall
排序规则如下:
1)列出混入的第一个特质(Category),作为临时叠加顺序
2)列出混入的第二个特质(Color)的继承关系,并将该顺序叠加到临时顺序前面,已经出现的特质不再重复
3)将子类(MyBall)放在临时叠加顺序的第一个,得到最终的叠加顺序MyClass-->Color-->Category-->Ball