1. 对象(object)
在scala 中被object 关键字修饰的类有一下特征
- 没有 有参数的主构造器, 但是有主构造器代码块(不包含在任何方法中的代码,就是主构造器代码)
- 它是单例的所以主构造器代码块只会执行一次
- 不需要通过关键字 new 创建对象,直接通过类名创建对象
- 通常用于封装一些常量,工具类,枚举和隐式转换函数
1.1 对象的定义
object ObjectDemo1 {
def main(args: Array[String]): Unit = {
println("ObjectDemo1 的主构造器代码块1")
def sayHello = println("hello ! ")
}
}
object ObjectDemo1_1 {
def main(args: Array[String]): Unit = {
val o1 = ObjectDemo1
val o2 = ObjectDemo1
println(o1.hashCode() == o2.hashCode()) //true
}
}
object ObjectDemo1_2 extends App{
println("ObjectDemo1_2 的主构造器代码块1")
}
1.2 对象作为枚举类
object ObjectDemo2 extends App{
println(MyEnum.WOLF) //Wolf
println(MyEnum.withName("Wolf")) //Wolf
//遍历枚举值
val values:MyEnum.ValueSet = MyEnum.values
values.foreach(println) //Wolf ,dog ,cat
//通过下标获取枚举值
println(MyEnum(0)) //Wolf
}
object MyEnum extends Enumeration{
val WOLF = Value(0,"Wolf")
println(s"WOLF=$WOLF")
val DOG = Value(1,"dog")
println(s"DOG=$DOG")
val CAT = Value(2,"cat")
println(s"CAT=$CAT")
}
2. 类(class)
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
2.1 类的定义
class Class1 {
// 在scala 类中定义字段有两个关键字 val 和 var
// 使用val修饰的字段属于常量,只能访问变量的值,不能修改变量的值
val name:String = "class1"
// 使用var修饰的字段属于变量,既能访问变量的值,也能修改变量的值
var age:Int =15
val country = "China"
// 使用private [this] 修饰的成员属性/方法只能在本类中访问
private [this] val color="red"
// color 在外部不能直接访问,可以在本类中使用方法访问后返回
def getColor= {
// color 是使用private [this] 修饰的只能在本类中访问
color
}
// def 定义成员方法, 每次调用时重新计算
def email = {
name + "@mail.com"
}
}
/**
* 限制类的访问范围只能在classpag 包下
*/
private [classpag] class Class2 {
val name:String = "class2"
}
// 类的实例化
val c1 = new Class1
println(c1.country) // China
println(c1.getColor) //red
// val 定义的属性只读不可写
// c1.country = "Japan" // 报错
println(c1.email) // class1@mail.com
// 无法访问到getColor 方法和 color属性
2.2 类的构造器(构造方法)
在scala 中类的构造器分为两种
主构造器:与类名交织在一起,一个类只能定义一个主构造器
- 创建对象的顺序一定是主构造器最先执行
- 主构造器的参数会成为类的字段属性
- 在主构造器参数中,没有被任何关键字修饰的参数,默认为private [this] var/val
- 不包含在任何方法中的代码,就是主构造器的代码
辅助构造器:辅助构造器可以定义多个
- 辅助构造器的第一行代码必须调用主构造器
- 创建对象的顺序一定是主构造器最先执行
class Class3(
val name:String, // 定义常量成员属性
var age:Int, // 定义成员变量
height:Int, // 此时 height 相当于使用private this val 修饰的,只能在本类中使用
private var brothday2:String, // 定义成员变量,只能在奔类中访问
var color:String="blue" // 定义默认值的成员变量,只能在参数的末尾
){
println("这是主构造器的代码")
// 定义成员变量
var sex:String=""
println(s"print in Class3 height= ${this.height}")
// 定义一个辅助构造器1
// 辅助构造器中的属性不能使用val 或者var来修饰
def this(name:String,age:Int){
//辅助构造器的第一行代码必须是调用主构造器
this(name,age,170,null,null)
this.sex = "男"
println("辅助构造器1")
}
//可以定义多个辅助构造器2
def this(name:String){
this(name,16) //辅助构造器可以调用辅助构造器
this.brothday2="birthday2"
println("辅助构造器2")
}
}
object ClassDemo2 {
def main(args: Array[String]): Unit = {
// 调用主构造器实例化类
val c3 = new Class3("xiaom",18,180,"birthday")
println("name=>"+c3.name)
println("age=>"+c3.age)
println("color=>"+c3.color)
println("sex=>"+c3.sex)
/**
* 这是主构造器的代码
* print in Class3 height= 180 //height 只能在Class3 类中访问到
* name=>xiaom
* age=>18
* color=>blue // 使用默认值 blue
* sex=>
*/
// 调用 辅助构造器1 实例化类
val c3_1 = new Class3("xiaob",19)
println("name=>"+c3_1.name)
println("age=>"+c3_1.age)
println("color=>"+c3_1.color)
println("sex=>"+c3_1.sex)
/**
* 这是主构造器的代码
* print in Class3 height= 170 //height 只能在Class3 类中访问到
* 辅助构造器1
* name=>xiaob
* age=>19
* color=>null //因为在使用构造器的时候传的是null 所以这里不用默认值
* sex=>男
*/
// 调用 辅助构造器2 实例化类
val c3_2 = new Class3("xiaos")
println("name=>"+c3_2.name)
println("age=>"+c3_2.age)
println("color=>"+c3_2.color)
println("sex=>"+c3_2.sex)
/**
* 这是主构造器的代码
* print in Class3 height= 170
* 辅助构造器1
* 辅助构造器2
* name=>xiaos
* age=>16
* color=>null
* sex=>男
*/
}
}
3. 伴生类与伴生对象
定义:
- 在同一个
scala的类文件中
- 如果
class和object的名字相同
- 那么class是object的伴生类
- object是class的伴生对象
- 在同一个
特点:
- 伴生类与伴生对象可以互相访问被关键字private修饰的字段,需要注意的是
被private this 修饰的对象或属性不能访问
- 可以通过伴生类与伴生对象创建对象
- 伴生类与伴生对象可以互相访问被关键字private修饰的字段,需要注意的是
- 创建伴生类与伴生对象
// class ClassDemo4 是 object ClassDemo4 的伴生类
class ClassDemo4 {
//被private修饰的字段只能在本类及其伴生对象中访问
private val name:String = "xiaom"
//被 private [this] 修饰的对象,只能在本类中访问,伴生对象也不能访问
private [this] var tel:String = "15000000000"
//向外部暴露get和set方法 以对private 修饰的字段进行 取值与赋值
def getTel() = {
this.tel
}
def setTel(tel:String): Unit ={
this.tel = tel
}
}
// object ClassDemo4 是 class ClassDemo4 的伴生对象
object ClassDemo4 {
def main(args: Array[String]): Unit = {
val c4 = new ClassDemo4
println(c4.name) //xiaom
//tel 被 private [this] 修饰,伴生对象中也不能访问
//println(c4.tel)
// 需要通过类中定义的方法访问 tel 属性
c4.setTel("new number")
println(c4.getTel()) //new number
}
}
object ClassDemo4_1 {
def main(args: Array[String]): Unit = {
val c4 = new ClassDemo4
// 不是 ClassDemo4 的伴生对象则不能访问name 属性
//println(c4.name) //error
}
}
- 通过伴生类与伴生对象创建对象
class MyArray{//伴生类
private var name:String = "小明"
println(MyArray.name)
}
object MyArray{ // 伴生对象
private var name:String = "小红"
println(new MyArray().name)
def apply(nums:Int*):Array[Int] = {
println("apply(nums:Int*)")
val arr = new Array[Int](nums.length)
for(i<- 0 to nums.length -1){
arr(i) = nums(i)
}
arr
}
}
object ObjectDemo05 {
def main(args: Array[String]): Unit = {
var myarr1 = MyArray(5) // 通过伴生对象创建对象 通过调用apply方法创建出来的对象不相同
var myarr2 = new MyArray() //通过伴生类创建对象
}
}
3.1 apply 方法
当对象(伴生对象)以函数的方式进行调用时,scala 会隐式地将调用改为在该对象上调用apply方法。
Scala 的apply有2张形式,一种是伴生对象的apply,一种是伴生类中的apply。
// object ClassDemo5 是 class ClassDemo5 的伴生对象
object ClassDemo5 {
println("hello world")//主构造代码块
def apply():String={
println("apply() in object")
"hello"
}
def apply(nums:Int*):ArrayBuffer[Int]={
println("apply(nums:Int*)")
val arr = new ArrayBuffer[Int]()
nums.map(arr.append(_))
arr
}
}
// class ClassDemo5 是 object ClassDemo5 的伴生类
class ClassDemo5 {
def apply() = println("apply in class")
def say: Unit = {
println("say Hello apply in class")
}
}
object ClassDemo5_1 extends App{
//调用无参数的apply()方法,创建ClassDemo5对象
val c5 = ClassDemo5() //返回的就是
println(c5.hashCode())
/**
* hello world
* apply() in object
*/
//调用可变参数的apply()方法,创建ClassDemo5对象
val c5_1 = ClassDemo5(1,2,3,4,5) //返回的就是一个集合
println(c5_1.toBuffer)
println(c5_1.hashCode())
/**
* apply(nums:Int*)
* ArrayBuffer(1, 2, 3, 4, 5)
*/
//调用class中的apply方法
val c5_2 = new ClassDemo5()
c5_2.say
/**
* apply in class
* say Hello apply in class
*/
}
3. 内部类
// 每个外部类的实例对象所持有的内部类都是不同的类
class Persion{
class Student
//val listBuffer = ListBuffer[Student]()
//类型投影
val listBuffer = ListBuffer[Persion#Student]()
def getStudent:Student={
new Student
}
}
object ObjectDemo08 {
def main(args: Array[String]): Unit = {
val p1 = new Persion
val s1 = p1.getStudent
p1.listBuffer += s1
val p2 = new Persion
val s2 = p2.getStudent
//因为外部类所持有的内部类不是相同的对象 所以不能放进到listBuffer中
//1.可通过外部类的伴生对象扩大内部类的影响范围,因为对象是单例的所以是相同类型
//2. 可以通过外部类的类型投影来扩大内部类的影响范围
//p1.listBuffer += s2
}
}
4. 匿名类
object ClassDemo6 {
def main(args: Array[String]): Unit = {
//这里并不是直接实例化对象 而是创建类的匿名类的实例对象
new ClassDemo6 {
override def sayHello(): Unit = {
println("hello ClassDemo6 的匿名类")
}
}.sayHello()//hello persion04 的匿名子类
//创建 Animal的匿名子类的实例对象
val a = new ClassDemo6_1("小明",12){
override def say: Unit = {
println(s"hello ClassDemo6_1 匿名类 $name $age")
}
}
a.say //hello 子类 小明 12
val b = new ClassDemo6_1("小明",12)
println(a.getClass) //
println(b.getClass) //
/**
* hello ClassDemo6 的匿名类
* hello ClassDemo6_1 匿名类 小明 12
* class hnbian.scala.classobject.classpag.ClassDemo6$$anon$2
* class hnbian.scala.classobject.classpag.ClassDemo6_1
*/
}
}
5. 抽象类
object ClassDemo8 {
def main(args: Array[String]): Unit = {
val c8 = new ClassDemo8("xiaom",18)
c8.sayHello //hello xiaom
}
}
// 定义一个抽象类
abstract class ClassDemo8_1 {
def sayHello // 抽象方法
val name: String //定义常量,没有赋值
var age: Int //定义变量,没有赋值
val id = 1001 // 定义常量并初始化
var number = 1001 // 定义变量并初始化
}
class ClassDemo8(val n:String,val a:Int) extends ClassDemo8_1{
// 继承抽象类需要重写抽象类中的抽象方法
override def sayHello: Unit = {
println(s"hello $name")
}
override val name: String = n
override var age: Int = a
// id=1002 // 父类中的id使用val修饰的所以不能覆盖
number = 1002
}
6. 样例类(case class)
scala 中被case修饰的类被称为样例类样例类和普通的类基本一致只是可以不使用new 关键字来创建对象
特点:
- 新建类实例不用new
- 自动定义好getXX方法,
- 提供默认的toString()
- 结合类继承可以通过模式匹配进行分解
6.1 样例类的定义
object ClassDemo7 {
def main(args: Array[String]): Unit = {
// 新建类实例不用new
val c7 = ClassDemo7_1("xioam",12)
//自动定义好getXX方法
println(c7.name)
println(c7.howOld())
//提供默认的toString()
println(c7.toString) //ClassDemo7_1(xioam,12)
val c7_1 = ClassDemo7_1("xioam",12)
}
}
// 定义一个样例类
case class ClassDemo7_1(name:String,age:Int){
def howOld(): Int ={
age
}
}
6.2 样例类结合模式匹配
class Amimal
case class Cat(age: Int) extends Amimal
case class Dog(age: Int, name: String) extends Amimal
object ClassDemo7_1 {
def main(args: Array[String]): Unit = {
println(f(Cat(5))) //cat age = 5
println(f(Dog(3, "dahuang"))) //dahuang age = 3
}
def f(t: Amimal) = t match {
case Cat(x) => "cat age = " + x
case Dog(x, y) => y + " age = " + x
}
}
6.3 样例类代替tuple
case class Dog(age: Int, name: String)
object ClassDemo7_2{
def main(args: Array[String]): Unit = {
val p1 = (20,"dahuang")
println(p1._1) //20
println(p1._2) //dahuang
//好处是简洁,但无意义
val p2 = Dog(20,"dahuang")
println(p2.age)
println(p2.name)
// 好处是有名字,自说明,可读性强
}
}
6.4 普通类与样例类的区别
在Scala中存在case class,它其实就是一个普通的class。但是它又和普通的class略有区别,如下:
- 初始化的时候可以不用new,当然你也可以加上,普通类一定需要加new;
scala> case class Student(name:String)
defined class Student
scala> val stu = Student("xiaoming")
stu: Student = Student(xiaoming)
scala> val stu2 = new Student("xiaohong")
stu2: Student = Student(xiaohong)
- toString的实现更漂亮;
scala> stu
res5: Student = Student(xiaoming)
- 默认实现了equals 和hashCode;
scala> val stu3 = new Student("xiaohong")
stu2: Student = Student(xiaohong)
scala> stu3 == stu2
res6: Boolean = true
- 默认是可以序列化的,也就是实现了Serializable ;
scala> class A
defined class A
scala> import java.io._
import java.io._
scala> val bos = new ByteArrayOutputStream
bos: java.io.ByteArrayOutputStream =
scala> val oos = new ObjectOutputStream(bos)
oos: java.io.ObjectOutputStream = java.io.ObjectOutputStream@4c257aef
scala> oos.writeObject("xiaoming")
scala> val a = new A
a: A = $iwC$$iwC$A@71687b10
scala> oos.writeObject(a)
java.io.NotSerializableException: $iwC$$iwC$A
自动从scala.Product中继承一些函数;
case class构造函数的参数是public级别的,我们可以直接访问;
scala> stu.name
res11: String = xiaoming
- 支持模式匹配;
其实感觉case class最重要的特性应该就是支持模式匹配。这也是我们定义case class的唯一理由,难怪Scala官方也说:It makes only sense to define case classes if pattern matching is used to decompose data structures. 。来看下面的例子:
object TermTest extends scala.App {
def printTerm(term: Term) {
term match {
case Var(n) =>
print(n)
case Fun(x, b) =>
print("^" + x + ".")
printTerm(b)
case App(f, v) =>
print("(")
printTerm(f)
print(" ")
printTerm(v)
print(")")
}
}
def isIdentityFun(term: Term): Boolean = term match {
case Fun(x, Var(y)) if x == y => true
case _ => false
}
val id = Fun("x", Var("x"))
val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
printTerm(t)
println
println(isIdentityFun(id))
println(isIdentityFun(t))
}
7. 继承(extends)
- 如果子类继承的父类是抽象类,那么要实现父类中所有未实现的方法与字段。
- 当子类重写父类的非抽象字段时,父类中该字段不能用val 修饰否则会报异常。
- 父类中抽象字段不能使用private修饰,因为抽象字段本身就是需要子类去实现的,如果添加private关键字,那么子类无法访问,则抛异常
- 子类重写父类的非抽象方法时必须要添加 override 关键字,如果需要指明调用父类的方法,需要使用supper.say()方法
- 子类继承父类后,在创建子类的实例对象时,会先初始化父类的构造函数,然后执行子类自己的构造函数
- 如果子类的构造函数和父类的构造函数,接收的参数名字是一样的,那么子类的相同的参数名字就不能添加任何关键字进行修饰,否则scala会认为子类要重写父类的字段
7.1 继承示例
class Animal{
println("Animal 的主构造器代码")
def say={
println("hello")
}
}
class Dog extends Animal {
println("Dog 的主构造器代码")
override def say: Unit = {
println("hello 汪汪")
}
}
class Cat extends Animal {
println("Cat 的主构造器代码")
override def say: Unit = {
println("hello 喵喵")
}
}
object ExtendsDemo1 {
def main(args: Array[String]): Unit = {
val dog = new Dog
dog.say // hello 汪汪
/**
* Animal 的主构造器代码
* Dog 的主构造器代码
* hello 汪汪
*/
val cat = new Cat
cat.say // hello 喵喵
/**
* Animal 的主构造器代码
* Cat 的主构造器代码
* hello 喵喵
*/
}
}
- 继承抽象类
// 定义抽象类
abstract class ExtendsDemo3_1{
//定义抽象字段
val name:String
//定义具体字段
val age:Int = 18
//定义抽象方法(必须在抽象类中)
def sayHello(context:String)
//也可以定义具体的方法
def say(context:String)={
println("say "+context)
}
}
/**
* 子类继承抽象类要实现父类中所有未实现的方法与字段
*/
class ExtendsDemo3 extends ExtendsDemo3_1{
// 实现父类的name字段
override val name: String = "小明"
//子类重写父类的非抽象字段,父类中该字段不能用val 修饰否则会报异常
override val age = 12
override def sayHello(context: String): Unit = {
println("say hello "+context)
}
//重写父类的非抽象方法必须要添加override,要指明调用父类的方法,需要使用supper.say()方法
override def say(context: String) = {
super.say(context)
}
}
7.3 匿名类的继承
object ClassDemo6 {
def main(args: Array[String]): Unit = {
//这里并不是直接ClassDemo6 而是创建ClassDemo6的匿名子类的实例对象
new ClassDemo6 {
override def sayHello(): Unit = {
println("hello ClassDemo6 的匿名子类")
}
}.sayHello()//hello ClassDemo6 的匿名子类
//创建 ClassDemo6_1 的匿名子类的实例对象
val a = new ClassDemo6_1("小明",12){
override def say: Unit = {
println(s"hello ClassDemo6_1 的匿名子类 $name $age")
}
}
a.say // hello ClassDemo6_1 的匿名子类 小明 12
val b = new ClassDemo6_1("小明",12)
println(a.getClass)
//class hnbian.scala.classobject.classpag.ClassDemo6$$anon$2
println(b.getClass)
//class hnbian.scala.classobject.classpag.ClassDemo6_1
}
}
abstract class ClassDemo6{
def sayHello()={
println("helo")
}
}
class ClassDemo6_1 (var name:String , var age:Int){
def say={
println(s"hello $name $age")
}
}
7.2 isInstanceOf / asInstanceOf
- isInstanceOf 用来判断实例对象是否是指定类及其子类, 那么意味着isInstanceOf 不能做精确判断
- asInstanceOf 用来将对象转换成其他类型,在用asInstanceOf 之前,应该使用isInstanceOf 先做判断
// isInstanceOf 与 asInstanceOf
object ExtendsDemo1_1 {
def main(args: Array[String]): Unit = {
val dog:Any = new Dog
//isInstanceOf 用来判断实例对象是否是指定类及其子类, 也就是说 isInstanceOf 不能做精确判断
println(s"dog.isInstanceOf[Animal]=${dog.isInstanceOf[Animal]}") //dog.isInstanceOf[Animal]=true
if(dog.isInstanceOf[Animal]){
//asInstanceOf 用来将对象转换成其他类型,在用asInstanceOf 之前,应该使用isInstanceOf 先做判断
val d = dog.asInstanceOf[Animal]
d.say
}
//使用isInstanceOf不能做精确判断,如果要使用精确判断可以使用 getClass 和classOf配合起来使用
val cat = new Cat
println(cat.getClass == classOf[Cat])
}
}
8. 特质(Trait)
- 在scala中triat是一种特殊概念,triat的用途有很多,可以作为类似java中的interface接口使用
- 类可以支持对triat的多重继承,继承的时候统一使用extends关键字,多继承时使用with关键字
8.1 特质的定义
// 定义一个trait
trait TraitDemo1_1{
// trait 中的抽象方法
def say(content:String):Unit
//使用val修饰的非抽象字段,不能被重写
val name:String = "小明"
//使用var修饰的非抽象字段,可以被重写
var name2:String = "小红"
//抽象字段,不管是使用var还是val修饰的抽象字段都可以被重写
val age:Int
}
// 定义一个trait
trait TraitDemo1_2{
def sayHello(content:String):Unit
}
/**
* 在这里trait 是作为接口使用,那么久必须实现trait的抽象方法
* 继承父类的字段不会添加到类当中 继承的trait的字段会添加到该类中
*/
class TraitDemo1_3 extends TraitDemo1_1 with TraitDemo1_2 {
//类实现triat的抽象方法,可以不使用override关键字,但实现非抽象方法一定要使用override
def say(content: String): Unit = {
println(content)
}
//类实现重写 triat的非抽象方法,一定要使用override
override def sayHello(content: String): Unit = {
println(s"hello $content")
}
// name 是用val 修饰,不能被重写
// name = "xiaom"
// name2 是用var 修饰,可以被重写
name2 = "xiaoh"
//可以重写trait 中的抽象字段,无论该字段用val 还是var修饰
override val age: Int = 10
}
object TraitDemo1 {
def main(args: Array[String]): Unit = {
val t = new TraitDemo1_3
t.say("hello") //hello
t.sayHello("xiaom") //hello xiaom
}
}
8.2 特质的构造函数
继承了类和Trait的构造机制如下
- 执行父类的构造代码块
- 执行Trait的构造代码块,如果继承多个Trait 那么从左往右执行Trait的构造代码块
- 如果多个Trait继承了同一个Trait,那么限制性父Trait的构造代码块,然后再执行子Trait的构造代码块
- 父Trait的构造代码块只会执行一次
- 最后执行自己的构造代码块,即子类的构造代码块最后执行
//trait 也有主构造代码块,不包含在任何方法中的代码就是trait的主构造器代码块
trait TraitDemo2_1{
println("TraitDemo2_1 的主构造代码块")
}
trait TraitDemo2_2 extends TraitDemo2_1{
println("TraitDemo2_2 的主构造代码块(extends trait)")
}
trait TraitDemo2_3 extends TraitDemo2_1{
println("TraitDemo2_3 的主构造代码块(extends trait)")
}
class TraitDemo2_4{
println("TraitDemo2_4 的主构造代码块(extends class)")
}
class TraitDemo2_5 extends TraitDemo2_4 with TraitDemo2_2 with TraitDemo2_3{
println("TraitDemo2_5 的主构造代码块")
}
object TraitDemo2 {
def main(args: Array[String]): Unit = {
val s = new TraitDemo2_5
/**
TraitDemo2_4 的主构造代码块(extends class)
TraitDemo2_1 的主构造代码块
TraitDemo2_2 的主构造代码块(extends trait)
TraitDemo2_3 的主构造代码块(extends trait)
TraitDemo2_5 的主构造代码块
*/
}
}
8.3 在实体类中混入特质
trait TraitDemo3_1{
def say: Unit ={
println("hello")
}
}
//一个类继承了一个父类或trait,那么这个类拥有该父类或者trait的所有非私有方法
class TraitDemo3_2(var name:String,var age:Int)
object TraitDemo3 {
def main(args: Array[String]): Unit = {
val b1 = new TraitDemo3_2("小明",1)
//为实例对象混入trait,那么该实例对象拥有trait的所有非私有方法
val b2 = new TraitDemo3_2("小明",1) with TraitDemo3_1
b2.say
}
}