一、包
1. 作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
2. 声明语法
// 第一种:与 Java 一致,以 . 作为层级划分
package package_name
// 第二种:以 {} 作为层级划分
/*
1. 一个源文件中可以声明多个 package
2. 子包中的类可以直接访问父包中的内容,而无需导包
*/
package package_level1 {
package package_level2 {
...
}
}
3. 命名规则
- 只能包含数字、字母、下划线,但不能用数字开头,也不要使用关键字
- 一般按照
com.公司名.项目名.业务模块名
划分层级
package com {
import com.tencent.Inner //父包访问子包需要导包
object Outer {
val out: String = "out"
def main(args: Array[String]): Unit = {
println(Inner.in)
}
}
package tencent {
object Inner {
val in: String = "in"
def main(args: Array[String]): Unit = {
println(Outer.out) //子包访问父包无需导包
}
}
}
}
package com {
package alibaba {
}
}
4. 包对象
-
概念:在 Scala 中可以为每个包定义一个与包同名的包对象,定义在包对象中的成员,可以作为其对应包下所有 class 和 object 的共享变量,可以被直接访问
-
案例:
/** Java 风格包,包名为 com.abc.demo 包对象的源文件名:package.scala com.abc.demo 下的 class 和 object 都能访问包对象的变量和方法 */ package object demo { val commonVal: String = "家里蹲" def commonFunc(): Unit = { println(s"他们正在${commonVal}") } } /** 嵌套风格包,包层级为 com abc demo 包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中 */ package com { package abc { package demo { object TestDemo { def main(args: Array[String]): Unit = { println(school) } } } // 包对象与包声明要在同一作用域 package object demo { val school: String = "AgriculturalUniversity" } } } // 包对象定义在与包不同的作用域,包对象变量访问不到 /* package object demo { val school: String = "AgriculturalUniversity" } */
5. 导包
-
默认导入的包:
import java.lang._
import scala._
import scala.Predef._
-
导包语法:
// 1. 在源文件文件首行导入包,与 Java 一致 import com.abc.Fruit package com.abc.demo object Test { ... } // 2. 局部导入:在使用时导入,仅在当前作用域可以使用 package com.abc.demo object Test { def main(args: Array[String]): Unit = { import com.abc.Fruit ... } def f1(): Unit = { // Fruit 类访问不到 } } // 3. 通配符导入 import java.util._ // 4. 给类起名 import java.util.{ArrayList=>JL} // 5. 导入相同包的多个类 import java.util.{HashSet, ArrayList} // 6. 屏蔽类 import java.util.{ArrayList =>_,_} // 7. 导入包的绝对路径 new _root_.java.util.HashMap
-
案例说明:
案例 说明 import com.abc.Fruit
引入 com.abc 包下 Fruit(class 和 object) import com.abc._
引入 com.abc 下的所有成员 import com.abc.Fruit._
引入 Fruit(object) 的所有成员 import com.abc.{Fruit,Vegetable}
引入 com.abc 下的 Fruit 和 Vegetable import com.abc.{Fruit=>Shuiguo}
引入 com.abc 包下的 Fruit 并更名为 Shuiguo import com.abc.{Fruit=>Shuiguo,_}
引入 com.abc 包下的所有成员,并将 Fruit 更名为 Shuiguo import com.abc.{Fruit=>_,_}
引入 com.abc 包下屏蔽 Fruit 类 new _root_.java.util.HashMap
引入的 Java 的绝对路径
二、类和对象
1. 概念
- 类:对象的抽象或模板
- 对象:表示具体的事物
2. 语法
object Test {
def main(args: Array[String]): Unit = {
val student = new Student()
// student.name // 报错,私有属性不能访问
println(student.age)
println(student.gender)
student.age = 18
println(student.age)
println(student.getStuId)
student.setStuId("002")
println(student.getStuId)
}
}
/*
声明类语法:
[修饰符] class class_name {
属性
方法
}
*/
class Student { // 默认为 public,不能显示的用 public 修饰
private val name: String = "李雷" // 属性默认为 public
var age: Int = _ // _ 表示属性默认值,Int 为 0,String 为 null, Boolean 为 false
var gender: String = _ // _ 只能给 var 型变量赋默认值
@BeanProperty
var stuId: String = "001" // @BeanProperty 可以自动生成规范的 setter/getter 方法
}
// 一个 scala 源文件可以声明多个类
class teacher {
...
}
三、封装
1. 概念
封装是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作
2. 对比 Java
- Java 封装:
- 将属性进行私有化
- 提供一个公共的 set 方法,用于对属性赋值
- 提供一个公共的 get 方法,用于获取属性的值
- Scala 封装:
- Scala 中的 public 属性,底层实际为 private,并通过 get 方法(
obj.field()
)和 set 方法(obj.field_=(value)
)对其进行操作,即底层已经实现封装操作 - 通过
@BeanProperty
注解实现显式的 getter 和 setter 方法可以兼容 Java 框架
- Scala 中的 public 属性,底层实际为 private,并通过 get 方法(
3. 访问权限
- Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字
- private 为私有权限,只在类的内部和伴生对象中可访问
- protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问(与 Java 不同)
- private[包名] 增加包访问权限,包名下的其他类也可以访问
package com.abc.scala.test
object Person {
def main(args: Array[String]): Unit = {
val person = new Person()
person.say()
println(person.name)
println(person.age)
}
}
class Person {
private var name: String = "bobo"
protected var age: Int = 18
var address: String = "幸福里"
private[test] var sex: String = "男"
def say(): Unit = {
println(s"$name $age $address $sex")
}
}
class Worker extends Person {
def test(): Unit = {
// this.name // error
this.age
this.sex
}
override def say(): Unit = { // 重写父类方法
println(s"$age $address $sex")
}
}
class Animal {
def test: Unit = {
// new Person().age // error
new Person().sex
}
}
package com.abc.scala.test2
class Teacher extends Person {
def test(): Unit = {
// this.name // error
this.age
// this.sex // error
}
}
4. 构造器
和 Java 一样,Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法
4.1 基本语法
/*
Scala 类的构造器包括:主构造器和辅助构造器
*/
class class_name(param1: type1,...) { // 主构造器
def this(param1: type1,...) { // 辅助构造器
}
def this(param1: type1,...) { // 辅助构造器可以有多个...
}
}
- 辅助构造器,函数的名称为 this,可以有多个,编译器通过参数的个数及类型来区分
- 辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法
- 构造器调用其他另外的构造器,要求被调用构造器必须提前声明
4.2 案例
object Student {
def main(args: Array[String]): Unit = {
val student = new Student
student.Student()
val student1 = new Student("张三")
val student2 = new Student("李四", 20)
}
}
class Student() { // 无参构造器
var name: String = _
var age: Int = _
println("1. 主构造器被调用")
def this(name: String) {
this()
this.name = name
println("2. 辅助构造器一被调用")
println(s"name:$name age:$age")
}
def this(name: String, age: Int) {
this(name)
this.age = age
println("3. 辅助构造器二被调用")
println(s"name:$name age:$age")
}
def Student(): Unit = {
println("普通方法被调用")
}
}
// 构造器私有化
class Teacher private() {
...
}
5. 构造器参数
-
说明:Scala 类的主构造器的形参包括三种类型
- 未用任何修饰符修饰,此时参数是一个局部变量
- var 修饰参数,作为类的成员属性使用,可以修改
- val 修饰参数,作为类只读成员属性使用,不能修改
-
案例:
object TestConstructor { def main(args: Array[String]): Unit = { val student1 = new Student1("tom", 18) student1.printInfo() val student2 = new Student2("jack", 20) println(s"name:$student2.name age:$student2.age") studen2.name = "curry" println(s"name:$student2.name age:$student2.age") val student3 = new Student3("james", 25) // student3.name = "bob" // error println(s"name:$student3.name age:$student3.age") var student4 = new Student4("james", 25) println(s"name:$student4.name age:$student4.age school:$student4.school") student4 = new Student4("james", 25, "ArtSchool") println(s"name:$student4.name age:$student4.age school:$student4.school") } } // 不使用修饰符 class Student1(_name: String, _age: Int) { // 不推荐使用 // var name: String = _name // var age: Int = _age def printInfo(): Unit = { println(s"name:$name age:$age") } } // 使用 var 修饰符,推荐 class Student2(var name: String, var age: Int) // 使用 val 修饰符 class Student3(val name: String, val age: Int) // 使用 var 修饰符且有辅助构造器 class Student4(var name: String, var age: Int) { var school: String = _ def this(name: String, age: Int, school: String) { this(name, age) this.school = school } }
四、继承和多态
1. 继承
-
语法:
class SonClass extends ParentClass { // 1. scala 是单继承 // 2. 子类继承父类的属性和方法 }
-
案例:
object TestInherit { def main(args: Array[String]): Unit = { val stu1 = new Student("张三", 18) // 1-3 val stu2 = new Student("李四", 20, "1001") // 1-3-4 } } // 定义父类 class Person() { // 空参主构造器 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 Student(name: String, age: Int) extends Person() { // 先调用父类主构造器 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"Student: $name $age $stdNo") } }
2. 多态
2.1 动态绑定比较
-
Java
public class TestDynamicBind { public static void main(String[] args) { Worker worker = new Worker(); System.out.println(worker.name); worker.hello(); worker.hi(); System.out.println("================"); Person person = new Worker(); System.out.println(person.name); // person,静态绑定属性 person.hello(); // hello worker,动态绑定方法 // person.hi(); // error } } class Person { String name = "person"; public void hello() { System.out.println("hello person"); } } class Worker extends Person { String name = "worker"; @override public void hello() { System.out.println("hello worker"); } public void hi() { System.out.println("hi worker"); } }
-
Scala
object TestDynamicBind { def main(args: Array[String]): Unit = { val person: Person = new Worker println(person.name) // worker,动态绑定属性 person.hello() // hello worker,动态绑定方法 } } class Person { val name: String = "person" def hello(): Unit = { println("hello person") } } class Worker extends Person { override val name: String = "worker" override def hello(): Unit = { println("hello worker") } }
2.2 多态案例
object TestMultiStatus {
def main(args: Array[String]): Unit = {
def printInfo(person: Person): Unit = {
person.printInfo()
}
val person = new Person
val student = new Student
val teacher = new Teacher
// 通过动态绑定技术实现同一父类的不同具体实现,调用不同的方法
printInfo(person) // hello person
printInfo(student) // hello student
printInfo(teacher) // hello teacher
}
}
class Person {
def printInfo(): Unit = {
println("hello person")
}
}
class Student extends Person {
override def printInfo(): Unit = {
println("hello student")
}
}
class Teacher extends Person {
override def printInfo(): Unit = {
println("hello teacher")
}
}
五、抽象类
1. 定义
- 抽象类:使用 abstract 关键字修饰,相当于模板类,可以包含抽象的属性方法和非抽象的属性方法,可以被继承
- 抽象属性:没有初始值的属性,只能存在于抽象类中,可以被继承实现
- 抽象方法:没有具体实现的方法,只能存在于抽象类中,可以被继承实现
object TestAbstractClass {
def main(args: Array[String]): Unit = {
val student: Student = new Student
println(student.name)
println(student.age)
println(student.stuNo)
student.eat()
student.sleep()
}
}
// 定义抽象类
abstract class Person {
// 非抽象属性
val name: String = "person"
var age: Int = 19
// 抽象属性
var stuNo: String
// 非抽象方法
def eat(): Unit = {
println("person eat")
}
// 抽象方法
def sleep(): Unit
}
// 定义继承类
class Student extends Person { // 继承类也可以定义为抽象类
override val name: String = "student"
// override var age: Int = 20 // error,var 属性不能被重写
age = 20
var stuNo: String = "1001" // 实现抽象属性
override def eat(): Unit = { // 重写方法
super.eat() // 调用父类的非抽象方法
println("student eat")
}
def sleep(): Unit = { // 实现抽象方法,可以不加 override
println("student sleep")
}
}
2. 匿名子类
object TestAnnoymousClass {
def main(args: Array[String]): Unit = {
val person: Person = new Person {
var name: String = "张三"
def eat(): Unit = println("张三 eat")
}
println(person.name)
person.eat()
}
}
abstract class Person {
var name: String
def eat(): Unit
}
六、单例对象
伴生对象
1. 定义
Scala 语言是完全面向对象的语言,所以并没有静态的概念和操作。为了能够和 Java 语言交互,就产生了一种特殊的对象来模拟类对象, 该对象为单例对象,也称为类的伴生对象。伴生对象名必须与类名一致,这个类的所有“静态”内容都可以在它的伴生对象中声明
2. 语法案例
// 定义一个类
class Student(val name: String, val age: Int) {
def printInfo(): Unit = {
println(s"student: name=$name age=$age school=$Student.school")
}
}
// 定义类的伴生对象
object Student { // 关键字 object,对象名与类名一致
// 定义静态的属性
val school: String = "highSchool"
}
object TestObject {
def main(args: Array[String]): Unit = {
val student1 = new Student("tom", 18)
val student2 = new Student("jerry", 19)
// school 的值是一样的
student1.printInfo()
student2.printInfo()
}
}
3. apply 方法
通过伴生对象的 apply 方法,可以实现不使用 new 方法创建对象
// 定义一个类
class Student private(val name: String, val age: Int) { // 构造方法私有化
def printInfo(): Unit = {
println(s"student: name=$name age=$age school=$Student.school")
}
}
// 定义类的伴生对象
object Student { // 关键字 object,对象名与类名一致
// 定义静态的属性
val school: String = "highSchool"
// 定义 apply 方法,创建伴生类的对象实例
def apply(name: String, age: Int): Student = new Student(name, age)
// apply 方法可以重载
def apply(): Student = {
null
}
}
object TestObject {
def main(args: Array[String]): Unit = {
val student1 = Student.apply("tom", 18)
val student2 = Student("tom", 18) // apply 方法名可以省略
val student3 = Student()
student1.printInfo()
student2.printInfo()
}
}
4. 单例设计模式实现
object TestSingleton {
def main(args: Array[String]): Unit = {
val obj1 = Singleton.getInstance()
val obj2 = Singleton.getInstance()
println(obj1)
println(obj2)
}
}
class Singleton private(name: String, age: Int) {
}
object Singleton {
// 饿汉式
// private val obj: Singleton = new Singleton("孙悟空", 500)
// def getInstance(): Singleton = obj
// 懒汉式
private var obj: Singleton = _
def getInstance(): Singleton = if(obj == null) new Singleton("孙悟空", 500) else obj
}
七、特质
1. 定义
- Scala 语言中,采用特质 (trait) 来代替接口的概念,当多个类具有相同的特质(特征)时,就可以将这个特质独立出来,采用关键字 trait 声明
- Scala 中的 trait 中既可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质
- Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充;同时所有的 Java 接口都可以当做 Scala 特质使用
2. 基本语法
/** 特质声明语法:
trait traitName {
}
*/
trait PersonTrait {
// 定义抽象和非抽象属性
val name: String = "tom"
var age: Int
// 定义抽象和非抽象方法
def say(): Unit = {
println("person can say")
}
def eat(): Unit
}
3. 特质混入
-
语法:
/** 使用 extends 和 with 关键字为类混入特质 */ // 存在父类 class SonClass extends ParentClass with trait1 with trait2 {} // 没有父类 class SubClass extends trait1 with trait2 with trait3 {}
-
案例:
object TestMixIn { def main(args: Array[String]): Unit = { val student1 = new Student student1.eat() student1.study() student1.increase() student1.play() student1.increase() student1.dating() student1.increase() println("===================") // 动态混入 val studentWithTalent = new Student with Talent { override def singing(): Unit = println("student is good at singing") override def dancing(): Unit = println("student is good at dancing") } studentWithTalent.eat() studentWithTalent.study() studentWithTalent.increase() studentWithTalent.play() studentWithTalent.increase() studentWithTalent.dating() studentWithTalent.increase() studentWithTalent.singing() studentWithTalent.dancing() } } class Person { val name: String = "person" val age: Int = 1 def eat(): Unit = println(s"person $name is eating") } trait Young { val name: String = "young" var age: Int def play(): Unit = println("young person $name is playing") def dating(): Unit } trait Kownledge { var amount: Int = 0 def increase(): Unit } trait Talent { def singing(): Unit def dancing(): Unit } class Student extends Person with Young with Kownledge { // 存在多个同名的非抽象属性,必须重写 override val name: String = "student" // 实现抽象方法 def dating(): Unit = println(s"student $name is dating") def increase(): Unit = { amount += 1 println(s"$name kownledge is increasing") } // 定义自有方法 def study(): Unit = println(s"student $name is studying") // 重写父类方法 override def eat(): Unit = println(s"student $name is eating") }
4. 特质叠加
4.1 问题引出
由于一个类可以继承 (extends) 父类和混入 (mixin) 多个 trait,当继承的父类和混入的特质中具有相同的属性和相同的方法 (方法名/参数列表/返回值均相同) 时,必然会出现继承冲突问题
继承冲突分为两种情况:第一种是混入的多个 trait 中具有相同的具体方法,且 trait 之间没有任何关系;混入的多个 trait 中具有相同的具体方法,且 trait 都继承自相同的父 trait,即钻石冲突问题
4.2 解决方法
-
第一种冲突:
object TestOverly { def main(args: Array[String]): Unit = { val student = new Student println(student.name) student.hello() // talent hello - student hello } } class Person { val name: String = "person" def hello(): Unit = { println("person hello") } } trait Kownledge { val name: String = "kownledge" def hello(): Unit = { println("kownledge hello") } } trait Talent { val name: String = "talent" def hello(): Unit = { println("talent hello") } } class Student extends Person with Kownledge with Talent { // 冲突属性必须重写 override val name: String = "student" // 冲突方法必须重写 override def hello(): Unit = { super.hello() // 调用的是最后一个混入特质的该方法 println("student hello") } }
-
第二种冲突:
object TestOverly { def main(args: Array[String]): Unit = { val myBall = new MyBall println(myBall.describe()) // my ball is a red-foot-ball } } trait Ball { def describe(): String = { "ball" } } trait ColorBall { val color: String = "red" def describe(): String = { color + "-" + super.describe() } } trait CategoryBall { val category: String = "foot" def describe(): String = { category + "-" + super.describe() } } class MyBall extends CategoryBall with ColorBall { // 冲突方法必须重写 override def describe(): String = { // super调用采用特质叠加的策略 // 先调用最后一个混入特质的方法再从右往左调用与其同父特质的特质的方法最后调用父特质的方法 "my ball is a " + super.describe() // 如果想要调用某个指定的混入特质中的方法,可以增加约束:super[traitName] // "my ball is a " + super[CategoryBall].describe() } }
5. 特质自身类型
5.1 概念
-
特质自身类型是在特质或类中定义一个外部的类或特质的持有,相当于 Java Spring 中的依赖注入
-
语法:
trait traitName { /* _ 表示自身类型别名的通配 type 是持有的具体类名或特质名 */ _: type => }
5.2 案例
模拟用户注册
object TestSelfType {
def main(args: Array[String]): Unit = {
val regUser = new RegUser("bob", "123456")
regUser.insertUser()
}
}
// 定义用户类
class User(var name: String, var password: String)
// 定义用户操作特质
trait UserDao {
// 使用特质自身类型将 User 类引入,类似于 spring 的依赖注入
/* @Autowired
private User user;
*/
_: User =>
// 插入用户信息到数据库
def insertUser(): Unit = { // 方法通过自身类型 this 可以获取 User 信息
println(s"insert into user:${this.name} into db")
}
}
// 定义注册用户类,继承用户类并混入用户操作特质
class RegUser(name: String, password: String) extends User(name, password) with UserDao
6 特质VS抽象类
- 优先使用特质,一个类扩展多个特质是很方便的,但却只能扩展一个抽象类
- 如果需要构造函数参数,则使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行 (有无参构造器)
八、扩展
1. 类型检查和转换
class Person
class Student extends Person
object TestExtends {
def main(args: Array[String]): Unit = {
val person: Person = new Person
val person2: Person = new Student
val student: Student = new Student
//(1)判断对象是否为某个类型的实例:isInstanceOf[className]
val res1: Boolean = person.isInstanceOf[Person] // true
val res2: Boolean = person.isInstanceOf[Student] // false
val res3: Boolean = person2.isInstanceOf[Student] // true
val res4: Boolean = student.isInstanceOf[Person] // true
//(2)将对象转换为某个类型的实例:asInstanceOf[className]
if (res3) { // 首先对象必须为该类型实例才能转换
val student2: Student = person2.asInstanceOf[Student]
println(student2)
}
//(3)获取类的信息:classOf[className]
val pClass: Class[Person] = classOf[Person]
println(pClass)
}
}
2. 枚举类和应用类
/*
枚举类定义语法:
object objName extends Enumeration {
val fieldName = Value(i: Int, value: String)
}
*/
object WorkDay extends Enumeration {
val MONDAY = Value(1, "星期一") // 枚举类属性一般全大写
val TUESDAY = Value(2, "星期二")
}
/*
应用类定义语法:
object objName extends App {
// 可执行的语句
}
*/
object MyApp extends App { // 必须定义为 object
// 省略了 main 方法,内部有 main 方法的封装
println(WorkDay.MONDAY)
}
3. Type 定义新类型
// 使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
object Test {
def main(args: Array[String]): Unit = {
type MyString = String
var str: MyString = "abc"
def test(): MyString = "xyz"
}
}